agent: delegate sessionID, cwd, env, uname to session.Session
Continue moving session concerns to the session package: - sessionID → sess.ID()/SetID() - cwd → sess.CWD()/SetCWD() - env/envMu → sess.SetEnv()/Env() - uname → sess.Uname() Mutex responsibilities are cleanly split: - session.Session.mu protects: state, reply, cwd, id - session.Session.envMu protects: env map - agent.mu protects: history, runtime (agent-specific state)
This commit is contained in:
parent
64f3f18573
commit
6396167361
|
|
@ -175,9 +175,9 @@ func (s *agent) handleCommand(ctx context.Context, input string) bool {
|
|||
return
|
||||
}
|
||||
d := s.newDispatcher()
|
||||
env := []string{"OLLIE_SESSION_ID=" + s.sessionID, "OLLIE_UNAME=" + s.uname}
|
||||
env := []string{"OLLIE_SESSION_ID=" + s.sess.ID(), "OLLIE_UNAME=" + s.sess.Uname()}
|
||||
env = append(env, s.promptEnvExtra...)
|
||||
rt := BuildRuntime(cfg, d, s.cwd, env, s.baseLayers...)
|
||||
rt := BuildRuntime(cfg, d, s.sess.CWD(), env, s.baseLayers...)
|
||||
if rt.CfgBackend != "" {
|
||||
newBe, err := s.newBackend(rt.CfgBackend)
|
||||
if err != nil {
|
||||
|
|
@ -226,8 +226,8 @@ func (s *agent) handleCommand(ctx context.Context, input string) bool {
|
|||
s.emit(infoEvent("nothing to compact"))
|
||||
return
|
||||
}
|
||||
if s.sessionsDir != "" && s.sessionID != "" {
|
||||
histPath := s.activeSessionPath(s.sessionID, ".compaction.jsonl")
|
||||
if s.sessionsDir != "" && s.sess.ID() != "" {
|
||||
histPath := s.activeSessionPath(s.sess.ID(), ".compaction.jsonl")
|
||||
if err := os.MkdirAll(filepath.Dir(histPath), 0700); err != nil {
|
||||
s.emit(infoEvent("compaction history save: " + err.Error()))
|
||||
} else if data, err := json.Marshal(snapshot); err == nil {
|
||||
|
|
@ -351,7 +351,7 @@ func (s *agent) handleCommand(ctx context.Context, input string) bool {
|
|||
continue
|
||||
}
|
||||
marker := " "
|
||||
if file.id == s.sessionID {
|
||||
if file.id == s.sess.ID() {
|
||||
marker = "* "
|
||||
}
|
||||
goal := ""
|
||||
|
|
|
|||
|
|
@ -318,14 +318,11 @@ type agent struct {
|
|||
agentName string
|
||||
agentsDir string
|
||||
sessionsDir string
|
||||
sessionID string
|
||||
uname string
|
||||
newDispatcher func() tools.Dispatcher
|
||||
newBackend func(string) (backend.Backend, error)
|
||||
readPlanStep func() string
|
||||
listHandlers map[string]func() []string
|
||||
turnError func(ctx context.Context, errType, errMsg string) HookResult // overridable for tests
|
||||
cwd string
|
||||
remote string // SSH target for remote execution
|
||||
promptEnvExtra []string // PRIME_* vars for prompt resolution
|
||||
baseLayers []string // system prompt, operational model, env block — for /agent reloads
|
||||
|
|
@ -334,8 +331,6 @@ type agent struct {
|
|||
toolCallCount atomic.Int64
|
||||
pendingInject atomic.Pointer[string]
|
||||
mu sync.RWMutex
|
||||
envMu sync.RWMutex
|
||||
env map[string]string // session-scoped env vars
|
||||
changeMu sync.Mutex
|
||||
changeCond *sync.Cond
|
||||
submitMu sync.Mutex // serializes Submit calls (commands + turns)
|
||||
|
|
@ -360,12 +355,7 @@ func (s *agent) ToolCallCount() int64 {
|
|||
|
||||
// SetEnv stores a session-scoped variable and propagates it to the execute server.
|
||||
func (s *agent) SetEnv(key, value string) {
|
||||
s.envMu.Lock()
|
||||
if s.env == nil {
|
||||
s.env = make(map[string]string)
|
||||
}
|
||||
s.env[key] = value
|
||||
s.envMu.Unlock()
|
||||
s.sess.SetEnv(key, value)
|
||||
if s.runtime == nil || s.runtime.Dispatcher == nil {
|
||||
return
|
||||
}
|
||||
|
|
@ -378,14 +368,14 @@ func (s *agent) SetEnv(key, value string) {
|
|||
|
||||
// pushSessionEnv injects OLLIE_SESSION_ID into the execute server subprocess env.
|
||||
func (s *agent) pushSessionEnv() {
|
||||
if s.runtime == nil || s.runtime.Dispatcher == nil || s.sessionID == "" {
|
||||
if s.runtime == nil || s.runtime.Dispatcher == nil || s.sess.ID() == "" {
|
||||
return
|
||||
}
|
||||
if srv, ok := s.runtime.Dispatcher.GetServer("execute"); ok {
|
||||
if es, ok := srv.(tools.EnvSetter); ok {
|
||||
es.SetEnv("OLLIE_SESSION_ID", s.sessionID)
|
||||
if s.uname != "" {
|
||||
es.SetEnv("OLLIE_UNAME", s.uname)
|
||||
es.SetEnv("OLLIE_SESSION_ID", s.sess.ID())
|
||||
if s.sess.Uname() != "" {
|
||||
es.SetEnv("OLLIE_UNAME", s.sess.Uname())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -393,7 +383,7 @@ func (s *agent) pushSessionEnv() {
|
|||
|
||||
// pushLockDir sets the flock directory on the execute server to the session tmpdir.
|
||||
func (s *agent) pushLockDir() {
|
||||
if s.runtime == nil || s.runtime.Dispatcher == nil || s.sessionID == "" {
|
||||
if s.runtime == nil || s.runtime.Dispatcher == nil || s.sess.ID() == "" {
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -488,9 +478,6 @@ func NewAgentCore(cfg AgentCoreConfig) Core {
|
|||
agentName: cfg.AgentName,
|
||||
agentsDir: cfg.AgentsDir,
|
||||
sessionsDir: cfg.SessionsDir,
|
||||
sessionID: cfg.SessionID,
|
||||
uname: cfg.Uname,
|
||||
cwd: paths.ExpandHome(cfg.CWD),
|
||||
remote: cfg.Remote,
|
||||
promptEnvExtra: cfg.PromptEnvExtra,
|
||||
baseLayers: cfg.BaseLayers,
|
||||
|
|
@ -505,7 +492,7 @@ func NewAgentCore(cfg AgentCoreConfig) Core {
|
|||
hookCtx, cancel := context.WithTimeout(context.Background(), time.Duration(hookTimeout)*time.Second)
|
||||
defer cancel()
|
||||
return a.runtime.Hooks.Run(hookCtx, HookTurnError, map[string]string{
|
||||
"session_id": a.sessionID,
|
||||
"session_id": a.sess.ID(),
|
||||
"cwd": a.CWD(),
|
||||
"model": a.runtime.Backend.Model(),
|
||||
"error_type": errType,
|
||||
|
|
@ -519,7 +506,7 @@ func NewAgentCore(cfg AgentCoreConfig) Core {
|
|||
|
||||
// Close releases resources for this session, including its tmpdir.
|
||||
func (s *agent) Close() {
|
||||
s.log.Debug("Close() session=%q", s.sessionID)
|
||||
s.log.Debug("Close() session=%q", s.sess.ID())
|
||||
s.flushSave()
|
||||
if s.runtime != nil && s.runtime.Dispatcher != nil {
|
||||
if srv, ok := s.runtime.Dispatcher.GetServer("execute"); ok {
|
||||
|
|
@ -529,8 +516,8 @@ func (s *agent) Close() {
|
|||
}
|
||||
}
|
||||
}
|
||||
if s.sessionID != "" {
|
||||
os.RemoveAll(filepath.Join(ollieTmpDir(), s.sessionID)) //nolint:errcheck
|
||||
if s.sess.ID() != "" {
|
||||
os.RemoveAll(filepath.Join(ollieTmpDir(), s.sess.ID())) //nolint:errcheck
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -790,9 +777,9 @@ func (s *agent) Reply() string {
|
|||
|
||||
// CWD returns the current working directory for tool execution.
|
||||
func (s *agent) CWD() string {
|
||||
if s.cwd != "" {
|
||||
s.log.Debug("CWD() = %q", s.cwd)
|
||||
return s.cwd
|
||||
if s.sess.CWD() != "" {
|
||||
s.log.Debug("CWD() = %q", s.sess.CWD())
|
||||
return s.sess.CWD()
|
||||
}
|
||||
wd, _ := os.Getwd()
|
||||
s.log.Debug("CWD() = %q (from getwd)", wd)
|
||||
|
|
@ -809,8 +796,8 @@ func (s *agent) SetCWD(dir string) error {
|
|||
return fmt.Errorf("cwd: %w", err)
|
||||
}
|
||||
}
|
||||
oldCwd := s.cwd
|
||||
s.cwd = dir
|
||||
oldCwd := s.sess.CWD()
|
||||
s.sess.SetCWD(dir)
|
||||
// Update cwd references in the system prompt.
|
||||
if oldCwd != "" && dir != "" && oldCwd != dir {
|
||||
s.runtime.Preamble = strings.ReplaceAll(s.runtime.Preamble, oldCwd, dir)
|
||||
|
|
@ -830,8 +817,8 @@ func (s *agent) SetCWD(dir string) error {
|
|||
// SetSessionID renames the session. It updates the in-memory ID, renames
|
||||
// persisted files on disk, and propagates to the execute server env.
|
||||
func (s *agent) SetSessionID(newID string) error {
|
||||
s.log.Debug("SetSessionID(%q) old=%q", newID, s.sessionID)
|
||||
oldID := s.sessionID
|
||||
s.log.Debug("SetSessionID(%q) old=%q", newID, s.sess.ID())
|
||||
oldID := s.sess.ID()
|
||||
if oldID == newID {
|
||||
return nil
|
||||
}
|
||||
|
|
@ -846,7 +833,7 @@ func (s *agent) SetSessionID(newID string) error {
|
|||
}
|
||||
}
|
||||
}
|
||||
s.sessionID = newID
|
||||
s.sess.SetID(newID)
|
||||
// Update session ID references in the system prompt.
|
||||
s.runtime.Preamble = strings.ReplaceAll(s.runtime.Preamble, oldID, newID)
|
||||
// Rename tmpdir so isread markers remain valid after rename.
|
||||
|
|
@ -888,7 +875,7 @@ func (s *agent) autoWarnLimit(ctx context.Context) int {
|
|||
// agent-specific prompt with any agentSpawn hook output.
|
||||
func (s *agent) spawnContext(ctx context.Context) string {
|
||||
result := s.runtime.Hooks.Run(ctx, HookAgentSpawn, map[string]string{
|
||||
"session_id": s.sessionID,
|
||||
"session_id": s.sess.ID(),
|
||||
"agent": s.agentName,
|
||||
"cwd": s.CWD(),
|
||||
"model": s.runtime.Backend.Model(),
|
||||
|
|
@ -910,7 +897,7 @@ func (s *agent) spawnContext(ctx context.Context) string {
|
|||
// re-injection, post-hook. Returns (n compacted, error). Returns (0, nil) if
|
||||
// the pre-hook blocked or there was nothing to compact. Caller manages setState.
|
||||
func (s *agent) runCompact(ctx context.Context, trigger string) (int, error) {
|
||||
payload := map[string]string{"session_id": s.sessionID, "trigger": trigger, "cwd": s.CWD()}
|
||||
payload := map[string]string{"session_id": s.sess.ID(), "trigger": trigger, "cwd": s.CWD()}
|
||||
pre := s.runtime.Hooks.Run(ctx, HookPreCompact, payload, s.log)
|
||||
if pre.Warning != "" {
|
||||
s.emit(infoEvent(pre.Warning))
|
||||
|
|
@ -937,7 +924,7 @@ func (s *agent) runCompact(ctx context.Context, trigger string) (int, error) {
|
|||
return 0, err
|
||||
}
|
||||
if n > 0 {
|
||||
s.auditLog.Debug("compact: removed %d messages trigger=%s session=%s", n, trigger, s.sessionID)
|
||||
s.auditLog.Debug("compact: removed %d messages trigger=%s session=%s", n, trigger, s.sess.ID())
|
||||
s.warnedContext = false
|
||||
if sc := s.spawnContext(ctx); sc != "" {
|
||||
s.history.appendUserMessage(sc)
|
||||
|
|
@ -982,15 +969,15 @@ func (s *agent) flushSave() {
|
|||
if !dirty {
|
||||
return
|
||||
}
|
||||
if s.history == nil || s.sessionID == "" || s.sessionsDir == "" {
|
||||
if s.history == nil || s.sess.ID() == "" || s.sessionsDir == "" {
|
||||
return
|
||||
}
|
||||
path := s.activeSessionPath(s.sessionID, ".json")
|
||||
path := s.activeSessionPath(s.sess.ID(), ".json")
|
||||
if err := os.MkdirAll(filepath.Dir(path), 0700); err != nil {
|
||||
s.log.Error("session save: %v", err)
|
||||
return
|
||||
}
|
||||
if err := s.history.saveToFull(path, s.sessionID, s.agentName,
|
||||
if err := s.history.saveToFull(path, s.sess.ID(), s.agentName,
|
||||
s.runtime.Backend.Name(), s.runtime.Backend.Model(), s.CWD(), s.remote); err != nil {
|
||||
s.log.Error("session save: %v", err)
|
||||
}
|
||||
|
|
@ -1004,7 +991,7 @@ func (s *agent) SaveSession(path string) error {
|
|||
if s.history == nil {
|
||||
return fmt.Errorf("no active session")
|
||||
}
|
||||
return s.history.saveToFull(path, s.sessionID, s.agentName,
|
||||
return s.history.saveToFull(path, s.sess.ID(), s.agentName,
|
||||
s.runtime.Backend.Name(), s.runtime.Backend.Model(), s.CWD(), s.remote)
|
||||
}
|
||||
|
||||
|
|
@ -1240,7 +1227,7 @@ func (s *agent) executeTurn(ctx context.Context, input string) string {
|
|||
s.emit(Event{Role: "user", Content: input})
|
||||
|
||||
hookResult := s.runtime.Hooks.Run(ctx, HookPreTurn, map[string]string{
|
||||
"session_id": s.sessionID,
|
||||
"session_id": s.sess.ID(),
|
||||
"cwd": s.CWD(),
|
||||
"prompt": input,
|
||||
}, s.log)
|
||||
|
|
@ -1286,7 +1273,7 @@ func (s *agent) executeTurn(ctx context.Context, input string) string {
|
|||
s.currentAction.Store(handle)
|
||||
s.setState("thinking")
|
||||
|
||||
s.auditLog.Debug("turn: start input=%s session=%s", auditTruncate(input), s.sessionID)
|
||||
s.auditLog.Debug("turn: start input=%s session=%s", auditTruncate(input), s.sess.ID())
|
||||
|
||||
// Build per-turn agentConfig from the current runtime.
|
||||
s.cfg = agentConfig{
|
||||
|
|
@ -1348,7 +1335,7 @@ func (s *agent) executeTurn(ctx context.Context, input string) string {
|
|||
}
|
||||
s.cfg.PreTool = func(ctx context.Context, name string, args json.RawMessage) HookResult {
|
||||
return s.runtime.Hooks.Run(ctx, HookPreTool, map[string]string{
|
||||
"session_id": s.sessionID,
|
||||
"session_id": s.sess.ID(),
|
||||
"cwd": s.CWD(),
|
||||
"tool": name,
|
||||
"args": string(args),
|
||||
|
|
@ -1356,7 +1343,7 @@ func (s *agent) executeTurn(ctx context.Context, input string) string {
|
|||
}
|
||||
s.cfg.PostTool = func(ctx context.Context, name string, args json.RawMessage, result string) HookResult {
|
||||
return s.runtime.Hooks.Run(ctx, HookPostTool, map[string]string{
|
||||
"session_id": s.sessionID,
|
||||
"session_id": s.sess.ID(),
|
||||
"cwd": s.CWD(),
|
||||
"tool": name,
|
||||
"args": string(args),
|
||||
|
|
@ -1478,7 +1465,7 @@ func (s *agent) executeTurn(ctx context.Context, input string) string {
|
|||
s.history.removeCancelledToolResults()
|
||||
}
|
||||
if errors.Is(err, context.Canceled) || errors.Is(err, ErrInterrupted) {
|
||||
s.auditLog.Debug("turn: interrupted session=%s", s.sessionID)
|
||||
s.auditLog.Debug("turn: interrupted session=%s", s.sess.ID())
|
||||
s.saveSession()
|
||||
return ""
|
||||
}
|
||||
|
|
@ -1491,7 +1478,7 @@ func (s *agent) executeTurn(ctx context.Context, input string) string {
|
|||
}
|
||||
|
||||
stopResult := s.runtime.Hooks.Run(ctx, HookPostTurn, map[string]string{
|
||||
"session_id": s.sessionID,
|
||||
"session_id": s.sess.ID(),
|
||||
"cwd": s.CWD(),
|
||||
}, s.log)
|
||||
if stopResult.Warning != "" {
|
||||
|
|
@ -1506,12 +1493,12 @@ func (s *agent) executeTurn(ctx context.Context, input string) string {
|
|||
|
||||
if s.history != nil {
|
||||
s.history.recordTurnCost(s.cfg.Backend.Model())
|
||||
appendUsageLog(s.sessionID, s.cfg.Backend.Name(), s.cfg.Backend.Model(), s.history)
|
||||
appendUsageLog(s.sess.ID(), s.cfg.Backend.Name(), s.cfg.Backend.Model(), s.history)
|
||||
if s.history.LastTurnCostUSD > 0 {
|
||||
s.emit(Event{Role: "info", Content: fmt.Sprintf("costLast=$%.4f\n", s.history.LastTurnCostUSD)})
|
||||
}
|
||||
s.auditLog.Debug("turn: end reply=%s cost=$%.4f session_total=$%.4f session=%s",
|
||||
auditTruncate(s.sess.Reply()), s.history.LastTurnCostUSD, s.history.SessionCostUSD, s.sessionID)
|
||||
auditTruncate(s.sess.Reply()), s.history.LastTurnCostUSD, s.history.SessionCostUSD, s.sess.ID())
|
||||
s.notifyChange()
|
||||
}
|
||||
s.saveSession()
|
||||
|
|
|
|||
|
|
@ -603,13 +603,13 @@ func TestCommand_Clear(t *testing.T) {
|
|||
if c.history == nil {
|
||||
t.Fatal("session nil after first turn")
|
||||
}
|
||||
oldID := c.sessionID
|
||||
oldID := c.sess.ID()
|
||||
collectEvents(context.Background(), c, "/clear")
|
||||
if c.history != nil {
|
||||
t.Error("session not nil after /clear")
|
||||
}
|
||||
if c.sessionID != oldID {
|
||||
t.Errorf("sessionID changed after /clear: %q → %q; want unchanged", oldID, c.sessionID)
|
||||
if c.sess.ID() != oldID {
|
||||
t.Errorf("sessionID changed after /clear: %q → %q; want unchanged", oldID, c.sess.ID())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1758,13 +1758,13 @@ func TestCore_Usage_WithSession(t *testing.T) {
|
|||
func TestSetSessionID_Rename(t *testing.T) {
|
||||
c := newCore(t, nil, nil)
|
||||
collectEvents(context.Background(), c, "hello") // saves session file
|
||||
oldID := c.sessionID
|
||||
oldID := c.sess.ID()
|
||||
newID := NewSessionID()
|
||||
if err := c.SetSessionID(newID); err != nil {
|
||||
t.Fatalf("SetSessionID: %v", err)
|
||||
}
|
||||
if c.sessionID != newID {
|
||||
t.Errorf("sessionID = %q; want %q", c.sessionID, newID)
|
||||
if c.sess.ID() != newID {
|
||||
t.Errorf("sessionID = %q; want %q", c.sess.ID(), newID)
|
||||
}
|
||||
if _, err := os.Stat(filepath.Join(c.sessionsDir, "active", oldID+".json")); !os.IsNotExist(err) {
|
||||
t.Errorf("old active session file still exists after rename; err=%v", err)
|
||||
|
|
@ -1803,12 +1803,12 @@ func TestSetSessionID_UpdatesPreamble(t *testing.T) {
|
|||
|
||||
func TestSetSessionID_SameID(t *testing.T) {
|
||||
c := newCore(t, nil, nil)
|
||||
id := c.sessionID
|
||||
id := c.sess.ID()
|
||||
if err := c.SetSessionID(id); err != nil {
|
||||
t.Fatalf("SetSessionID with same ID: %v", err)
|
||||
}
|
||||
if c.sessionID != id {
|
||||
t.Errorf("sessionID changed: got %q; want %q", c.sessionID, id)
|
||||
if c.sess.ID() != id {
|
||||
t.Errorf("sessionID changed: got %q; want %q", c.sess.ID(), id)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2312,13 +2312,12 @@ func TestSetEnv_StoredInCore(t *testing.T) {
|
|||
c.SetEnv("FOO", "bar")
|
||||
c.SetEnv("BAZ", "qux")
|
||||
|
||||
c.envMu.RLock()
|
||||
defer c.envMu.RUnlock()
|
||||
if c.env["FOO"] != "bar" {
|
||||
t.Errorf("env[FOO] = %q; want bar", c.env["FOO"])
|
||||
env := c.sess.Env()
|
||||
if env["FOO"] != "bar" {
|
||||
t.Errorf("env[FOO] = %q; want bar", env["FOO"])
|
||||
}
|
||||
if c.env["BAZ"] != "qux" {
|
||||
t.Errorf("env[BAZ] = %q; want qux", c.env["BAZ"])
|
||||
if env["BAZ"] != "qux" {
|
||||
t.Errorf("env[BAZ] = %q; want qux", env["BAZ"])
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2327,8 +2326,9 @@ func TestSetEnv_NilDispatcher_NoPanic(t *testing.T) {
|
|||
c.runtime.Dispatcher = nil
|
||||
// Must not panic.
|
||||
c.SetEnv("K", "V")
|
||||
if c.env["K"] != "V" {
|
||||
t.Errorf("env[K] = %q; want V", c.env["K"])
|
||||
env := c.sess.Env()
|
||||
if env["K"] != "V" {
|
||||
t.Errorf("env[K] = %q; want V", env["K"])
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Reference in New Issue