diff --git a/pkg/agent/commands.go b/pkg/agent/commands.go index bdc0f86..f4a660b 100644 --- a/pkg/agent/commands.go +++ b/pkg/agent/commands.go @@ -5,6 +5,7 @@ import ( "encoding/json" "fmt" "os" + "path/filepath" "slices" "strconv" "strings" @@ -226,8 +227,10 @@ func (s *agent) handleCommand(ctx context.Context, input string) bool { return } if s.sessionsDir != "" && s.sessionID != "" { - histPath := s.sessionsDir + "/" + s.sessionID + ".compaction.jsonl" - if data, err := json.Marshal(snapshot); err == nil { + histPath := s.activeSessionPath(s.sessionID, ".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 { f, err := os.OpenFile(histPath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600) if err == nil { f.Write(append(data, '\n')) //nolint:errcheck @@ -309,20 +312,34 @@ func (s *agent) handleCommand(ctx context.Context, input string) bool { "/sessions": func(args []string) { allFlag := len(args) > 0 && args[0] == "-a" - entries, err := os.ReadDir(s.sessionsDir) - if err != nil { - s.emit(infoEvent(fmt.Sprintf("sessions: %v", err))) - return + type sessionFile struct { + id string + path string } + var files []sessionFile + appendSessionFiles := func(dir string) { + entries, err := os.ReadDir(dir) + if err != nil { + return + } + for i := len(entries) - 1; i >= 0; i-- { + e := entries[i] + if e.IsDir() || !strings.HasSuffix(e.Name(), ".json") { + continue + } + files = append(files, sessionFile{ + id: strings.TrimSuffix(e.Name(), ".json"), + path: filepath.Join(dir, e.Name()), + }) + } + } + appendSessionFiles(s.sessionsDir) + appendSessionFiles(filepath.Join(s.sessionsDir, "active")) + cwd := s.CWD() found := false - for i := len(entries) - 1; i >= 0; i-- { - e := entries[i] - if e.IsDir() || !strings.HasSuffix(e.Name(), ".json") { - continue - } - id := strings.TrimSuffix(e.Name(), ".json") - data, readErr := os.ReadFile(s.sessionsDir + "/" + e.Name()) + for _, file := range files { + data, readErr := os.ReadFile(file.path) if readErr != nil { continue } @@ -334,7 +351,7 @@ func (s *agent) handleCommand(ctx context.Context, input string) bool { continue } marker := " " - if id == s.sessionID { + if file.id == s.sessionID { marker = "* " } goal := "" @@ -347,7 +364,7 @@ func (s *agent) handleCommand(ctx context.Context, input string) bool { if len(goal) > 60 { goal = goal[:60] + "..." } - s.emit(infoEvent(marker + fmt.Sprintf("%-24s [%s] %q", id, ps.Agent, goal))) + s.emit(infoEvent(marker + fmt.Sprintf("%-24s [%s] %q", file.id, ps.Agent, goal))) found = true } if !found { diff --git a/pkg/agent/core.go b/pkg/agent/core.go index 06f3608..c24738d 100644 --- a/pkg/agent/core.go +++ b/pkg/agent/core.go @@ -31,8 +31,6 @@ import ( // with other read-class tools. nil means treat all tools as serial. type toolClassifier func(name string) bool - - // BuildRuntime constructs a Runtime from a pre-configured Dispatcher and // optional agent config. cwd sets the working directory reported in the // system prompt; if empty, the process working directory is used. @@ -105,21 +103,21 @@ func BuildRuntime(cfg *config.Config, d tools.Dispatcher, cwd string, env []stri } } - exec := func(ctx context.Context, name string, args json.RawMessage) (string, []backend.ContentBlock, error) { - server, ok := serverOf[name] - if !ok { - return "", nil, fmt.Errorf("unknown tool: %s", name) - } - raw, err := d.Dispatch(ctx, server, name, args) - if err != nil { - return "", nil, err - } - text, blocks, isErr := extractToolResult(raw) - if isErr { - return "", nil, fmt.Errorf("%s", text) - } - return text, blocks, nil + exec := func(ctx context.Context, name string, args json.RawMessage) (string, []backend.ContentBlock, error) { + server, ok := serverOf[name] + if !ok { + return "", nil, fmt.Errorf("unknown tool: %s", name) } + raw, err := d.Dispatch(ctx, server, name, args) + if err != nil { + return "", nil, err + } + text, blocks, isErr := extractToolResult(raw) + if isErr { + return "", nil, fmt.Errorf("%s", text) + } + return text, blocks, nil + } var classify toolClassifier if srv, ok := d.GetServer("execute"); ok { @@ -196,7 +194,6 @@ func BuildRuntime(cfg *config.Config, d tools.Dispatcher, cwd string, env []stri } } - // DefaultPromptsDir returns the default directory for prompt templates. func DefaultPromptsDir() string { return paths.CfgDir() + "/prompts" @@ -218,7 +215,6 @@ func AgentsDirs() []string { return []string{paths.CfgDir() + "/agents"} } - // AgentConfigPath resolves the config file path for a named agent. // It searches all directories in OLLIE_AGENTS_PATH, falling back to agentsDir. func AgentConfigPath(agentsDir, name string) string { @@ -240,13 +236,11 @@ func NewSessionID() string { return strconv.FormatInt(time.Now().UnixNano(), 10) + "-" + fmt.Sprintf("%06x", b) } - // infoEvent wraps a plain-text message as an info Event. func infoEvent(text string) Event { return Event{Role: "info", Content: text + "\n"} } - // actionHandle holds the cancel function for the current agent turn. type actionHandle struct { cancel context.CancelCauseFunc @@ -266,13 +260,13 @@ type AgentCoreConfig struct { Runtime *Runtime NewDispatcher func() tools.Dispatcher NewBackend func(string) (backend.Backend, error) // if nil, defaults to backend.NewWithName - Log *olog.Logger // if nil, logging is disabled + Log *olog.Logger // if nil, logging is disabled // MaxSteps overrides the agent JSON maxSteps when non-zero. // 0 means use the value from the agent config (or unlimited if absent). - MaxSteps int + MaxSteps int // ReadPlanStep, if non-nil, is called to get the next unchecked plan step. // When provided, it is used directly instead of reading from the filesystem. - ReadPlanStep func() string + ReadPlanStep func() string // ListHandlers provides harness-specific list implementations for slash // commands such as /skills, /tools, /agents. Keyed by command name without // the leading slash. Commands with no entry emit nothing. @@ -294,7 +288,7 @@ type AgentCoreConfig struct { type agent struct { session *Session runtime *Runtime - cfg agentConfig // per-turn config built from runtime; set in executeTurn + cfg agentConfig // per-turn config built from runtime; set in executeTurn log *olog.Logger agentName string agentsDir string @@ -310,7 +304,7 @@ type agent struct { 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 - startupMessages []string + startupMessages []string currentAction atomic.Pointer[actionHandle] toolCallCount atomic.Int64 fifo PromptFIFO @@ -328,9 +322,9 @@ type agent struct { auditLog *olog.Logger // Debounced session persistence - saveMu sync.Mutex - saveDirty bool - saveTimer *time.Timer + saveMu sync.Mutex + saveDirty bool + saveTimer *time.Timer // Cross-turn tool result cache (read-safe tools only) resultCache sync.Map @@ -549,11 +543,21 @@ func (s *agent) ListDetached() []DetachedInfo { for _, r := range raw { if m, ok := r.(map[string]any); ok { di := DetachedInfo{} - if v, ok := m["pid"].(int); ok { di.PID = v } - if v, ok := m["command"].(string); ok { di.Command = v } - if v, ok := m["started"].(int64); ok { di.Started = v } - if v, ok := m["exited"].(bool); ok { di.Exited = v } - if v, ok := m["exit_code"].(int); ok { di.ExitCode = v } + if v, ok := m["pid"].(int); ok { + di.PID = v + } + if v, ok := m["command"].(string); ok { + di.Command = v + } + if v, ok := m["started"].(int64); ok { + di.Started = v + } + if v, ok := m["exited"].(bool); ok { + di.Exited = v + } + if v, ok := m["exit_code"].(int); ok { + di.ExitCode = v + } out = append(out, di) } } @@ -730,12 +734,12 @@ func (s *agent) SetSessionID(newID string) error { if oldID == newID { return nil } - // Rename persisted files on disk. + // Rename active persisted files on disk. if s.sessionsDir != "" && oldID != "" { for _, suffix := range []string{".json", ".compaction.jsonl"} { - oldPath := s.sessionsDir + "/" + oldID + suffix + oldPath := s.activeSessionPath(oldID, suffix) if _, err := os.Stat(oldPath); err == nil { - if err := os.Rename(oldPath, s.sessionsDir+"/"+newID+suffix); err != nil { + if err := os.Rename(oldPath, s.activeSessionPath(newID, suffix)); err != nil { return fmt.Errorf("rename %s: %w", suffix, err) } } @@ -851,6 +855,10 @@ func (s *agent) runCompact(ctx context.Context, trigger string) (int, error) { return n, nil } +func (s *agent) activeSessionPath(id, suffix string) string { + return filepath.Join(s.sessionsDir, "active", id+suffix) +} + func (s *agent) saveSession() { s.saveMu.Lock() s.saveDirty = true @@ -876,7 +884,11 @@ func (s *agent) flushSave() { if s.session == nil || s.sessionID == "" || s.sessionsDir == "" { return } - path := s.sessionsDir + "/" + s.sessionID + ".json" + path := s.activeSessionPath(s.sessionID, ".json") + if err := os.MkdirAll(filepath.Dir(path), 0700); err != nil { + s.log.Error("session save: %v", err) + return + } if err := s.session.saveTo(path, s.sessionID, s.agentName, s.CWD()); err != nil { s.log.Error("session save: %v", err) } @@ -1192,7 +1204,7 @@ func (s *agent) executeTurn(ctx context.Context, input string) string { GenerationParams: s.runtime.GenParams, MaxSteps: s.runtime.MaxSteps, ReadPlanStep: s.readPlanStep, - TurnError: s.turnError, + TurnError: s.turnError, } var replyBuf strings.Builder @@ -1427,7 +1439,6 @@ func (s *agent) executeTurn(ctx context.Context, input string) string { return "" } - func toolInfosToBackend(infos []tools.ToolInfo) []backend.Tool { out := make([]backend.Tool, len(infos)) for i, t := range infos { @@ -1444,10 +1455,10 @@ func extractToolResult(raw json.RawMessage) (text string, contentBlocks []backen var result struct { IsError bool `json:"isError"` Content []struct { - Type string `json:"type"` - Text string `json:"text"` - MediaType string `json:"media_type"` - Data string `json:"data"` + Type string `json:"type"` + Text string `json:"text"` + MediaType string `json:"media_type"` + Data string `json:"data"` } `json:"content"` } if err := json.Unmarshal(raw, &result); err != nil { diff --git a/pkg/agent/core_test.go b/pkg/agent/core_test.go index f185a87..991a29a 100644 --- a/pkg/agent/core_test.go +++ b/pkg/agent/core_test.go @@ -94,7 +94,7 @@ func newCore(t *testing.T, be backend.Backend, hooks Hooks) *agent { hooks = Hooks{} } env := &Runtime{ - Hooks: hooks, + Hooks: hooks, Preamble: "test system prompt", } c := NewAgentCore(AgentCoreConfig{ @@ -184,7 +184,7 @@ func TestSubmit_StateTransitions(t *testing.T) { unblock := make(chan struct{}) be := defaultBE() be.respond = func(ctx context.Context, _ []backend.Message, _ []backend.Tool, _ backend.GenerationParams) (<-chan backend.StreamEvent, error) { -return blockedStream(ctx, unblock), nil + return blockedStream(ctx, unblock), nil } c := newCore(t, be, nil) @@ -222,10 +222,10 @@ func TestSubmit_ToolCallStateTransitions(t *testing.T) { StopReason: "tool_calls", } close(ch) -return ch, nil + return ch, nil } -return textStream("done"), nil + return textStream("done"), nil } @@ -234,7 +234,7 @@ return textStream("done"), nil var stateAtExec string c.runtime.Exec = func(_ context.Context, _ string, _ json.RawMessage) (string, []backend.ContentBlock, error) { stateAtExec = c.State() -return `{}`, nil, nil + return `{}`, nil, nil } collectEvents(context.Background(), c, "run tool") @@ -255,7 +255,7 @@ func TestSubmit_PreTurnHookBlocks(t *testing.T) { be := defaultBE() be.respond = func(_ context.Context, _ []backend.Message, _ []backend.Tool, _ backend.GenerationParams) (<-chan backend.StreamEvent, error) { callCount++ -return textStream("should not be called"), nil + return textStream("should not be called"), nil } c := newCore(t, be, Hooks{HookPreTurn: []string{"exit 2"}}) @@ -285,7 +285,7 @@ func TestSubmit_PreTurnHookContext(t *testing.T) { lastUserMsg = m.Content } } - return textStream("ok"), nil + return textStream("ok"), nil } c := newCore(t, be, Hooks{HookPreTurn: []string{`echo "extra context"`}}) @@ -308,7 +308,7 @@ func TestSubmit_PostTurnHookContinue(t *testing.T) { be := defaultBE() be.respond = func(_ context.Context, _ []backend.Message, _ []backend.Tool, _ backend.GenerationParams) (<-chan backend.StreamEvent, error) { callCount++ -return textStream(fmt.Sprintf("response %d", callCount)), nil + return textStream(fmt.Sprintf("response %d", callCount)), nil } c := newCore(t, be, Hooks{HookPostTurn: []string{hookScript}}) @@ -327,7 +327,7 @@ func TestSubmit_FIFODrain(t *testing.T) { be := defaultBE() be.respond = func(_ context.Context, _ []backend.Message, _ []backend.Tool, _ backend.GenerationParams) (<-chan backend.StreamEvent, error) { callCount.Add(1) - return textStream("ok"), nil + return textStream("ok"), nil } c := newCore(t, be, nil) @@ -355,7 +355,7 @@ func TestSubmit_PendingInjectAsNextTurn(t *testing.T) { be := defaultBE() be.respond = func(_ context.Context, _ []backend.Message, _ []backend.Tool, _ backend.GenerationParams) (<-chan backend.StreamEvent, error) { callCount++ - return textStream("ok"), nil + return textStream("ok"), nil } c := newCore(t, be, nil) @@ -375,7 +375,7 @@ func TestInterrupt_Running(t *testing.T) { unblock := make(chan struct{}) be := defaultBE() be.respond = func(ctx context.Context, _ []backend.Message, _ []backend.Tool, _ backend.GenerationParams) (<-chan backend.StreamEvent, error) { -return blockedStream(ctx, unblock), nil + return blockedStream(ctx, unblock), nil } c := newCore(t, be, nil) @@ -418,7 +418,7 @@ func TestSubmit_WhileRunning(t *testing.T) { unblock := make(chan struct{}) be := defaultBE() be.respond = func(ctx context.Context, _ []backend.Message, _ []backend.Tool, _ backend.GenerationParams) (<-chan backend.StreamEvent, error) { -return blockedStream(ctx, unblock), nil + return blockedStream(ctx, unblock), nil } c := newCore(t, be, nil) @@ -456,7 +456,7 @@ func TestManualCompact(t *testing.T) { be.respond = func(_ context.Context, _ []backend.Message, _ []backend.Tool, _ backend.GenerationParams) (<-chan backend.StreamEvent, error) { callCount++ stateAtCompact = c.State() -return textStream("summary or answer"), nil + return textStream("summary or answer"), nil } @@ -508,10 +508,10 @@ func TestAutoCompact(t *testing.T) { callCount++ if callCount == 1 { stateAtCompact = c.State() -return textStream("summary text for compaction"), nil + return textStream("summary text for compaction"), nil } -return textStream("answer"), nil + return textStream("answer"), nil } @@ -674,7 +674,7 @@ func TestCommand_I_SetsInject(t *testing.T) { done := make(chan struct{}) be.respond = func(_ context.Context, msgs []backend.Message, _ []backend.Tool, _ backend.GenerationParams) (<-chan backend.StreamEvent, error) { <-done - return textStream("ok"), nil + return textStream("ok"), nil } c := newCore(t, be, nil) submitDone := make(chan struct{}) @@ -698,7 +698,7 @@ func TestCommand_I_SubmitsWhenIdle(t *testing.T) { be := defaultBE() be.respond = func(_ context.Context, _ []backend.Message, _ []backend.Tool, _ backend.GenerationParams) (<-chan backend.StreamEvent, error) { callCount.Add(1) - return textStream("ok"), nil + return textStream("ok"), nil } c := newCore(t, be, nil) c.Submit(context.Background(), "/i my prompt") @@ -742,7 +742,7 @@ func TestCommand_Compact_WhileRunning(t *testing.T) { unblock := make(chan struct{}) be := defaultBE() be.respond = func(ctx context.Context, _ []backend.Message, _ []backend.Tool, _ backend.GenerationParams) (<-chan backend.StreamEvent, error) { -return blockedStream(ctx, unblock), nil + return blockedStream(ctx, unblock), nil } c := newCore(t, be, nil) @@ -811,7 +811,7 @@ func TestCommand_Clear_WhileRunning(t *testing.T) { unblock := make(chan struct{}) be := defaultBE() be.respond = func(ctx context.Context, _ []backend.Message, _ []backend.Tool, _ backend.GenerationParams) (<-chan backend.StreamEvent, error) { -return blockedStream(ctx, unblock), nil + return blockedStream(ctx, unblock), nil } c := newCore(t, be, nil) @@ -893,7 +893,7 @@ func TestRun_StreamInterrupted(t *testing.T) { be.respond = func(_ context.Context, _ []backend.Message, _ []backend.Tool, _ backend.GenerationParams) (<-chan backend.StreamEvent, error) { ch := make(chan backend.StreamEvent) close(ch) // close without sending Done=true -return ch, nil + return ch, nil } c := newCore(t, be, nil) @@ -917,7 +917,7 @@ func TestRun_UnknownStopReason(t *testing.T) { ch := make(chan backend.StreamEvent, 1) ch <- backend.StreamEvent{Done: true, StopReason: "max_completion_tokens", Content: "partial"} close(ch) -return ch, nil + return ch, nil } c := newCore(t, be, nil) @@ -948,10 +948,10 @@ func TestRun_ToolEmptyName(t *testing.T) { StopReason: "tool_calls", } close(ch) -return ch, nil + return ch, nil } -return textStream("done"), nil + return textStream("done"), nil } c := newCore(t, be, nil) @@ -979,10 +979,10 @@ func TestRun_NoExec(t *testing.T) { StopReason: "tool_calls", } close(ch) -return ch, nil + return ch, nil } -return textStream("done"), nil + return textStream("done"), nil } c := newCore(t, be, nil) @@ -1000,7 +1000,7 @@ func TestHook_NonZeroExitCode(t *testing.T) { be := defaultBE() be.respond = func(_ context.Context, _ []backend.Message, _ []backend.Tool, _ backend.GenerationParams) (<-chan backend.StreamEvent, error) { callCount++ - return textStream("ok"), nil + return textStream("ok"), nil } // exit 1 is a non-blocking warning: turn should still proceed. c := newCore(t, be, Hooks{HookPreTurn: []string{"exit 1"}}) @@ -1040,7 +1040,7 @@ func TestCommand_Backend_WhileRunning(t *testing.T) { unblock := make(chan struct{}) be := defaultBE() be.respond = func(ctx context.Context, _ []backend.Message, _ []backend.Tool, _ backend.GenerationParams) (<-chan backend.StreamEvent, error) { -return blockedStream(ctx, unblock), nil + return blockedStream(ctx, unblock), nil } c := newCore(t, be, nil) @@ -1097,7 +1097,7 @@ func TestCommand_Model_WhileRunning(t *testing.T) { unblock := make(chan struct{}) be := defaultBE() be.respond = func(ctx context.Context, _ []backend.Message, _ []backend.Tool, _ backend.GenerationParams) (<-chan backend.StreamEvent, error) { -return blockedStream(ctx, unblock), nil + return blockedStream(ctx, unblock), nil } c := newCore(t, be, nil) @@ -1180,7 +1180,7 @@ func TestCommand_Agent_WhileRunning(t *testing.T) { unblock := make(chan struct{}) be := defaultBE() be.respond = func(ctx context.Context, _ []backend.Message, _ []backend.Tool, _ backend.GenerationParams) (<-chan backend.StreamEvent, error) { -return blockedStream(ctx, unblock), nil + return blockedStream(ctx, unblock), nil } c := newCore(t, be, nil) @@ -1507,7 +1507,7 @@ func TestRun_RateLimitRetry(t *testing.T) { if callCount == 1 { return nil, &backend.RateLimitError{RetryAfter: time.Millisecond} } - return textStream("ok"), nil + return textStream("ok"), nil } c := newCore(t, be, nil) var retryEvents []Event @@ -1543,7 +1543,7 @@ func TestRun_ToolCancelledBeforeExec(t *testing.T) { StopReason: "tool_calls", } close(ch) -return ch, nil + return ch, nil } c := newCore(t, be, nil) @@ -1589,7 +1589,7 @@ func TestRun_ExecCancelledWithInject(t *testing.T) { StopReason: "tool_calls", } close(ch) -return ch, nil + return ch, nil } c := newCore(t, be, nil) @@ -1620,10 +1620,10 @@ func TestAutoCompact_WithHookContext(t *testing.T) { be.respond = func(_ context.Context, _ []backend.Message, _ []backend.Tool, _ backend.GenerationParams) (<-chan backend.StreamEvent, error) { callCount++ if callCount == 1 { -return textStream("summary text for compaction"), nil + return textStream("summary text for compaction"), nil } -return textStream("answer"), nil + return textStream("answer"), nil } c.session = newSession("goal") @@ -1691,7 +1691,7 @@ func TestCore_SetGenerationParams_WhileRunning(t *testing.T) { unblock := make(chan struct{}) be := defaultBE() be.respond = func(ctx context.Context, _ []backend.Message, _ []backend.Tool, _ backend.GenerationParams) (<-chan backend.StreamEvent, error) { -return blockedStream(ctx, unblock), nil + return blockedStream(ctx, unblock), nil } c := newCore(t, be, nil) @@ -1767,11 +1767,11 @@ func TestSetSessionID_Rename(t *testing.T) { if c.sessionID != newID { t.Errorf("sessionID = %q; want %q", c.sessionID, newID) } - if _, err := os.Stat(c.sessionsDir + "/" + oldID + ".json"); !os.IsNotExist(err) { - t.Errorf("old session file still exists after rename; err=%v", err) + 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) } - if _, err := os.Stat(c.sessionsDir + "/" + newID + ".json"); err != nil { - t.Errorf("new session file not found after rename: %v", err) + if _, err := os.Stat(filepath.Join(c.sessionsDir, "active", newID+".json")); err != nil { + t.Errorf("new active session file not found after rename: %v", err) } } @@ -1919,7 +1919,7 @@ func TestManualCompact_EmptySummary(t *testing.T) { ch := make(chan backend.StreamEvent, 1) ch <- backend.StreamEvent{Done: true, StopReason: "stop", Content: " "} close(ch) -return ch, nil + return ch, nil } c.session = newSession("goal") @@ -1947,7 +1947,7 @@ func TestManualCompact_WithToolMessages(t *testing.T) { be := defaultBE() c := newCore(t, be, nil) be.respond = func(_ context.Context, _ []backend.Message, _ []backend.Tool, _ backend.GenerationParams) (<-chan backend.StreamEvent, error) { -return textStream("summary"), nil + return textStream("summary"), nil } c.session = newSession("goal") @@ -2206,7 +2206,7 @@ type mockEnvServer struct { env map[string]string } -func (m *mockEnvServer) ListTools() ([]tools.ToolInfo, error) { return nil, nil } +func (m *mockEnvServer) ListTools() ([]tools.ToolInfo, error) { return nil, nil } func (m *mockEnvServer) CallTool(_ context.Context, _ string, _ json.RawMessage) (json.RawMessage, error) { return nil, fmt.Errorf("not implemented") } @@ -2230,9 +2230,9 @@ func newCoreWithExecServer(t *testing.T, srv *mockEnvServer) *agent { d := tools.NewDispatcher() d.AddServer("execute", srv) env := &Runtime{ - Hooks: Hooks{}, - Preamble: "test system prompt", - Dispatcher: d, + Hooks: Hooks{}, + Preamble: "test system prompt", + Dispatcher: d, } c := NewAgentCore(AgentCoreConfig{ Backend: defaultBE(), @@ -2276,7 +2276,6 @@ func TestSetEnv_StoredInCore(t *testing.T) { } } - func TestSetEnv_NilDispatcher_NoPanic(t *testing.T) { c := newCore(t, nil, nil) c.runtime.Dispatcher = nil @@ -2502,7 +2501,7 @@ func TestBuildRuntime_ExecDispatchSuccess(t *testing.T) { d := &mockDispatcher{ tools: []tools.ToolInfo{{Server: "s1", Name: "mytool"}}, dispatch: func(ctx context.Context, server, tool string, args json.RawMessage) (json.RawMessage, error) { -return json.RawMessage(`{"content":[{"type":"text","text":"ok"}]}`), nil + return json.RawMessage(`{"content":[{"type":"text","text":"ok"}]}`), nil }, } env := BuildRuntime(nil, d, t.TempDir(), nil) @@ -2535,7 +2534,7 @@ func TestBuildRuntime_ExecToolResultIsError(t *testing.T) { d := &mockDispatcher{ tools: []tools.ToolInfo{{Server: "s1", Name: "mytool"}}, dispatch: func(ctx context.Context, server, tool string, args json.RawMessage) (json.RawMessage, error) { -return json.RawMessage(`{"isError":true,"content":[{"type":"text","text":"bad thing"}]}`), nil + return json.RawMessage(`{"isError":true,"content":[{"type":"text","text":"bad thing"}]}`), nil }, } env := BuildRuntime(nil, d, t.TempDir(), nil)