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,
ReadFile: func(string) ([]byte, error) { return []byte("#!/bin/sh\n"), 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
},
})
@ -1221,7 +1221,7 @@ func TestSessionManagerCreateSessionEnvExpansion(t *testing.T) {
Sink: sink,
ReadFile: func(string) ([]byte, error) { return []byte("#!/bin/sh\n"), 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
return &stubCore{state: "idle", backend_: "stub", model: "m", agentName: agentName, cwd: cwd}, nil
},
@ -1248,7 +1248,7 @@ func TestSessionManagerCreateSessionTildeExpansion(t *testing.T) {
Sink: sink,
ReadFile: func(string) ([]byte, error) { return []byte("#!/bin/sh\n"), 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
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
id string
uname string // immutable user principal (numeric UID), set at creation
Core agent.Session
Core Core
Ctx context.Context
cancel context.CancelFunc
log []byte
@ -46,7 +46,7 @@ type Session struct {
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.startEventLog()
return sess
@ -236,7 +236,7 @@ type ManagerConfig struct {
MkdirAll func(string, os.FileMode) error
// 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.
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 bool
// 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)
}
var core agent.Session
var core Core
var sessPtr *Session
uname := s.nextUname()
if s.cfg.NewCore != nil {