Files
tutti/packages/agent/daemon/runtime/controller_root_turn_test.go
agent-radar 2ed3415fb7
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
源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
2026-09-01 20:17:27 +08:00

83 lines
2.5 KiB
Go

package agentruntime
import (
"context"
"testing"
"time"
agentsessionstore "github.com/tutti-os/tutti/packages/agent/daemon/activity"
)
// A failed daemon root settlement must carry the durable failure reason into
// the live event stream: mid-turn provider failures (for example Codex quota
// exhaustion) settle through this path, and without the detail the
// visible-error projection can only render a generic card.
func TestReconcileRootTurnSettlementPublishesFailureDetail(t *testing.T) {
t.Parallel()
reporter := &recordingReporter{}
controller := NewDefaultControllerWithProcessTransport(reporter, newScriptedACPTransport())
ctx := context.Background()
started, err := controller.Start(ctx, StartInput{
RoomID: "room-1",
Provider: ProviderCodex,
CWD: "/workspace",
})
if err != nil {
t.Fatalf("Start: %v", err)
}
events, unsubscribe, ok := controller.Subscribe("room-1", started.Session.AgentSessionID)
if !ok {
t.Fatal("Subscribe returned ok=false")
}
defer unsubscribe()
execResult, err := controller.Exec(ctx, ExecInput{
RoomID: "room-1",
AgentSessionID: started.Session.AgentSessionID,
Content: textPrompt("hello"),
})
if err != nil {
t.Fatalf("Exec: %v", err)
}
if !execResult.Accepted || execResult.TurnID == "" {
t.Fatalf("Exec result = %#v, want accepted result with turn id", execResult)
}
const failureDetail = "You've hit your usage limit. Upgrade to Pro or try again later."
controller.ReconcileRootTurnSettlement(RootTurnSettlement{
RoomID: "room-1",
AgentSessionID: started.Session.AgentSessionID,
TurnID: execResult.TurnID,
Outcome: "failed",
ErrorMessage: failureDetail,
})
deadline := time.After(5 * time.Second)
for {
select {
case event := <-events:
if event.EventType != StreamEventMessageUpdate {
continue
}
update, ok := event.Data.(agentsessionstore.WorkspaceAgentMessageUpdate)
if !ok || update.Payload["kind"] != visibleErrorKind {
continue
}
if update.Payload["phase"] != "turn" {
t.Fatalf("visible failure phase = %#v, want turn", update.Payload["phase"])
}
if update.Payload["code"] != "quota_or_rate_limit" {
t.Fatalf("visible failure code = %#v, want quota_or_rate_limit", update.Payload["code"])
}
if update.Payload["detail"] != failureDetail {
t.Fatalf("visible failure detail = %#v, want %q", update.Payload["detail"], failureDetail)
}
return
case <-deadline:
t.Fatal("expected visible failure message for failed root turn settlement")
}
}
}