session: define Core interface at consumer, use concrete *session.Session

Core interface defined in 9p/session/core.go lists the methods
the 9P layer needs. The real *session.Session satisfies it implicitly.
Test stubs continue to work via interface satisfaction.
This commit is contained in:
Levi Neely 2026-07-29 19:25:27 +02:00
parent 50f4a06d2e
commit cf894920fa
3 changed files with 62 additions and 7 deletions

View File

@ -165,7 +165,7 @@ func newTestSessionManagerWithCore(t *testing.T) *session.Manager {
Sink: sink, Sink: sink,
ReadFile: func(string) ([]byte, error) { return []byte("#!/bin/sh\n"), nil }, ReadFile: func(string) ([]byte, error) { return []byte("#!/bin/sh\n"), nil },
MkdirAll: func(string, os.FileMode) error { return nil }, MkdirAll: func(string, os.FileMode) error { return nil },
NewCore: func(sessionID, agentName, cwd string) (agent.Session, error) { NewCore: func(sessionID, agentName, cwd string) (session.Core, error) {
return &stubCore{state: "idle", backend_: "stub", model: "m", agentName: agentName, cwd: cwd}, nil return &stubCore{state: "idle", backend_: "stub", model: "m", agentName: agentName, cwd: cwd}, nil
}, },
}) })
@ -1221,7 +1221,7 @@ func TestSessionManagerCreateSessionEnvExpansion(t *testing.T) {
Sink: sink, Sink: sink,
ReadFile: func(string) ([]byte, error) { return []byte("#!/bin/sh\n"), nil }, ReadFile: func(string) ([]byte, error) { return []byte("#!/bin/sh\n"), nil },
MkdirAll: func(string, os.FileMode) error { return nil }, MkdirAll: func(string, os.FileMode) error { return nil },
NewCore: func(sessionID, agentName, cwd string) (agent.Session, error) { NewCore: func(sessionID, agentName, cwd string) (session.Core, error) {
gotCwd = cwd gotCwd = cwd
return &stubCore{state: "idle", backend_: "stub", model: "m", agentName: agentName, cwd: cwd}, nil return &stubCore{state: "idle", backend_: "stub", model: "m", agentName: agentName, cwd: cwd}, nil
}, },
@ -1248,7 +1248,7 @@ func TestSessionManagerCreateSessionTildeExpansion(t *testing.T) {
Sink: sink, Sink: sink,
ReadFile: func(string) ([]byte, error) { return []byte("#!/bin/sh\n"), nil }, ReadFile: func(string) ([]byte, error) { return []byte("#!/bin/sh\n"), nil },
MkdirAll: func(string, os.FileMode) error { return nil }, MkdirAll: func(string, os.FileMode) error { return nil },
NewCore: func(sessionID, agentName, cwd string) (agent.Session, error) { NewCore: func(sessionID, agentName, cwd string) (session.Core, error) {
gotCwd = cwd gotCwd = cwd
return &stubCore{state: "idle", backend_: "stub", model: "m", agentName: agentName, cwd: cwd}, nil return &stubCore{state: "idle", backend_: "stub", model: "m", agentName: agentName, cwd: cwd}, nil
}, },

55
session/core.go Normal file
View File

@ -0,0 +1,55 @@
package session
import (
"context"
"github.com/simonfxr/pubsub"
agent "ollie/session"
"ollie/backend"
)
// Core is the interface that a session implementation must satisfy.
// Defined here (at the consumer) rather than in ollie/session so that
// the core package exports only a concrete struct with no interface overhead.
// Tests can substitute a lightweight stub.
type Core interface {
Submit(ctx context.Context, input string)
Interrupt(cause error) bool
Inject(prompt string)
Queue(prompt string)
PopQueue() (string, bool)
Bus() *pubsub.Bus
IsRunning() bool
State() string
Reply() string
AgentName() string
BackendName() string
ModelName() string
CWD() string
SetCWD(dir string) error
SetSessionID(newID string) error
Context() []backend.Message
SystemPrompt() string
GenerationParams() backend.GenerationParams
SetGenerationParams(params backend.GenerationParams) error
CompactionModel() string
SetCompactionModel(model string)
Usage() string
Cost() string
CtxSz() string
ListModels() string
WaitChange(ctx context.Context, field, current string) (string, bool)
SaveSession(path string) error
Close()
Detach() bool
ListDetached() []agent.DetachedInfo
SignalDetached(pid, signal int) error
GetDetachedOutput(pid int) (string, error)
DismissDetached(pid int) bool
InjectSystemEvent(content string)
React(emoji string)
ReactTo(responseID, emoji string) error
Reactions() map[string]string
ToolCallCount() int64
SetEnv(key, value string)
}

View File

@ -29,7 +29,7 @@ type Session struct {
mu sync.RWMutex mu sync.RWMutex
id string id string
uname string // immutable user principal (numeric UID), set at creation uname string // immutable user principal (numeric UID), set at creation
Core agent.Session Core Core
Ctx context.Context Ctx context.Context
cancel context.CancelFunc cancel context.CancelFunc
log []byte log []byte
@ -46,7 +46,7 @@ type Session struct {
modelsCacheAt time.Time modelsCacheAt time.Time
} }
func NewSession(id string, core agent.Session, ctx context.Context, cancel context.CancelFunc) *Session { func NewSession(id string, core Core, ctx context.Context, cancel context.CancelFunc) *Session {
sess := &Session{id: id, Core: core, Ctx: ctx, cancel: cancel} sess := &Session{id: id, Core: core, Ctx: ctx, cancel: cancel}
sess.startEventLog() sess.startEventLog()
return sess return sess
@ -236,7 +236,7 @@ type ManagerConfig struct {
MkdirAll func(string, os.FileMode) error MkdirAll func(string, os.FileMode) error
// NewCore, if non-nil, replaces the default backend.New + agent.New // NewCore, if non-nil, replaces the default backend.New + agent.New
// path. It receives the session ID, agent name, and cwd, and returns a Core. // path. It receives the session ID, agent name, and cwd, and returns a Core.
NewCore func(sessionID, agentName, cwd string) (agent.Session, error) NewCore func(sessionID, agentName, cwd string) (Core, error)
// Strict rejects inline code steps; only tool steps are allowed. // Strict rejects inline code steps; only tool steps are allowed.
Strict bool Strict bool
// Yolo skips the landrun sandbox. // Yolo skips the landrun sandbox.
@ -1122,7 +1122,7 @@ func (s *Manager) CreateSession(args []string) (string, error) {
return "", fmt.Errorf("session already exists: %s", sessID) return "", fmt.Errorf("session already exists: %s", sessID)
} }
var core agent.Session var core Core
var sessPtr *Session var sessPtr *Session
uname := s.nextUname() uname := s.nextUname()
if s.cfg.NewCore != nil { if s.cfg.NewCore != nil {