Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
249 lines
7.5 KiB
Go
249 lines
7.5 KiB
Go
package agentruntime
|
|
|
|
import (
|
|
"encoding/json"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
type codexAppServerDiagnosticRecord struct {
|
|
event string
|
|
fields map[string]any
|
|
}
|
|
|
|
type codexAppServerDiagnosticSink struct {
|
|
records []codexAppServerDiagnosticRecord
|
|
}
|
|
|
|
func (s *codexAppServerDiagnosticSink) Log(event string, fields map[string]any) {
|
|
s.records = append(s.records, codexAppServerDiagnosticRecord{event: event, fields: fields})
|
|
}
|
|
|
|
func TestCodexAppServerTurnDiagnosticsRecordsBoundedToolLifecycle(t *testing.T) {
|
|
startedAt := time.Date(2026, 7, 24, 7, 0, 0, 0, time.UTC)
|
|
now := startedAt
|
|
sink := &codexAppServerDiagnosticSink{}
|
|
diagnostics := newCodexAppServerTurnDiagnostics(sink, "turn-1")
|
|
diagnostics.startedAt = startedAt
|
|
diagnostics.now = func() time.Time { return now }
|
|
|
|
now = startedAt.Add(time.Second)
|
|
command := map[string]any{
|
|
"id": "command-1",
|
|
"type": "commandExecution",
|
|
"command": "curl https://secret.example/?token=do-not-log",
|
|
"cwd": "/secret/workspace",
|
|
"status": "inProgress",
|
|
}
|
|
diagnostics.ObserveNotification(appServerNotifyItemStarted, map[string]any{"item": command})
|
|
diagnostics.ObserveNotification(appServerNotifyItemStarted, map[string]any{"item": command})
|
|
|
|
now = startedAt.Add(3250 * time.Millisecond)
|
|
command["status"] = "completed"
|
|
command["exitCode"] = 7
|
|
command["aggregatedOutput"] = "secret command output"
|
|
diagnostics.ObserveNotification(appServerNotifyItemCompleted, map[string]any{"item": command})
|
|
diagnostics.ObserveNotification(appServerNotifyItemCompleted, map[string]any{"item": command})
|
|
|
|
now = startedAt.Add(4 * time.Second)
|
|
diagnostics.ObserveNotification(appServerNotifyItemStarted, map[string]any{
|
|
"item": map[string]any{
|
|
"id": "mcp-1",
|
|
"type": "mcpToolCall",
|
|
"server": "documents",
|
|
"tool": "search",
|
|
"arguments": map[string]any{"query": "secret query"},
|
|
},
|
|
})
|
|
diagnostics.ObserveNotification(appServerNotifyAgentMessageDelta, map[string]any{
|
|
"delta": "secret assistant content",
|
|
})
|
|
|
|
now = startedAt.Add(10 * time.Second)
|
|
diagnostics.Finish("completed", "provider-turn-1")
|
|
diagnostics.Finish("failed", "provider-turn-1")
|
|
|
|
if got, want := len(sink.records), 4; got != want {
|
|
t.Fatalf("record count = %d, want %d: %#v", got, want, sink.records)
|
|
}
|
|
wantEvents := []string{
|
|
"turn.tool.started",
|
|
"turn.tool.completed",
|
|
"turn.tool.started",
|
|
"turn.completed",
|
|
}
|
|
for index, want := range wantEvents {
|
|
if got := sink.records[index].event; got != want {
|
|
t.Fatalf("record[%d].event = %q, want %q", index, got, want)
|
|
}
|
|
}
|
|
|
|
completed := sink.records[1].fields
|
|
if got := completed["duration_ms"]; got != int64(2250) {
|
|
t.Fatalf("command duration_ms = %#v, want 2250", got)
|
|
}
|
|
if got := completed["outcome"]; got != "failed" {
|
|
t.Fatalf("command outcome = %#v, want failed", got)
|
|
}
|
|
if got := completed["exit_code"]; got != 7 {
|
|
t.Fatalf("command exit_code = %#v, want 7", got)
|
|
}
|
|
|
|
summary := sink.records[3].fields
|
|
for key, want := range map[string]any{
|
|
"duration_ms": int64(10000),
|
|
"tool_started_count": 2,
|
|
"tool_completed_count": 1,
|
|
"tool_failed_count": 1,
|
|
"open_tool_count": 1,
|
|
"max_tool_duration_ms": int64(2250),
|
|
"max_tool_item_type": "commandExecution",
|
|
"longest_open_tool_ms": int64(6000),
|
|
"longest_open_item_type": "mcpToolCall",
|
|
"provider_turn_id": "provider-turn-1",
|
|
"turn_id": "turn-1",
|
|
"outcome": "completed",
|
|
} {
|
|
if got := summary[key]; got != want {
|
|
t.Fatalf("summary[%q] = %#v, want %#v", key, got, want)
|
|
}
|
|
}
|
|
|
|
serializedRecords := make([]map[string]any, 0, len(sink.records))
|
|
for _, record := range sink.records {
|
|
serializedRecords = append(serializedRecords, map[string]any{
|
|
"event": record.event,
|
|
"fields": record.fields,
|
|
})
|
|
}
|
|
encoded, err := json.Marshal(serializedRecords)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, secret := range []string{
|
|
"do-not-log",
|
|
"/secret/workspace",
|
|
"secret command output",
|
|
"secret query",
|
|
"secret assistant content",
|
|
} {
|
|
if strings.Contains(string(encoded), secret) {
|
|
t.Fatalf("diagnostic records leaked %q: %s", secret, encoded)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestCodexAppServerTurnDiagnosticsCompletesToolWithoutStart(t *testing.T) {
|
|
sink := &codexAppServerDiagnosticSink{}
|
|
diagnostics := newCodexAppServerTurnDiagnostics(sink, "turn-2")
|
|
diagnostics.ObserveNotification(appServerNotifyItemCompleted, map[string]any{
|
|
"item": map[string]any{
|
|
"id": "tool-1",
|
|
"type": "dynamicToolCall",
|
|
"tool": "render",
|
|
"status": "completed",
|
|
"success": true,
|
|
},
|
|
})
|
|
|
|
if got, want := len(sink.records), 1; got != want {
|
|
t.Fatalf("record count = %d, want %d", got, want)
|
|
}
|
|
fields := sink.records[0].fields
|
|
if got := fields["start_observed"]; got != false {
|
|
t.Fatalf("start_observed = %#v, want false", got)
|
|
}
|
|
if _, ok := fields["duration_ms"]; ok {
|
|
t.Fatalf("duration_ms should be absent when item/started was not observed: %#v", fields)
|
|
}
|
|
}
|
|
|
|
func TestCodexAppServerReducerRecordsOnlyAcceptedToolNotifications(t *testing.T) {
|
|
adapter := NewCodexAppServerAdapter(nil)
|
|
session := Session{
|
|
AgentSessionID: "agent-1",
|
|
Provider: ProviderCodex,
|
|
ProviderSessionID: "thread-1",
|
|
}
|
|
normalizer := newACPTurnNormalizer()
|
|
sink := &codexAppServerDiagnosticSink{}
|
|
activeTurn := &codexAppServerActiveTurn{
|
|
turnID: "turn-1",
|
|
normalizer: normalizer,
|
|
diagnostics: newCodexAppServerTurnDiagnostics(sink, "turn-1"),
|
|
phase: codexAppServerTurnPhaseRunning,
|
|
}
|
|
adapter.storeSession(session.AgentSessionID, &codexAppServerSession{
|
|
threadID: session.ProviderSessionID,
|
|
activeTurn: activeTurn,
|
|
})
|
|
reducer := newCodexAppServerReducer(adapter)
|
|
|
|
reducer.ReduceNotification(nil, session, activeTurn.turnID, acpMessage{
|
|
Method: appServerNotifyItemStarted,
|
|
Params: mustJSONRawMessage(t, map[string]any{
|
|
"threadId": session.ProviderSessionID,
|
|
"turnId": "provider-turn-1",
|
|
"item": map[string]any{
|
|
"id": "command-1",
|
|
"type": "commandExecution",
|
|
"command": "do-not-log this command",
|
|
},
|
|
}),
|
|
}, normalizer, nil)
|
|
reducer.ReduceNotification(nil, session, activeTurn.turnID, acpMessage{
|
|
Method: appServerNotifyItemStarted,
|
|
Params: mustJSONRawMessage(t, map[string]any{
|
|
"threadId": "foreign-thread",
|
|
"turnId": "foreign-turn",
|
|
"item": map[string]any{
|
|
"id": "foreign-command",
|
|
"type": "commandExecution",
|
|
"command": "foreign command",
|
|
},
|
|
}),
|
|
}, normalizer, nil)
|
|
|
|
if got, want := len(sink.records), 1; got != want {
|
|
t.Fatalf("record count = %d, want %d: %#v", got, want, sink.records)
|
|
}
|
|
if got := sink.records[0].fields["item_id"]; got != "command-1" {
|
|
t.Fatalf("item_id = %#v, want command-1", got)
|
|
}
|
|
}
|
|
|
|
func TestCodexAppServerToolDiagnosticFieldsRecognizeOnlyTools(t *testing.T) {
|
|
toolTypes := []string{
|
|
"commandExecution",
|
|
"fileChange",
|
|
"mcpToolCall",
|
|
"webSearch",
|
|
"dynamicToolCall",
|
|
"collabAgentToolCall",
|
|
"imageGeneration",
|
|
"imageView",
|
|
}
|
|
for _, itemType := range toolTypes {
|
|
t.Run(itemType, func(t *testing.T) {
|
|
_, gotType, _, ok := codexAppServerToolDiagnosticFields(map[string]any{
|
|
"id": "item-1",
|
|
"type": itemType,
|
|
})
|
|
if !ok || gotType != itemType {
|
|
t.Fatalf("recognized = %v, type = %q", ok, gotType)
|
|
}
|
|
})
|
|
}
|
|
for _, itemType := range []string{"agentMessage", "reasoning", "plan", "userMessage"} {
|
|
t.Run(itemType, func(t *testing.T) {
|
|
if _, _, _, ok := codexAppServerToolDiagnosticFields(map[string]any{
|
|
"id": "item-1",
|
|
"type": itemType,
|
|
}); ok {
|
|
t.Fatalf("non-tool item %q was recognized as a tool", itemType)
|
|
}
|
|
})
|
|
}
|
|
}
|