package subagents import ( "encoding/json" "strings" "testing" "github.com/bnema/zut/packages/core" "github.com/bnema/zut/packages/provider" ) func TestResidentLiveProjectionPublishesCopiesAndFinalizesLiveItems(t *testing.T) { projection := newResidentLiveProjection() projection.Start("turn-1") projection.Apply(core.EvTextDelta{Delta: "draft"}) projection.Apply(core.EvToolUseStart{ID: "call-1", Name: "bash"}) projection.Apply(core.EvToolUseArgs{ID: "call-1", Delta: `{"command":"pwd"}`}) projection.Apply(core.EvToolExecutionStarted{ID: "call-1", Name: "bash"}) live := projection.Snapshot() if live.TurnID != "turn-1" || live.State != ResidentRunning || live.AssistantText != "draft" || len(live.Tools) != 1 || live.Tools[0].State != ResidentLiveToolRunning || string(live.Tools[0].Args) != `{"command":"pwd"}` || live.Revision == 0 { t.Fatalf("live projection = %#v", live) } live.Tools[0].Args[0] = 'x' if got := projection.Snapshot(); string(got.Tools[0].Args) != `{"command":"pwd"}` { t.Fatalf("snapshot mutated projection: %#v", got) } projection.Apply(core.EvToolResult{ID: "call-1", Result: core.ToolResult{Content: []provider.Content{provider.TextBlock{Text: "done"}}}}) projection.Apply(core.EvRequestStarted{}) if !projection.Snapshot().WaitingForModel { t.Fatal("provider request did not mark the resident as waiting for the model") } projection.Apply(core.EvAssistantMessage{Message: provider.Message{Role: provider.RoleAssistant, Content: []provider.Content{provider.TextBlock{Text: "final"}}}}) projection.Finish(ResidentIdle) final := projection.Snapshot() if final.AssistantText != "" || len(final.Tools) != 0 || final.State != ResidentIdle || final.Revision <= live.Revision { t.Fatalf("final projection = %#v", final) } } func TestResidentLiveProjectionPreservesContextAcrossZeroUsage(t *testing.T) { projection := newResidentLiveProjection() usage := provider.Usage{InputTokens: 84_000, CacheReadTokens: 123_000} projection.Apply(core.EvUsage{Usage: usage, Cumulative: usage}) projection.Apply(core.EvUsage{Usage: provider.Usage{}, Cumulative: usage}) got := projection.Snapshot() if got.Usage != usage || got.ContextUsed != 207_000 { t.Fatalf("live usage = %#v, context = %d; want cumulative usage and preserved context", got.Usage, got.ContextUsed) } } func TestResidentLiveProjectionBoundsToolArguments(t *testing.T) { projection := newResidentLiveProjection() projection.Start("turn-1") projection.Apply(core.EvToolCall{ID: "call-1", Name: "bash", Args: json.RawMessage(`{"command":"pwd"}`)}) if got := projection.Snapshot(); len(got.Tools) != 1 || string(got.Tools[0].Args) != `{"command":"pwd"}` { t.Fatalf("live tool = %#v", got) } projection.Apply(core.EvToolCall{ID: "call-2", Name: "bash", Args: json.RawMessage(strings.Repeat("a", residentLiveArgumentBytes+1024))}) if got := projection.Snapshot(); len(got.Tools) != 2 || len(got.Tools[1].Args) != residentLiveArgumentBytes { t.Fatalf("oversized tool args = %d bytes", len(got.Tools[1].Args)) } }