@stigmer/react 0.0.56 → 0.0.57

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/execution/ApprovalCard.d.ts.map +1 -1
  2. package/execution/ApprovalCard.js +1 -1
  3. package/execution/ApprovalCard.js.map +1 -1
  4. package/execution/ArtifactsWidget.d.ts +1 -1
  5. package/execution/ArtifactsWidget.js +1 -1
  6. package/execution/ExecutionProgress.d.ts.map +1 -1
  7. package/execution/ExecutionProgress.js +2 -59
  8. package/execution/ExecutionProgress.js.map +1 -1
  9. package/execution/MessageThread.d.ts.map +1 -1
  10. package/execution/MessageThread.js +31 -6
  11. package/execution/MessageThread.js.map +1 -1
  12. package/execution/SubAgentSection.d.ts +25 -4
  13. package/execution/SubAgentSection.d.ts.map +1 -1
  14. package/execution/SubAgentSection.js +70 -11
  15. package/execution/SubAgentSection.js.map +1 -1
  16. package/execution/TodoList.d.ts +42 -0
  17. package/execution/TodoList.d.ts.map +1 -0
  18. package/execution/TodoList.js +108 -0
  19. package/execution/TodoList.js.map +1 -0
  20. package/execution/ToolCallItem.js +1 -1
  21. package/execution/ToolCallItem.js.map +1 -1
  22. package/execution/UsageWidget.d.ts +57 -0
  23. package/execution/UsageWidget.d.ts.map +1 -0
  24. package/execution/UsageWidget.js +72 -0
  25. package/execution/UsageWidget.js.map +1 -0
  26. package/execution/index.d.ts +4 -4
  27. package/execution/index.d.ts.map +1 -1
  28. package/execution/index.js +2 -2
  29. package/execution/index.js.map +1 -1
  30. package/execution/useExecutionArtifacts.d.ts +1 -1
  31. package/execution/useExecutionArtifacts.js +1 -1
  32. package/index.d.ts +4 -4
  33. package/index.d.ts.map +1 -1
  34. package/index.js +2 -2
  35. package/index.js.map +1 -1
  36. package/package.json +4 -4
  37. package/session/index.d.ts +2 -0
  38. package/session/index.d.ts.map +1 -1
  39. package/session/index.js +1 -0
  40. package/session/index.js.map +1 -1
  41. package/session/useSessionUsage.d.ts +65 -0
  42. package/session/useSessionUsage.d.ts.map +1 -0
  43. package/session/useSessionUsage.js +107 -0
  44. package/session/useSessionUsage.js.map +1 -0
  45. package/src/execution/ApprovalCard.tsx +7 -13
  46. package/src/execution/ArtifactsWidget.tsx +1 -1
  47. package/src/execution/ExecutionProgress.tsx +2 -134
  48. package/src/execution/MessageThread.tsx +39 -6
  49. package/src/execution/SubAgentSection.tsx +323 -16
  50. package/src/execution/TodoList.tsx +202 -0
  51. package/src/execution/ToolCallItem.tsx +1 -1
  52. package/src/execution/{ExecutionCostSummary.tsx → UsageWidget.tsx} +43 -50
  53. package/src/execution/index.ts +10 -4
  54. package/src/execution/useExecutionArtifacts.ts +1 -1
  55. package/src/index.ts +12 -5
  56. package/src/session/index.ts +6 -0
  57. package/src/session/useSessionUsage.ts +159 -0
  58. package/styles.css +1 -1
  59. package/execution/ExecutionCostSummary.d.ts +0 -47
  60. package/execution/ExecutionCostSummary.d.ts.map +0 -1
  61. package/execution/ExecutionCostSummary.js +0 -77
  62. package/execution/ExecutionCostSummary.js.map +0 -1
  63. package/execution/__tests__/ExecutionCostSummary.test.d.ts +0 -2
  64. package/execution/__tests__/ExecutionCostSummary.test.d.ts.map +0 -1
  65. package/execution/__tests__/ExecutionCostSummary.test.js +0 -255
  66. package/execution/__tests__/ExecutionCostSummary.test.js.map +0 -1
  67. package/execution/__tests__/useExecutionUsage.test.d.ts +0 -2
  68. package/execution/__tests__/useExecutionUsage.test.d.ts.map +0 -1
  69. package/execution/__tests__/useExecutionUsage.test.js +0 -303
  70. package/execution/__tests__/useExecutionUsage.test.js.map +0 -1
  71. package/execution/useExecutionUsage.d.ts +0 -45
  72. package/execution/useExecutionUsage.d.ts.map +0 -1
  73. package/execution/useExecutionUsage.js +0 -157
  74. package/execution/useExecutionUsage.js.map +0 -1
  75. package/src/execution/__tests__/ExecutionCostSummary.test.tsx +0 -416
  76. package/src/execution/__tests__/useExecutionUsage.test.tsx +0 -408
  77. package/src/execution/useExecutionUsage.ts +0 -213
@@ -0,0 +1,65 @@
1
+ import type { AgentExecution } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
2
+ /**
3
+ * Per-model cost breakdown computed from per-message {@link LlmCallMetrics}.
4
+ */
5
+ export interface ModelCostEntry {
6
+ readonly model: string;
7
+ readonly provider: string;
8
+ readonly estimatedCostUsd: number;
9
+ readonly inputTokens: number;
10
+ readonly outputTokens: number;
11
+ readonly cacheCreationTokens: number;
12
+ readonly cacheReadTokens: number;
13
+ readonly callCount: number;
14
+ }
15
+ export interface UseSessionUsageReturn {
16
+ /** Total estimated cost across all executions in the session. */
17
+ readonly totalCostUsd: number;
18
+ /** Total tokens (all types) across all executions. */
19
+ readonly totalTokens: number;
20
+ /** Total input tokens (non-cached) across all executions. */
21
+ readonly inputTokens: number;
22
+ /** Total output tokens across all executions. */
23
+ readonly outputTokens: number;
24
+ /** Total cache read tokens across all executions. */
25
+ readonly cacheReadTokens: number;
26
+ /** Total cache creation tokens across all executions. */
27
+ readonly cacheCreationTokens: number;
28
+ /** Total number of LLM calls across all executions. */
29
+ readonly llmCallCount: number;
30
+ /** Per-model breakdown, sorted by cost descending. */
31
+ readonly modelBreakdown: readonly ModelCostEntry[];
32
+ /** Primary model (first model encountered). */
33
+ readonly primaryModel: string;
34
+ /** Primary provider (first provider encountered). */
35
+ readonly primaryProvider: string;
36
+ /** `true` when at least one execution has cost data. */
37
+ readonly hasUsage: boolean;
38
+ }
39
+ /**
40
+ * Pure derivation hook that aggregates usage data across all executions
41
+ * in a session from per-message {@link LlmCallMetrics}.
42
+ *
43
+ * Follows the same pattern as {@link useSessionArtifacts} and
44
+ * {@link useSessionWriteBacks}: `useMemo`-based derivation, no side
45
+ * effects, no data fetching. Takes the same `executions` array input.
46
+ *
47
+ * Per-message `llm_metrics` on `AgentMessage` (type == MESSAGE_AI) is
48
+ * the single source of truth for cost data. This hook walks all messages
49
+ * (main agent + sub-agents) across all executions and sums the fields.
50
+ *
51
+ * @param executions - All executions for a session, in chronological
52
+ * order. Pass both completed and active-stream executions.
53
+ *
54
+ * @example
55
+ * ```tsx
56
+ * const conv = useSessionConversation(sessionId, org);
57
+ * const allExecutions = [
58
+ * ...conv.completedExecutions,
59
+ * ...(conv.activeStreamExecution ? [conv.activeStreamExecution] : []),
60
+ * ];
61
+ * const { totalCostUsd, totalTokens, hasUsage } = useSessionUsage(allExecutions);
62
+ * ```
63
+ */
64
+ export declare function useSessionUsage(executions: readonly AgentExecution[]): UseSessionUsageReturn;
65
+ //# sourceMappingURL=useSessionUsage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useSessionUsage.d.ts","sourceRoot":"","sources":["../../src/session/useSessionUsage.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,6DAA6D,CAAC;AAIlG;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,mBAAmB,EAAE,MAAM,CAAC;IACrC,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,qBAAqB;IACpC,iEAAiE;IACjE,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,sDAAsD;IACtD,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,6DAA6D;IAC7D,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,iDAAiD;IACjD,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,qDAAqD;IACrD,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,yDAAyD;IACzD,QAAQ,CAAC,mBAAmB,EAAE,MAAM,CAAC;IACrC,uDAAuD;IACvD,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,sDAAsD;IACtD,QAAQ,CAAC,cAAc,EAAE,SAAS,cAAc,EAAE,CAAC;IACnD,+CAA+C;IAC/C,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,qDAAqD;IACrD,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,wDAAwD;IACxD,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;CAC5B;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,eAAe,CAC7B,UAAU,EAAE,SAAS,cAAc,EAAE,GACpC,qBAAqB,CAqFvB"}
@@ -0,0 +1,107 @@
1
+ "use client";
2
+ import { useMemo } from "react";
3
+ /**
4
+ * Pure derivation hook that aggregates usage data across all executions
5
+ * in a session from per-message {@link LlmCallMetrics}.
6
+ *
7
+ * Follows the same pattern as {@link useSessionArtifacts} and
8
+ * {@link useSessionWriteBacks}: `useMemo`-based derivation, no side
9
+ * effects, no data fetching. Takes the same `executions` array input.
10
+ *
11
+ * Per-message `llm_metrics` on `AgentMessage` (type == MESSAGE_AI) is
12
+ * the single source of truth for cost data. This hook walks all messages
13
+ * (main agent + sub-agents) across all executions and sums the fields.
14
+ *
15
+ * @param executions - All executions for a session, in chronological
16
+ * order. Pass both completed and active-stream executions.
17
+ *
18
+ * @example
19
+ * ```tsx
20
+ * const conv = useSessionConversation(sessionId, org);
21
+ * const allExecutions = [
22
+ * ...conv.completedExecutions,
23
+ * ...(conv.activeStreamExecution ? [conv.activeStreamExecution] : []),
24
+ * ];
25
+ * const { totalCostUsd, totalTokens, hasUsage } = useSessionUsage(allExecutions);
26
+ * ```
27
+ */
28
+ export function useSessionUsage(executions) {
29
+ return useMemo(() => {
30
+ let totalCostUsd = 0;
31
+ let totalTokens = 0;
32
+ let inputTokens = 0;
33
+ let outputTokens = 0;
34
+ let cacheReadTokens = 0;
35
+ let cacheCreationTokens = 0;
36
+ let llmCallCount = 0;
37
+ let primaryModel = "";
38
+ let primaryProvider = "";
39
+ const modelMap = new Map();
40
+ const processMessage = (msg) => {
41
+ const m = msg.llmMetrics;
42
+ if (!m)
43
+ return;
44
+ totalCostUsd += m.estimatedCostUsd;
45
+ totalTokens += m.totalTokens;
46
+ inputTokens += m.inputTokens;
47
+ outputTokens += m.outputTokens;
48
+ cacheReadTokens += m.cacheReadTokens;
49
+ cacheCreationTokens += m.cacheCreationTokens;
50
+ llmCallCount++;
51
+ if (!primaryModel && m.model) {
52
+ primaryModel = m.model;
53
+ primaryProvider = m.provider;
54
+ }
55
+ const key = `${m.model}\0${m.provider}`;
56
+ const existing = modelMap.get(key);
57
+ if (existing) {
58
+ modelMap.set(key, {
59
+ ...existing,
60
+ estimatedCostUsd: existing.estimatedCostUsd + m.estimatedCostUsd,
61
+ inputTokens: existing.inputTokens + m.inputTokens,
62
+ outputTokens: existing.outputTokens + m.outputTokens,
63
+ cacheCreationTokens: existing.cacheCreationTokens + m.cacheCreationTokens,
64
+ cacheReadTokens: existing.cacheReadTokens + m.cacheReadTokens,
65
+ callCount: existing.callCount + 1,
66
+ });
67
+ }
68
+ else {
69
+ modelMap.set(key, {
70
+ model: m.model,
71
+ provider: m.provider,
72
+ estimatedCostUsd: m.estimatedCostUsd,
73
+ inputTokens: m.inputTokens,
74
+ outputTokens: m.outputTokens,
75
+ cacheCreationTokens: m.cacheCreationTokens,
76
+ cacheReadTokens: m.cacheReadTokens,
77
+ callCount: 1,
78
+ });
79
+ }
80
+ };
81
+ for (const execution of executions) {
82
+ for (const msg of execution.status?.messages ?? []) {
83
+ processMessage(msg);
84
+ }
85
+ for (const sub of execution.status?.subAgentExecutions ?? []) {
86
+ for (const msg of sub.messages) {
87
+ processMessage(msg);
88
+ }
89
+ }
90
+ }
91
+ const modelBreakdown = Array.from(modelMap.values()).sort((a, b) => b.estimatedCostUsd - a.estimatedCostUsd);
92
+ return {
93
+ totalCostUsd,
94
+ totalTokens,
95
+ inputTokens,
96
+ outputTokens,
97
+ cacheReadTokens,
98
+ cacheCreationTokens,
99
+ llmCallCount,
100
+ modelBreakdown,
101
+ primaryModel,
102
+ primaryProvider,
103
+ hasUsage: llmCallCount > 0,
104
+ };
105
+ }, [executions]);
106
+ }
107
+ //# sourceMappingURL=useSessionUsage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useSessionUsage.js","sourceRoot":"","sources":["../../src/session/useSessionUsage.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAO,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC;AA4ChC;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,MAAM,UAAU,eAAe,CAC7B,UAAqC;IAErC,OAAO,OAAO,CAAC,GAAG,EAAE;QAClB,IAAI,YAAY,GAAG,CAAC,CAAC;QACrB,IAAI,WAAW,GAAG,CAAC,CAAC;QACpB,IAAI,WAAW,GAAG,CAAC,CAAC;QACpB,IAAI,YAAY,GAAG,CAAC,CAAC;QACrB,IAAI,eAAe,GAAG,CAAC,CAAC;QACxB,IAAI,mBAAmB,GAAG,CAAC,CAAC;QAC5B,IAAI,YAAY,GAAG,CAAC,CAAC;QACrB,IAAI,YAAY,GAAG,EAAE,CAAC;QACtB,IAAI,eAAe,GAAG,EAAE,CAAC;QACzB,MAAM,QAAQ,GAAG,IAAI,GAAG,EAA0B,CAAC;QAEnD,MAAM,cAAc,GAAG,CAAC,GAAuD,EAAE,EAAE;YACjF,MAAM,CAAC,GAAG,GAAG,CAAC,UAAU,CAAC;YACzB,IAAI,CAAC,CAAC;gBAAE,OAAO;YAEf,YAAY,IAAI,CAAC,CAAC,gBAAgB,CAAC;YACnC,WAAW,IAAI,CAAC,CAAC,WAAW,CAAC;YAC7B,WAAW,IAAI,CAAC,CAAC,WAAW,CAAC;YAC7B,YAAY,IAAI,CAAC,CAAC,YAAY,CAAC;YAC/B,eAAe,IAAI,CAAC,CAAC,eAAe,CAAC;YACrC,mBAAmB,IAAI,CAAC,CAAC,mBAAmB,CAAC;YAC7C,YAAY,EAAE,CAAC;YAEf,IAAI,CAAC,YAAY,IAAI,CAAC,CAAC,KAAK,EAAE,CAAC;gBAC7B,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC;gBACvB,eAAe,GAAG,CAAC,CAAC,QAAQ,CAAC;YAC/B,CAAC;YAED,MAAM,GAAG,GAAG,GAAG,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,QAAQ,EAAE,CAAC;YACxC,MAAM,QAAQ,GAAG,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YACnC,IAAI,QAAQ,EAAE,CAAC;gBACb,QAAQ,CAAC,GAAG,CAAC,GAAG,EAAE;oBAChB,GAAG,QAAQ;oBACX,gBAAgB,EAAE,QAAQ,CAAC,gBAAgB,GAAG,CAAC,CAAC,gBAAgB;oBAChE,WAAW,EAAE,QAAQ,CAAC,WAAW,GAAG,CAAC,CAAC,WAAW;oBACjD,YAAY,EAAE,QAAQ,CAAC,YAAY,GAAG,CAAC,CAAC,YAAY;oBACpD,mBAAmB,EAAE,QAAQ,CAAC,mBAAmB,GAAG,CAAC,CAAC,mBAAmB;oBACzE,eAAe,EAAE,QAAQ,CAAC,eAAe,GAAG,CAAC,CAAC,eAAe;oBAC7D,SAAS,EAAE,QAAQ,CAAC,SAAS,GAAG,CAAC;iBAClC,CAAC,CAAC;YACL,CAAC;iBAAM,CAAC;gBACN,QAAQ,CAAC,GAAG,CAAC,GAAG,EAAE;oBAChB,KAAK,EAAE,CAAC,CAAC,KAAK;oBACd,QAAQ,EAAE,CAAC,CAAC,QAAQ;oBACpB,gBAAgB,EAAE,CAAC,CAAC,gBAAgB;oBACpC,WAAW,EAAE,CAAC,CAAC,WAAW;oBAC1B,YAAY,EAAE,CAAC,CAAC,YAAY;oBAC5B,mBAAmB,EAAE,CAAC,CAAC,mBAAmB;oBAC1C,eAAe,EAAE,CAAC,CAAC,eAAe;oBAClC,SAAS,EAAE,CAAC;iBACb,CAAC,CAAC;YACL,CAAC;QACH,CAAC,CAAC;QAEF,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;YACnC,KAAK,MAAM,GAAG,IAAI,SAAS,CAAC,MAAM,EAAE,QAAQ,IAAI,EAAE,EAAE,CAAC;gBACnD,cAAc,CAAC,GAAG,CAAC,CAAC;YACtB,CAAC;YACD,KAAK,MAAM,GAAG,IAAI,SAAS,CAAC,MAAM,EAAE,kBAAkB,IAAI,EAAE,EAAE,CAAC;gBAC7D,KAAK,MAAM,GAAG,IAAI,GAAG,CAAC,QAAQ,EAAE,CAAC;oBAC/B,cAAc,CAAC,GAAG,CAAC,CAAC;gBACtB,CAAC;YACH,CAAC;QACH,CAAC;QAED,MAAM,cAAc,GAAG,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,CACvD,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,gBAAgB,GAAG,CAAC,CAAC,gBAAgB,CAClD,CAAC;QAEF,OAAO;YACL,YAAY;YACZ,WAAW;YACX,WAAW;YACX,YAAY;YACZ,eAAe;YACf,mBAAmB;YACnB,YAAY;YACZ,cAAc;YACd,YAAY;YACZ,eAAe;YACf,QAAQ,EAAE,YAAY,GAAG,CAAC;SAC3B,CAAC;IACJ,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC;AACnB,CAAC"}
@@ -134,6 +134,12 @@ export function ApprovalCard({
134
134
  )}
135
135
  </span>
136
136
 
137
+ {pendingApproval.fromSubAgent && pendingApproval.subAgentName && (
138
+ <span className="shrink-0 rounded bg-muted px-1 py-0.5 font-mono text-muted-foreground">
139
+ via {pendingApproval.subAgentSubject || pendingApproval.subAgentName}
140
+ </span>
141
+ )}
142
+
137
143
  <WaitingDuration requestedAt={pendingApproval.requestedAt} />
138
144
 
139
145
  <span className="shrink-0 text-warning" aria-hidden="true">
@@ -143,19 +149,7 @@ export function ApprovalCard({
143
149
 
144
150
  {/* Body */}
145
151
  <div className="px-3 py-2.5 space-y-2">
146
- {/* Sub-agent attribution */}
147
- {pendingApproval.fromSubAgent && pendingApproval.subAgentName && (
148
- <p className="text-xs text-muted-foreground">
149
- Sub-agent{" "}
150
- <span className="font-medium text-foreground">
151
- {pendingApproval.subAgentName}
152
- </span>{" "}
153
- wants to execute this tool
154
- </p>
155
- )}
156
-
157
- {/* Approval message */}
158
- {pendingApproval.message && (
152
+ {pendingApproval.message && categoryInfo.category !== "shell" && (
159
153
  <p className="text-xs text-foreground">
160
154
  {pendingApproval.message}
161
155
  </p>
@@ -51,7 +51,7 @@ export interface ArtifactsWidgetProps {
51
51
  *
52
52
  * Returns `null` when the executions list is empty or no execution
53
53
  * has artifacts, matching the conditional-render pattern of
54
- * {@link ExecutionProgress} and {@link ExecutionCostSummary}.
54
+ * {@link ExecutionProgress} and {@link UsageWidget}.
55
55
  *
56
56
  * Renders without card chrome — each {@link ArtifactCard} provides its
57
57
  * own border and padding. The consumer controls the container styling
@@ -1,11 +1,9 @@
1
1
  "use client";
2
2
 
3
- import { useMemo } from "react";
4
3
  import type { AgentExecution } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
5
- import type { TodoItem } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
6
- import { TodoStatus } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
7
4
  import { cn } from "@stigmer/theme";
8
5
  import { ExecutionPhaseBadge } from "./ExecutionPhaseBadge";
6
+ import { TodoList } from "./TodoList";
9
7
 
10
8
  export interface ExecutionProgressProps {
11
9
  /** The execution to display progress for. Renders nothing when null. */
@@ -13,17 +11,6 @@ export interface ExecutionProgressProps {
13
11
  readonly className?: string;
14
12
  }
15
13
 
16
- const STATUS_SORT_ORDER: ReadonlyMap<TodoStatus, number> = new Map([
17
- [TodoStatus.TODO_IN_PROGRESS, 0],
18
- [TodoStatus.TODO_PENDING, 1],
19
- [TodoStatus.TODO_COMPLETED, 2],
20
- [TodoStatus.TODO_CANCELLED, 3],
21
- ]);
22
-
23
- function todoSortKey(item: TodoItem): number {
24
- return STATUS_SORT_ORDER.get(item.status) ?? 4;
25
- }
26
-
27
14
  /**
28
15
  * Displays execution lifecycle phase and, when present, the agent's
29
16
  * todo checklist showing multi-step task progress.
@@ -59,13 +46,6 @@ export function ExecutionProgress({
59
46
 
60
47
  const todos = execution.status?.todos;
61
48
 
62
- const sortedTodos = useMemo(() => {
63
- if (!todos) return [];
64
- const items = Object.values(todos);
65
- if (items.length === 0) return [];
66
- return items.slice().sort((a, b) => todoSortKey(a) - todoSortKey(b));
67
- }, [todos]);
68
-
69
49
  return (
70
50
  <div
71
51
  className={cn("flex flex-col gap-2", className)}
@@ -73,119 +53,7 @@ export function ExecutionProgress({
73
53
  aria-label="Execution progress"
74
54
  >
75
55
  <ExecutionPhaseBadge phase={phase} />
76
- {sortedTodos.length > 0 && (
77
- <ul role="list" className="flex flex-col gap-1" aria-label="Tasks">
78
- {sortedTodos.map((item) => (
79
- <TodoRow key={item.id} item={item} />
80
- ))}
81
- </ul>
82
- )}
56
+ {todos && <TodoList todos={todos} />}
83
57
  </div>
84
58
  );
85
59
  }
86
-
87
- // ---------------------------------------------------------------------------
88
- // Internal sub-components
89
- // ---------------------------------------------------------------------------
90
-
91
- function TodoRow({ item }: { item: TodoItem }) {
92
- const Icon = TODO_ICONS[item.status] ?? TodoPendingIcon;
93
- const colorClass = TODO_COLORS[item.status] ?? "text-muted-foreground";
94
- const cancelled = item.status === TodoStatus.TODO_CANCELLED;
95
-
96
- return (
97
- <li className="flex items-start gap-1.5 text-xs">
98
- <span className={cn("mt-0.5 shrink-0", colorClass)} aria-hidden="true">
99
- <Icon />
100
- </span>
101
- <span
102
- className={cn(
103
- "min-w-0 break-words",
104
- cancelled ? "text-muted-foreground line-through" : "text-foreground",
105
- )}
106
- >
107
- {item.content}
108
- </span>
109
- </li>
110
- );
111
- }
112
-
113
- // ---------------------------------------------------------------------------
114
- // Status icon mapping
115
- // ---------------------------------------------------------------------------
116
-
117
- const TODO_ICONS: Partial<Record<TodoStatus, () => React.JSX.Element>> = {
118
- [TodoStatus.TODO_PENDING]: TodoPendingIcon,
119
- [TodoStatus.TODO_IN_PROGRESS]: TodoInProgressIcon,
120
- [TodoStatus.TODO_COMPLETED]: TodoCompletedIcon,
121
- [TodoStatus.TODO_CANCELLED]: TodoCancelledIcon,
122
- };
123
-
124
- const TODO_COLORS: Partial<Record<TodoStatus, string>> = {
125
- [TodoStatus.TODO_PENDING]: "text-muted-foreground",
126
- [TodoStatus.TODO_IN_PROGRESS]: "text-foreground",
127
- [TodoStatus.TODO_COMPLETED]: "text-success",
128
- [TodoStatus.TODO_CANCELLED]: "text-muted-foreground",
129
- };
130
-
131
- // ---------------------------------------------------------------------------
132
- // Inline SVG icons — no external icon dependency in SDK
133
- // ---------------------------------------------------------------------------
134
-
135
- function TodoPendingIcon() {
136
- return (
137
- <svg
138
- width="12"
139
- height="12"
140
- viewBox="0 0 12 12"
141
- fill="none"
142
- stroke="currentColor"
143
- strokeWidth="1.5"
144
- >
145
- <circle cx="6" cy="6" r="4.5" />
146
- </svg>
147
- );
148
- }
149
-
150
- function TodoInProgressIcon() {
151
- return (
152
- <span className="relative flex h-3 w-3 items-center justify-center">
153
- <span className="absolute inline-flex h-2 w-2 animate-ping rounded-full bg-current opacity-75" />
154
- <span className="relative inline-flex h-2 w-2 rounded-full bg-current" />
155
- </span>
156
- );
157
- }
158
-
159
- function TodoCompletedIcon() {
160
- return (
161
- <svg
162
- width="12"
163
- height="12"
164
- viewBox="0 0 12 12"
165
- fill="none"
166
- stroke="currentColor"
167
- strokeWidth="2"
168
- strokeLinecap="round"
169
- strokeLinejoin="round"
170
- >
171
- <path d="M2.5 6L5 8.5L9.5 3.5" />
172
- </svg>
173
- );
174
- }
175
-
176
- function TodoCancelledIcon() {
177
- return (
178
- <svg
179
- width="12"
180
- height="12"
181
- viewBox="0 0 12 12"
182
- fill="none"
183
- stroke="currentColor"
184
- strokeWidth="2"
185
- strokeLinecap="round"
186
- strokeLinejoin="round"
187
- >
188
- <path d="M3 3L9 9M9 3L3 9" />
189
- </svg>
190
- );
191
- }
@@ -17,6 +17,7 @@ import { cn } from "@stigmer/theme";
17
17
  import { isTerminalPhase } from "./execution-phases";
18
18
  import { MessageEntry } from "./MessageEntry";
19
19
  import { ToolCallGroup } from "./ToolCallGroup";
20
+ import { SubAgentSection } from "./SubAgentSection";
20
21
  import { ExecutionPhaseBadge } from "./ExecutionPhaseBadge";
21
22
  import { SetupProgress } from "./SetupProgress";
22
23
  import { ApprovalCard } from "./ApprovalCard";
@@ -110,6 +111,7 @@ const AUTO_SCROLL_THRESHOLD_PX = 80;
110
111
  type ThreadItem =
111
112
  | { readonly kind: "message"; readonly message: AgentMessage; readonly key: string }
112
113
  | { readonly kind: "tool-group"; readonly toolCalls: readonly ToolCall[]; readonly subAgentExecutions: readonly SubAgentExecution[]; readonly key: string }
114
+ | { readonly kind: "sub-agent"; readonly subAgentExecution: SubAgentExecution; readonly key: string }
113
115
  | { readonly kind: "phase-badge"; readonly phase: ExecutionPhase; readonly key: string }
114
116
  | { readonly kind: "pending-message"; readonly content: string; readonly key: string }
115
117
  | { readonly kind: "approval-request"; readonly pendingApproval: PendingApproval; readonly key: string }
@@ -176,12 +178,35 @@ function buildThreadItems(
176
178
  msg.type === MessageType.MESSAGE_AI &&
177
179
  msg.toolCalls.length > 0
178
180
  ) {
179
- items.push({
180
- kind: "tool-group",
181
- toolCalls: msg.toolCalls,
182
- subAgentExecutions: subAgents,
183
- key: `e${ei}-m${mi}-tc`,
184
- });
181
+ const regularTools: ToolCall[] = [];
182
+ const taskTools: ToolCall[] = [];
183
+ for (const tc of msg.toolCalls) {
184
+ if (tc.name === "task") {
185
+ taskTools.push(tc);
186
+ } else {
187
+ regularTools.push(tc);
188
+ }
189
+ }
190
+
191
+ if (regularTools.length > 0) {
192
+ items.push({
193
+ kind: "tool-group",
194
+ toolCalls: regularTools,
195
+ subAgentExecutions: subAgents,
196
+ key: `e${ei}-m${mi}-tc`,
197
+ });
198
+ }
199
+
200
+ for (let ti = 0; ti < taskTools.length; ti++) {
201
+ const matched = subAgents.find((sa) => sa.id === taskTools[ti].id);
202
+ if (matched) {
203
+ items.push({
204
+ kind: "sub-agent",
205
+ subAgentExecution: matched,
206
+ key: `e${ei}-m${mi}-sa${ti}`,
207
+ });
208
+ }
209
+ }
185
210
  }
186
211
  }
187
212
  }
@@ -349,6 +374,14 @@ export function MessageThread({
349
374
  className="mx-4"
350
375
  />
351
376
  );
377
+ case "sub-agent":
378
+ return (
379
+ <SubAgentSection
380
+ key={item.key}
381
+ subAgentExecution={item.subAgentExecution}
382
+ className="mx-4"
383
+ />
384
+ );
352
385
  case "phase-badge":
353
386
  return (
354
387
  <div key={item.key} className="flex justify-center py-3">