源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
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
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
This commit is contained in:
@@ -0,0 +1,345 @@
|
||||
package agentruntime
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
agentsessionstore "github.com/tutti-os/tutti/packages/agent/daemon/activity"
|
||||
activityshared "github.com/tutti-os/tutti/packages/agent/daemon/activity/events"
|
||||
"github.com/tutti-os/tutti/packages/agent/store-sqlite/canonical"
|
||||
)
|
||||
|
||||
type captureGoalControlLifecycleObserver struct {
|
||||
observations []GoalControlAppliedObservation
|
||||
}
|
||||
|
||||
func (o *captureGoalControlLifecycleObserver) ObserveGoalControlApplied(
|
||||
_ context.Context,
|
||||
observation GoalControlAppliedObservation,
|
||||
) error {
|
||||
o.observations = append(o.observations, observation)
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestControllerRoutesGoalControlAppliedOutsideSessionMetadata(t *testing.T) {
|
||||
t.Parallel()
|
||||
controller := NewController(nil, nil)
|
||||
observer := &captureGoalControlLifecycleObserver{}
|
||||
controller.SetGoalControlLifecycleObserver(observer)
|
||||
session := Session{
|
||||
RoomID: "room-1", AgentSessionID: "agent-session-1", Provider: ProviderClaudeCode,
|
||||
ProviderSessionID: "provider-session-1", CWD: "/workspace",
|
||||
}
|
||||
controller.store(session)
|
||||
ctx, ok := activityEventContext(session, "goal-applied-1", "")
|
||||
if !ok {
|
||||
t.Fatal("goal control event context unavailable")
|
||||
}
|
||||
event := activityshared.NewGoalControlApplied(ctx, map[string]any{
|
||||
"operationId": "goal-op-1", "revision": int64(3), "repairEpoch": int64(1),
|
||||
"action": "set", "providerTurnId": "provider-turn-1",
|
||||
"goal": map[string]any{"objective": "ship it", "status": "active"},
|
||||
})
|
||||
|
||||
controller.applySessionEventsByAgentSessionID(session.AgentSessionID, []activityshared.Event{event})
|
||||
|
||||
if len(observer.observations) != 1 {
|
||||
t.Fatalf("goal observations=%#v", observer.observations)
|
||||
}
|
||||
observation := observer.observations[0]
|
||||
if observation.WorkspaceID != "room-1" || observation.AgentSessionID != "agent-session-1" ||
|
||||
observation.OperationID != "goal-op-1" || observation.Revision != 3 || observation.RepairEpoch != 1 ||
|
||||
observation.Action != "set" || observation.ProviderTurnID != "provider-turn-1" ||
|
||||
observation.Observed["objective"] != "ship it" {
|
||||
t.Fatalf("goal observation=%#v", observation)
|
||||
}
|
||||
if report := reportActivityInput(session, []activityshared.Event{event}); len(report.StatePatches) != 0 {
|
||||
t.Fatalf("internal goal lifecycle leaked into session report=%#v", report.StatePatches)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnrichReportStatePatchesWithSessionMetadataFillsSnapshotTitleAndIdentity(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
report := &agentsessionstore.ReportActivityInput{
|
||||
StatePatches: []agentsessionstore.WorkspaceAgentStatePatch{{
|
||||
AgentSessionID: "agent-session-1",
|
||||
CurrentPhase: "failed",
|
||||
}},
|
||||
}
|
||||
enrichReportStatePatchesWithSessionMetadata(report, agentsessionstore.WorkspaceAgentStatePatch{
|
||||
AgentSessionID: "agent-session-1",
|
||||
Provider: "codex",
|
||||
ProviderSessionID: "provider-session-1",
|
||||
Model: "gpt-5",
|
||||
PermissionModeID: "bypassPermissions",
|
||||
CWD: "/workspace",
|
||||
Title: "Automation Review",
|
||||
Settings: map[string]any{"model": "gpt-5"},
|
||||
RuntimeContext: map[string]any{
|
||||
"title": "Automation Review",
|
||||
"cwd": "/workspace",
|
||||
},
|
||||
})
|
||||
|
||||
patch := report.StatePatches[0]
|
||||
if patch.Provider != "codex" ||
|
||||
patch.ProviderSessionID != "provider-session-1" ||
|
||||
patch.Model != "gpt-5" ||
|
||||
patch.PermissionModeID != "bypassPermissions" ||
|
||||
patch.CWD != "/workspace" ||
|
||||
patch.Title != "Automation Review" {
|
||||
t.Fatalf("enriched patch = %#v, want snapshot identity and title", patch)
|
||||
}
|
||||
if patch.RuntimeContext["title"] != "Automation Review" {
|
||||
t.Fatalf("runtime context = %#v, want title fallback", patch.RuntimeContext)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnrichReportStatePatchesWithSessionMetadataKeepsIncomingTitle(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
report := &agentsessionstore.ReportActivityInput{
|
||||
StatePatches: []agentsessionstore.WorkspaceAgentStatePatch{{
|
||||
AgentSessionID: "agent-session-1",
|
||||
Title: "Provider title",
|
||||
}},
|
||||
}
|
||||
enrichReportStatePatchesWithSessionMetadata(report, agentsessionstore.WorkspaceAgentStatePatch{
|
||||
AgentSessionID: "agent-session-1",
|
||||
Title: "Automation Review",
|
||||
})
|
||||
|
||||
if got := report.StatePatches[0].Title; got != "Provider title" {
|
||||
t.Fatalf("title = %q, want incoming provider title", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnrichReportStatePatchesWithSessionMetadataDoesNotAttachTurnLifecycle(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
activeTurnID := "root-turn-1"
|
||||
report := &agentsessionstore.ReportActivityInput{
|
||||
StatePatches: []agentsessionstore.WorkspaceAgentStatePatch{{
|
||||
AgentSessionID: "agent-session-1",
|
||||
RootProviderTurn: &canonical.WorkspaceAgentRootProviderTurnTransition{
|
||||
RootTurnID: "root-turn-1",
|
||||
ProviderTurnID: "provider-turn-1",
|
||||
Phase: agentsessionstore.RootProviderTurnPhaseCompleted,
|
||||
},
|
||||
}},
|
||||
}
|
||||
enrichReportStatePatchesWithSessionMetadata(report, agentsessionstore.WorkspaceAgentStatePatch{
|
||||
AgentSessionID: "agent-session-1",
|
||||
Provider: ProviderClaudeCode,
|
||||
TurnLifecycle: &canonical.WorkspaceAgentTurnLifecycle{
|
||||
ActiveTurnID: &activeTurnID,
|
||||
Phase: "waiting",
|
||||
},
|
||||
SubmitAvailability: &canonical.WorkspaceAgentSubmitAvailability{
|
||||
State: "blocked",
|
||||
Reason: "active_turn",
|
||||
},
|
||||
})
|
||||
|
||||
patch := report.StatePatches[0]
|
||||
if patch.Provider != ProviderClaudeCode {
|
||||
t.Fatalf("provider = %q, want session metadata", patch.Provider)
|
||||
}
|
||||
if patch.TurnLifecycle != nil || patch.SubmitAvailability != nil {
|
||||
t.Fatalf("persisted provider patch inherited runtime lifecycle: %#v", patch)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnrichStreamStateEventsWithSessionSnapshotFillsRuntimeContext(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
adapter := &statefulInteractiveAdapter{
|
||||
provider: ProviderClaudeCode,
|
||||
snapshot: SessionStateSnapshot{
|
||||
AgentSessionID: "agent-session-1",
|
||||
Provider: ProviderClaudeCode,
|
||||
TurnLifecycle: &TurnLifecycle{
|
||||
ActiveTurnID: stringPtr("synthetic-turn-1"),
|
||||
Phase: "running",
|
||||
},
|
||||
SubmitAvailability: &SubmitAvailability{
|
||||
State: "blocked",
|
||||
Reason: "active_turn",
|
||||
},
|
||||
PendingInteractive: &SessionInteractivePrompt{
|
||||
Kind: "ask-user",
|
||||
RequestID: "request-1",
|
||||
ToolName: "AskUserQuestion",
|
||||
Status: "waiting",
|
||||
Input: map[string]any{
|
||||
"questions": []any{map[string]any{
|
||||
"id": "scope",
|
||||
"question": "Scope?",
|
||||
}},
|
||||
},
|
||||
},
|
||||
RuntimeContext: map[string]any{
|
||||
"usage": map[string]any{
|
||||
"contextWindow": map[string]any{
|
||||
"usedTokens": int64(38414),
|
||||
"totalTokens": int64(200000),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
controller := NewController([]Adapter{adapter}, nil)
|
||||
session := Session{
|
||||
RoomID: "room-1",
|
||||
AgentSessionID: "agent-session-1",
|
||||
Provider: ProviderClaudeCode,
|
||||
}
|
||||
controller.store(session)
|
||||
events := []StreamEvent{{
|
||||
EventType: StreamEventStatePatch,
|
||||
Data: agentsessionstore.WorkspaceAgentStatePatch{
|
||||
AgentSessionID: "agent-session-1",
|
||||
CurrentPhase: "idle",
|
||||
},
|
||||
}}
|
||||
|
||||
controller.enrichStreamStateEventsWithSessionSnapshot(session, events)
|
||||
|
||||
patch, ok := events[0].Data.(agentsessionstore.WorkspaceAgentStatePatch)
|
||||
if !ok {
|
||||
t.Fatalf("stream patch type = %T, want WorkspaceAgentStatePatch", events[0].Data)
|
||||
}
|
||||
usage, _ := patch.RuntimeContext["usage"].(map[string]any)
|
||||
contextWindow, _ := usage["contextWindow"].(map[string]any)
|
||||
if got, _ := int64Value(contextWindow["totalTokens"]); got != 200000 {
|
||||
t.Fatalf("runtime context usage = %#v, want totalTokens=200000", patch.RuntimeContext["usage"])
|
||||
}
|
||||
if patch.TurnLifecycle == nil ||
|
||||
patch.TurnLifecycle.ActiveTurnID == nil ||
|
||||
*patch.TurnLifecycle.ActiveTurnID != "synthetic-turn-1" ||
|
||||
patch.TurnLifecycle.Phase != "running" {
|
||||
t.Fatalf("turn lifecycle = %#v, want synthetic running", patch.TurnLifecycle)
|
||||
}
|
||||
if patch.SubmitAvailability == nil ||
|
||||
patch.SubmitAvailability.State != "blocked" ||
|
||||
patch.SubmitAvailability.Reason != "active_turn" {
|
||||
t.Fatalf("submit availability = %#v, want active_turn blocked", patch.SubmitAvailability)
|
||||
}
|
||||
if patch.InteractionTransition != nil {
|
||||
t.Fatalf("snapshot enrichment interaction transition = %#v, want nil", patch.InteractionTransition)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSessionTitleEnrichmentDoesNotCrossSessionBoundaries(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
controller := NewController(nil, nil)
|
||||
session := Session{
|
||||
RoomID: "room-1",
|
||||
AgentSessionID: "root-session",
|
||||
Provider: ProviderCodex,
|
||||
Title: "Root title",
|
||||
UserTitleSet: true,
|
||||
}
|
||||
controller.store(session)
|
||||
|
||||
report := &agentsessionstore.ReportActivityInput{
|
||||
StatePatches: []agentsessionstore.WorkspaceAgentStatePatch{
|
||||
{AgentSessionID: "root-session", Title: "Stale root title"},
|
||||
{AgentSessionID: "child-session", Title: "Child title"},
|
||||
},
|
||||
}
|
||||
controller.enrichReportStatePatchesWithSessionMetadata(session, report)
|
||||
if got := report.StatePatches[0].Title; got != "Root title" {
|
||||
t.Fatalf("root report title = %q, want Root title", got)
|
||||
}
|
||||
if got := report.StatePatches[1].Title; got != "Child title" {
|
||||
t.Fatalf("child report title = %q, want Child title", got)
|
||||
}
|
||||
|
||||
stream := []StreamEvent{
|
||||
{
|
||||
EventType: StreamEventStatePatch,
|
||||
Data: agentsessionstore.WorkspaceAgentStatePatch{
|
||||
AgentSessionID: "root-session",
|
||||
Title: "Stale root title",
|
||||
},
|
||||
},
|
||||
{
|
||||
EventType: StreamEventStatePatch,
|
||||
Data: agentsessionstore.WorkspaceAgentStatePatch{
|
||||
AgentSessionID: "child-session",
|
||||
Title: "Child title",
|
||||
},
|
||||
},
|
||||
}
|
||||
controller.enrichStreamStateEventsWithSessionSnapshot(session, stream)
|
||||
rootPatch := stream[0].Data.(agentsessionstore.WorkspaceAgentStatePatch)
|
||||
childPatch := stream[1].Data.(agentsessionstore.WorkspaceAgentStatePatch)
|
||||
if rootPatch.Title != "Root title" {
|
||||
t.Fatalf("root stream title = %q, want Root title", rootPatch.Title)
|
||||
}
|
||||
if childPatch.Title != "Child title" {
|
||||
t.Fatalf("child stream title = %q, want Child title", childPatch.Title)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeriveSessionStatusFromEvents(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
session := Session{AgentSessionID: "agent-session-1", Provider: ProviderCodex}
|
||||
tests := []struct {
|
||||
name string
|
||||
events []activityshared.Event
|
||||
fallback string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "keeps working without terminal event",
|
||||
events: []activityshared.Event{newTurnActivityEvent(session, EventTurnStarted, "turn-1", SessionStatusWorking, "", "", nil)},
|
||||
fallback: SessionStatusReady,
|
||||
want: SessionStatusWorking,
|
||||
},
|
||||
{
|
||||
name: "turn completed makes conversation ready",
|
||||
events: []activityshared.Event{newTurnActivityEvent(session, EventTurnCompleted, "turn-1", SessionStatusCompleted, "", "", nil)},
|
||||
fallback: SessionStatusWorking,
|
||||
want: SessionStatusReady,
|
||||
},
|
||||
{
|
||||
name: "interrupted turn marks session canceled",
|
||||
events: []activityshared.Event{newTurnActivityEvent(session, EventTurnCanceled, "turn-1", SessionStatusCanceled, "", "", nil)},
|
||||
fallback: SessionStatusWorking,
|
||||
want: SessionStatusCanceled,
|
||||
},
|
||||
{
|
||||
name: "failed terminal event wins",
|
||||
events: []activityshared.Event{newTurnActivityEvent(session, EventTurnFailed, "turn-1", SessionStatusFailed, "", "", nil)},
|
||||
fallback: SessionStatusWorking,
|
||||
want: SessionStatusFailed,
|
||||
},
|
||||
{
|
||||
name: "waiting turn update keeps session waiting",
|
||||
events: []activityshared.Event{newTurnActivityEvent(session, EventTurnUpdated, "turn-1", SessionStatusWaiting, "", "", map[string]any{"phase": string(activityshared.TurnPhaseWaitingApproval)})},
|
||||
fallback: SessionStatusWorking,
|
||||
want: SessionStatusWaiting,
|
||||
},
|
||||
{
|
||||
name: "session completed ends session",
|
||||
events: []activityshared.Event{newSessionActivityEvent(session, EventSessionCompleted, SessionStatusCompleted, nil)},
|
||||
fallback: SessionStatusWorking,
|
||||
want: SessionStatusCompleted,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
if got := deriveSessionStatusFromEvents(tt.events, tt.fallback); got != tt.want {
|
||||
t.Fatalf("status = %q, want %q", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user