persist active sessions under active directory
This commit is contained in:
parent
2202014397
commit
95557b35ab
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Reference in New Issue