fix(subagents): preserve primary trace activity
- commit
- e1622b937dad4a55e324fa19cd82067abf721d04
- author
- brice · · 2026-08-12 10:06 UTC
- committer
- brice · · 2026-08-12 12:33 UTC
- parent
- 3e0f68b
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
4 files · +35 -7
M packages/agent/modes/subagent_indicator.go +1 -1
| @@ -46,7 +46,7 @@ func renderSubagentActivityLine(th tui.Theme, spinnerGlyph string, snapshot suba | |||
| 46 | 46 | } | |
| 47 | 47 | activity := operation.Label() | |
| 48 | 48 | if observation != nil { | |
| 49 | - | activity = observation.Label() + " · " + formatSubagentActivityAge(observation.At, now) + " ago" | |
| 49 | + | activity += " · " + observation.Label() + " " + formatSubagentActivityAge(observation.At, now) + " ago" | |
| 50 | 50 | } | |
| 51 | 51 | age := formatSubagentActivityAge(operation.StartedAt, now) | |
| 52 | 52 | plain, layout := fitSubagentActivityLine(spinnerGlyph, name, activity, age, width-2) | |
| @@ -46,7 +46,7 @@ func renderSubagentActivityLine(th tui.Theme, spinnerGlyph string, snapshot suba | |||||
| 46 | } | 46 | } | ||
| 47 | activity := operation.Label() | 47 | activity := operation.Label() | ||
| 48 | if observation != nil { | 48 | if observation != nil { | ||
| 49 | - | activity = observation.Label() + " · " + formatSubagentActivityAge(observation.At, now) + " ago" | 49 | + | activity += " · " + observation.Label() + " " + formatSubagentActivityAge(observation.At, now) + " ago" |
| 50 | } | 50 | } | ||
| 51 | age := formatSubagentActivityAge(operation.StartedAt, now) | 51 | age := formatSubagentActivityAge(operation.StartedAt, now) | ||
| 52 | plain, layout := fitSubagentActivityLine(spinnerGlyph, name, activity, age, width-2) | 52 | plain, layout := fitSubagentActivityLine(spinnerGlyph, name, activity, age, width-2) | ||
M packages/agent/modes/subagent_indicator_test.go +1 -1
| @@ -32,7 +32,7 @@ func TestRenderSubagentActivityLinesShowsSafeLastObservation(t *testing.T) { | |||
| 32 | 32 | lines := plainActivityLines(renderSubagentActivityLines(tui.Dark, "/", []subagents.AgentSnapshot{{ID: "review-123", Subagent: "reviewer"}}, map[string]subagents.AgentTraceView{ | |
| 33 | 33 | "review-123": {PrimaryOperation: &subagents.Operation{Type: "turn.started", StartedAt: now.Add(-time.Minute)}, LastObservation: &subagents.LiveObservation{Type: "assistant.stream.observed", At: now.Add(-time.Second)}}, | |
| 34 | 34 | }, 100, now)) | |
| 35 | - | if got := strings.Join(lines, "\n"); !strings.Contains(got, "assistant streaming · 1s ago") || strings.Contains(got, "delta") { | |
| 35 | + | if got := strings.Join(lines, "\n"); !strings.Contains(got, "turn open · assistant streaming 1s ago") || strings.Contains(got, "delta") { | |
| 36 | 36 | t.Fatalf("activity lines = %#v", lines) | |
| 37 | 37 | } | |
| 38 | 38 | } | |
| @@ -32,7 +32,7 @@ func TestRenderSubagentActivityLinesShowsSafeLastObservation(t *testing.T) { | |||||
| 32 | lines := plainActivityLines(renderSubagentActivityLines(tui.Dark, "/", []subagents.AgentSnapshot{{ID: "review-123", Subagent: "reviewer"}}, map[string]subagents.AgentTraceView{ | 32 | lines := plainActivityLines(renderSubagentActivityLines(tui.Dark, "/", []subagents.AgentSnapshot{{ID: "review-123", Subagent: "reviewer"}}, map[string]subagents.AgentTraceView{ | ||
| 33 | "review-123": {PrimaryOperation: &subagents.Operation{Type: "turn.started", StartedAt: now.Add(-time.Minute)}, LastObservation: &subagents.LiveObservation{Type: "assistant.stream.observed", At: now.Add(-time.Second)}}, | 33 | "review-123": {PrimaryOperation: &subagents.Operation{Type: "turn.started", StartedAt: now.Add(-time.Minute)}, LastObservation: &subagents.LiveObservation{Type: "assistant.stream.observed", At: now.Add(-time.Second)}}, | ||
| 34 | }, 100, now)) | 34 | }, 100, now)) | ||
| 35 | - | if got := strings.Join(lines, "\n"); !strings.Contains(got, "assistant streaming · 1s ago") || strings.Contains(got, "delta") { | 35 | + | if got := strings.Join(lines, "\n"); !strings.Contains(got, "turn open · assistant streaming 1s ago") || strings.Contains(got, "delta") { |
| 36 | t.Fatalf("activity lines = %#v", lines) | 36 | t.Fatalf("activity lines = %#v", lines) | ||
| 37 | } | 37 | } | ||
| 38 | } | 38 | } | ||
M packages/agent/tools/subagent_status.go +3 -3
| @@ -68,8 +68,8 @@ type subagentStatusResult struct { | |||
| 68 | 68 | State string `json:"state"` | |
| 69 | 69 | Available bool `json:"available"` | |
| 70 | 70 | Ref string `json:"ref,omitempty"` | |
| 71 | - | Delivered bool `json:"delivered,omitempty"` | |
| 72 | - | DeliveryFailed bool `json:"delivery_failed,omitempty"` | |
| 71 | + | Delivered bool `json:"delivered"` | |
| 72 | + | DeliveryFailed bool `json:"delivery_failed"` | |
| 73 | 73 | } | |
| 74 | 74 | ||
| 75 | 75 | const subagentStatusSchema = `{ | |
| @@ -190,7 +190,7 @@ func publicSubagentStatus(snapshot subagents.AgentSnapshot, view subagents.Agent | |||
| 190 | 190 | } | |
| 191 | 191 | if view.Result != nil { | |
| 192 | 192 | if entry.Result == nil { | |
| 193 | - | entry.Result = &subagentStatusResult{} | |
| 193 | + | entry.Result = &subagentStatusResult{State: "unknown"} | |
| 194 | 194 | } | |
| 195 | 195 | entry.Result.Available = view.Result.Available | |
| 196 | 196 | if view.Result.Ref != "" { | |
| @@ -68,8 +68,8 @@ type subagentStatusResult struct { | |||||
| 68 | State string `json:"state"` | 68 | State string `json:"state"` | ||
| 69 | Available bool `json:"available"` | 69 | Available bool `json:"available"` | ||
| 70 | Ref string `json:"ref,omitempty"` | 70 | Ref string `json:"ref,omitempty"` | ||
| 71 | - | Delivered bool `json:"delivered,omitempty"` | 71 | + | Delivered bool `json:"delivered"` |
| 72 | - | DeliveryFailed bool `json:"delivery_failed,omitempty"` | 72 | + | DeliveryFailed bool `json:"delivery_failed"` |
| 73 | } | 73 | } | ||
| 74 | 74 | ||||
| 75 | const subagentStatusSchema = `{ | 75 | const subagentStatusSchema = `{ | ||
| @@ -190,7 +190,7 @@ func publicSubagentStatus(snapshot subagents.AgentSnapshot, view subagents.Agent | |||||
| 190 | } | 190 | } | ||
| 191 | if view.Result != nil { | 191 | if view.Result != nil { | ||
| 192 | if entry.Result == nil { | 192 | if entry.Result == nil { | ||
| 193 | - | entry.Result = &subagentStatusResult{} | 193 | + | entry.Result = &subagentStatusResult{State: "unknown"} |
| 194 | } | 194 | } | ||
| 195 | entry.Result.Available = view.Result.Available | 195 | entry.Result.Available = view.Result.Available | ||
| 196 | if view.Result.Ref != "" { | 196 | if view.Result.Ref != "" { | ||
M packages/agent/tools/subagent_status_test.go +30 -2
| @@ -64,9 +64,10 @@ func TestSubagentStatusIncludesTurnCounters(t *testing.T) { | |||
| 64 | 64 | func TestPublicSubagentStatusUsesOnlyPrimaryOperation(t *testing.T) { | |
| 65 | 65 | at := time.Date(2026, 8, 12, 7, 0, 0, 0, time.UTC) | |
| 66 | 66 | entry := publicSubagentStatus(subagents.AgentSnapshot{ID: "agent-1"}, subagents.AgentTraceView{ | |
| 67 | - | OpenOperations: []subagents.Operation{{Type: "turn.started", StartedAt: at}, {Type: "tool.started", StartedAt: at.Add(time.Second)}}, | |
| 67 | + | OpenOperations: []subagents.Operation{{Type: "turn.started", StartedAt: at}}, | |
| 68 | + | PrimaryOperation: &subagents.Operation{Type: "tool.started", StartedAt: at.Add(time.Second)}, | |
| 68 | 69 | }) | |
| 69 | - | if entry.PrimaryOperation == nil || entry.PrimaryOperation.Type != "turn.started" { | |
| 70 | + | if entry.PrimaryOperation == nil || entry.PrimaryOperation.Type != "tool.started" { | |
| 70 | 71 | t.Fatalf("primary operation = %#v", entry.PrimaryOperation) | |
| 71 | 72 | } | |
| 72 | 73 | encoded, err := json.Marshal(entry) | |
| @@ -80,6 +81,33 @@ func TestPublicSubagentStatusUsesOnlyPrimaryOperation(t *testing.T) { | |||
| 80 | 81 | if _, found := fields["operation"]; found { | |
| 81 | 82 | t.Fatalf("obsolete operation field in %#v", fields) | |
| 82 | 83 | } | |
| 84 | + | if _, found := fields["primary_operation"]; !found { | |
| 85 | + | t.Fatalf("primary operation missing from %#v", fields) | |
| 86 | + | } | |
| 87 | + | } | |
| 88 | + | ||
| 89 | + | func TestPublicSubagentStatusIncludesResultDeliveryFacts(t *testing.T) { | |
| 90 | + | entry := publicSubagentStatus(subagents.AgentSnapshot{ID: "agent-1"}, subagents.AgentTraceView{ | |
| 91 | + | Result: &subagents.ResultFact{Available: true, Ref: "subagent://agent-1/result"}, | |
| 92 | + | }) | |
| 93 | + | if entry.Result == nil || entry.Result.State != "unknown" || entry.Result.Delivered || entry.Result.DeliveryFailed { | |
| 94 | + | t.Fatalf("result = %#v", entry.Result) | |
| 95 | + | } | |
| 96 | + | encoded, err := json.Marshal(entry) | |
| 97 | + | if err != nil { | |
| 98 | + | t.Fatal(err) | |
| 99 | + | } | |
| 100 | + | var fields map[string]json.RawMessage | |
| 101 | + | if err := json.Unmarshal(encoded, &fields); err != nil { | |
| 102 | + | t.Fatal(err) | |
| 103 | + | } | |
| 104 | + | var result map[string]any | |
| 105 | + | if err := json.Unmarshal(fields["result"], &result); err != nil { | |
| 106 | + | t.Fatal(err) | |
| 107 | + | } | |
| 108 | + | if result["delivered"] != false || result["delivery_failed"] != false { | |
| 109 | + | t.Fatalf("result JSON = %#v", result) | |
| 110 | + | } | |
| 83 | 111 | } | |
| 84 | 112 | ||
| 85 | 113 | func TestSubagentStatusSchemaHasOptionalAgentID(t *testing.T) { | |
| @@ -64,9 +64,10 @@ func TestSubagentStatusIncludesTurnCounters(t *testing.T) { | |||||
| 64 | func TestPublicSubagentStatusUsesOnlyPrimaryOperation(t *testing.T) { | 64 | func TestPublicSubagentStatusUsesOnlyPrimaryOperation(t *testing.T) { | ||
| 65 | at := time.Date(2026, 8, 12, 7, 0, 0, 0, time.UTC) | 65 | at := time.Date(2026, 8, 12, 7, 0, 0, 0, time.UTC) | ||
| 66 | entry := publicSubagentStatus(subagents.AgentSnapshot{ID: "agent-1"}, subagents.AgentTraceView{ | 66 | entry := publicSubagentStatus(subagents.AgentSnapshot{ID: "agent-1"}, subagents.AgentTraceView{ | ||
| 67 | - | OpenOperations: []subagents.Operation{{Type: "turn.started", StartedAt: at}, {Type: "tool.started", StartedAt: at.Add(time.Second)}}, | 67 | + | OpenOperations: []subagents.Operation{{Type: "turn.started", StartedAt: at}}, |
| 68 | + | PrimaryOperation: &subagents.Operation{Type: "tool.started", StartedAt: at.Add(time.Second)}, | |||
| 68 | }) | 69 | }) | ||
| 69 | - | if entry.PrimaryOperation == nil || entry.PrimaryOperation.Type != "turn.started" { | 70 | + | if entry.PrimaryOperation == nil || entry.PrimaryOperation.Type != "tool.started" { |
| 70 | t.Fatalf("primary operation = %#v", entry.PrimaryOperation) | 71 | t.Fatalf("primary operation = %#v", entry.PrimaryOperation) | ||
| 71 | } | 72 | } | ||
| 72 | encoded, err := json.Marshal(entry) | 73 | encoded, err := json.Marshal(entry) | ||
| @@ -80,6 +81,33 @@ func TestPublicSubagentStatusUsesOnlyPrimaryOperation(t *testing.T) { | |||||
| 80 | if _, found := fields["operation"]; found { | 81 | if _, found := fields["operation"]; found { | ||
| 81 | t.Fatalf("obsolete operation field in %#v", fields) | 82 | t.Fatalf("obsolete operation field in %#v", fields) | ||
| 82 | } | 83 | } | ||
| 84 | + | if _, found := fields["primary_operation"]; !found { | |||
| 85 | + | t.Fatalf("primary operation missing from %#v", fields) | |||
| 86 | + | } | |||
| 87 | + | } | |||
| 88 | + | ||||
| 89 | + | func TestPublicSubagentStatusIncludesResultDeliveryFacts(t *testing.T) { | |||
| 90 | + | entry := publicSubagentStatus(subagents.AgentSnapshot{ID: "agent-1"}, subagents.AgentTraceView{ | |||
| 91 | + | Result: &subagents.ResultFact{Available: true, Ref: "subagent://agent-1/result"}, | |||
| 92 | + | }) | |||
| 93 | + | if entry.Result == nil || entry.Result.State != "unknown" || entry.Result.Delivered || entry.Result.DeliveryFailed { | |||
| 94 | + | t.Fatalf("result = %#v", entry.Result) | |||
| 95 | + | } | |||
| 96 | + | encoded, err := json.Marshal(entry) | |||
| 97 | + | if err != nil { | |||
| 98 | + | t.Fatal(err) | |||
| 99 | + | } | |||
| 100 | + | var fields map[string]json.RawMessage | |||
| 101 | + | if err := json.Unmarshal(encoded, &fields); err != nil { | |||
| 102 | + | t.Fatal(err) | |||
| 103 | + | } | |||
| 104 | + | var result map[string]any | |||
| 105 | + | if err := json.Unmarshal(fields["result"], &result); err != nil { | |||
| 106 | + | t.Fatal(err) | |||
| 107 | + | } | |||
| 108 | + | if result["delivered"] != false || result["delivery_failed"] != false { | |||
| 109 | + | t.Fatalf("result JSON = %#v", result) | |||
| 110 | + | } | |||
| 83 | } | 111 | } | ||
| 84 | 112 | ||||
| 85 | func TestSubagentStatusSchemaHasOptionalAgentID(t *testing.T) { | 113 | func TestSubagentStatusSchemaHasOptionalAgentID(t *testing.T) { | ||