@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.
- package/execution/ApprovalCard.d.ts.map +1 -1
- package/execution/ApprovalCard.js +1 -1
- package/execution/ApprovalCard.js.map +1 -1
- package/execution/ArtifactsWidget.d.ts +1 -1
- package/execution/ArtifactsWidget.js +1 -1
- package/execution/ExecutionProgress.d.ts.map +1 -1
- package/execution/ExecutionProgress.js +2 -59
- package/execution/ExecutionProgress.js.map +1 -1
- package/execution/MessageThread.d.ts.map +1 -1
- package/execution/MessageThread.js +31 -6
- package/execution/MessageThread.js.map +1 -1
- package/execution/SubAgentSection.d.ts +25 -4
- package/execution/SubAgentSection.d.ts.map +1 -1
- package/execution/SubAgentSection.js +70 -11
- package/execution/SubAgentSection.js.map +1 -1
- package/execution/TodoList.d.ts +42 -0
- package/execution/TodoList.d.ts.map +1 -0
- package/execution/TodoList.js +108 -0
- package/execution/TodoList.js.map +1 -0
- package/execution/ToolCallItem.js +1 -1
- package/execution/ToolCallItem.js.map +1 -1
- package/execution/UsageWidget.d.ts +57 -0
- package/execution/UsageWidget.d.ts.map +1 -0
- package/execution/UsageWidget.js +72 -0
- package/execution/UsageWidget.js.map +1 -0
- package/execution/index.d.ts +4 -4
- package/execution/index.d.ts.map +1 -1
- package/execution/index.js +2 -2
- package/execution/index.js.map +1 -1
- package/execution/useExecutionArtifacts.d.ts +1 -1
- package/execution/useExecutionArtifacts.js +1 -1
- package/index.d.ts +4 -4
- package/index.d.ts.map +1 -1
- package/index.js +2 -2
- package/index.js.map +1 -1
- package/package.json +4 -4
- package/session/index.d.ts +2 -0
- package/session/index.d.ts.map +1 -1
- package/session/index.js +1 -0
- package/session/index.js.map +1 -1
- package/session/useSessionUsage.d.ts +65 -0
- package/session/useSessionUsage.d.ts.map +1 -0
- package/session/useSessionUsage.js +107 -0
- package/session/useSessionUsage.js.map +1 -0
- package/src/execution/ApprovalCard.tsx +7 -13
- package/src/execution/ArtifactsWidget.tsx +1 -1
- package/src/execution/ExecutionProgress.tsx +2 -134
- package/src/execution/MessageThread.tsx +39 -6
- package/src/execution/SubAgentSection.tsx +323 -16
- package/src/execution/TodoList.tsx +202 -0
- package/src/execution/ToolCallItem.tsx +1 -1
- package/src/execution/{ExecutionCostSummary.tsx → UsageWidget.tsx} +43 -50
- package/src/execution/index.ts +10 -4
- package/src/execution/useExecutionArtifacts.ts +1 -1
- package/src/index.ts +12 -5
- package/src/session/index.ts +6 -0
- package/src/session/useSessionUsage.ts +159 -0
- package/styles.css +1 -1
- package/execution/ExecutionCostSummary.d.ts +0 -47
- package/execution/ExecutionCostSummary.d.ts.map +0 -1
- package/execution/ExecutionCostSummary.js +0 -77
- package/execution/ExecutionCostSummary.js.map +0 -1
- package/execution/__tests__/ExecutionCostSummary.test.d.ts +0 -2
- package/execution/__tests__/ExecutionCostSummary.test.d.ts.map +0 -1
- package/execution/__tests__/ExecutionCostSummary.test.js +0 -255
- package/execution/__tests__/ExecutionCostSummary.test.js.map +0 -1
- package/execution/__tests__/useExecutionUsage.test.d.ts +0 -2
- package/execution/__tests__/useExecutionUsage.test.d.ts.map +0 -1
- package/execution/__tests__/useExecutionUsage.test.js +0 -303
- package/execution/__tests__/useExecutionUsage.test.js.map +0 -1
- package/execution/useExecutionUsage.d.ts +0 -45
- package/execution/useExecutionUsage.d.ts.map +0 -1
- package/execution/useExecutionUsage.js +0 -157
- package/execution/useExecutionUsage.js.map +0 -1
- package/src/execution/__tests__/ExecutionCostSummary.test.tsx +0 -416
- package/src/execution/__tests__/useExecutionUsage.test.tsx +0 -408
- package/src/execution/useExecutionUsage.ts +0 -213
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
+
import { useMemo, useState } from "react";
|
|
3
4
|
import type { SubAgentExecution } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/subagent_pb";
|
|
5
|
+
import type { TodoItem } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/todo_pb";
|
|
4
6
|
import type { AgentMessage, ToolCall } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/message_pb";
|
|
5
7
|
import {
|
|
6
8
|
MessageType,
|
|
@@ -10,9 +12,23 @@ import { cn } from "@stigmer/theme";
|
|
|
10
12
|
import { formatDuration } from "./ToolCallDetail";
|
|
11
13
|
import { MessageEntry } from "./MessageEntry";
|
|
12
14
|
import { ToolCallGroup } from "./ToolCallGroup";
|
|
15
|
+
import {
|
|
16
|
+
TodoList,
|
|
17
|
+
TodoInProgressIcon,
|
|
18
|
+
findActiveTodo,
|
|
19
|
+
todoCompletionSummary,
|
|
20
|
+
} from "./TodoList";
|
|
13
21
|
|
|
14
22
|
export interface SubAgentSectionProps {
|
|
15
23
|
readonly subAgentExecution: SubAgentExecution;
|
|
24
|
+
/**
|
|
25
|
+
* Whether to render as a collapsible card with expand/collapse
|
|
26
|
+
* toggle. Defaults to `true`.
|
|
27
|
+
*
|
|
28
|
+
* Set to `false` when rendered inside a parent that already
|
|
29
|
+
* provides its own expand/collapse (e.g. {@link ToolCallItem}).
|
|
30
|
+
*/
|
|
31
|
+
readonly collapsible?: boolean;
|
|
16
32
|
readonly className?: string;
|
|
17
33
|
}
|
|
18
34
|
|
|
@@ -20,28 +36,221 @@ export interface SubAgentSectionProps {
|
|
|
20
36
|
* Renders a sub-agent execution as a nested mini-thread inside the
|
|
21
37
|
* parent conversation.
|
|
22
38
|
*
|
|
39
|
+
* When `collapsible` is `true` (default), the component renders as a
|
|
40
|
+
* bordered card with a clickable summary row. Cards start collapsed
|
|
41
|
+
* — the summary row shows status, subject, and duration at a glance.
|
|
42
|
+
* Users click to expand and see the nested messages and tool calls.
|
|
43
|
+
*
|
|
44
|
+
* Visually distinguished from {@link ToolCallGroup} via a left
|
|
45
|
+
* accent border and bot icon, signaling delegated sub-agent work.
|
|
46
|
+
*
|
|
47
|
+
* When `collapsible` is `false`, the component renders flat content
|
|
48
|
+
* without a toggle — suitable for embedding inside a parent that
|
|
49
|
+
* already provides expand/collapse (e.g. {@link ToolCallItem}).
|
|
50
|
+
*
|
|
23
51
|
* Composes {@link MessageEntry} and {@link ToolCallGroup} to display
|
|
24
52
|
* the sub-agent's internal messages and tool calls — the same
|
|
25
53
|
* building blocks used by the top-level {@link MessageThread}.
|
|
26
54
|
*
|
|
27
|
-
* Visually distinguished from the parent thread via a left border
|
|
28
|
-
* and subtle background.
|
|
29
|
-
*
|
|
30
55
|
* @example
|
|
31
56
|
* ```tsx
|
|
57
|
+
* // Standalone collapsible card (default)
|
|
32
58
|
* <SubAgentSection subAgentExecution={sub} />
|
|
59
|
+
*
|
|
60
|
+
* // Flat layout inside a parent expand/collapse
|
|
61
|
+
* <SubAgentSection subAgentExecution={sub} collapsible={false} />
|
|
33
62
|
* ```
|
|
34
63
|
*/
|
|
35
64
|
export function SubAgentSection({
|
|
36
65
|
subAgentExecution: sub,
|
|
66
|
+
collapsible = true,
|
|
37
67
|
className,
|
|
38
68
|
}: SubAgentSectionProps) {
|
|
39
69
|
const duration = formatDuration(sub.startedAt, sub.completedAt);
|
|
40
70
|
const statusInfo = SUB_AGENT_STATUS_MAP[sub.status];
|
|
41
|
-
const
|
|
71
|
+
const StatusIcon = statusInfo.icon;
|
|
42
72
|
const isFailed = sub.status === SubAgentStatus.SUB_AGENT_FAILED;
|
|
43
73
|
const threadItems = buildSubAgentThreadItems(sub.messages);
|
|
44
74
|
|
|
75
|
+
const displayLabel = sub.subject || sub.name;
|
|
76
|
+
|
|
77
|
+
if (!collapsible) {
|
|
78
|
+
return (
|
|
79
|
+
<FlatContent
|
|
80
|
+
sub={sub}
|
|
81
|
+
statusInfo={statusInfo}
|
|
82
|
+
StatusIcon={StatusIcon}
|
|
83
|
+
duration={duration}
|
|
84
|
+
isFailed={isFailed}
|
|
85
|
+
threadItems={threadItems}
|
|
86
|
+
className={className}
|
|
87
|
+
/>
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return (
|
|
92
|
+
<CollapsibleCard
|
|
93
|
+
sub={sub}
|
|
94
|
+
statusInfo={statusInfo}
|
|
95
|
+
displayLabel={displayLabel}
|
|
96
|
+
duration={duration}
|
|
97
|
+
isFailed={isFailed}
|
|
98
|
+
threadItems={threadItems}
|
|
99
|
+
className={className}
|
|
100
|
+
/>
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// ---------------------------------------------------------------------------
|
|
105
|
+
// Collapsible card — progressive disclosure (default mode)
|
|
106
|
+
// ---------------------------------------------------------------------------
|
|
107
|
+
|
|
108
|
+
interface CollapsibleCardProps {
|
|
109
|
+
readonly sub: SubAgentExecution;
|
|
110
|
+
readonly statusInfo: SubAgentStatusInfo;
|
|
111
|
+
readonly displayLabel: string;
|
|
112
|
+
readonly duration: string | null;
|
|
113
|
+
readonly isFailed: boolean;
|
|
114
|
+
readonly threadItems: SubAgentThreadItem[];
|
|
115
|
+
readonly className?: string;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function CollapsibleCard({
|
|
119
|
+
sub,
|
|
120
|
+
statusInfo,
|
|
121
|
+
displayLabel,
|
|
122
|
+
duration,
|
|
123
|
+
isFailed,
|
|
124
|
+
threadItems,
|
|
125
|
+
className,
|
|
126
|
+
}: CollapsibleCardProps) {
|
|
127
|
+
const [expanded, setExpanded] = useState(false);
|
|
128
|
+
|
|
129
|
+
const handleToggle = () => {
|
|
130
|
+
setExpanded((v) => !v);
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
const hasTodos =
|
|
134
|
+
sub.todos != null && Object.keys(sub.todos).length > 0;
|
|
135
|
+
const isRunning = sub.status === SubAgentStatus.SUB_AGENT_IN_PROGRESS;
|
|
136
|
+
const isCompleted = sub.status === SubAgentStatus.SUB_AGENT_COMPLETED;
|
|
137
|
+
|
|
138
|
+
const activeTodo = useMemo(() => findActiveTodo(sub.todos), [sub.todos]);
|
|
139
|
+
const completionLabel = useMemo(
|
|
140
|
+
() => (isCompleted && hasTodos ? todoCompletionSummary(sub.todos) : null),
|
|
141
|
+
[sub.todos, isCompleted, hasTodos],
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
const collapsedPreview =
|
|
145
|
+
isRunning && activeTodo
|
|
146
|
+
? activeTodo.content
|
|
147
|
+
: isCompleted && completionLabel
|
|
148
|
+
? completionLabel
|
|
149
|
+
: null;
|
|
150
|
+
|
|
151
|
+
const ariaLabel = `Sub-agent: ${displayLabel}, ${statusInfo.label}`;
|
|
152
|
+
|
|
153
|
+
return (
|
|
154
|
+
<div
|
|
155
|
+
role="group"
|
|
156
|
+
aria-label={ariaLabel}
|
|
157
|
+
className={cn(
|
|
158
|
+
"rounded-md border border-border border-l-2 border-l-primary/30 bg-muted/30",
|
|
159
|
+
className,
|
|
160
|
+
)}
|
|
161
|
+
>
|
|
162
|
+
{/* Summary trigger */}
|
|
163
|
+
<button
|
|
164
|
+
type="button"
|
|
165
|
+
aria-expanded={expanded}
|
|
166
|
+
onClick={handleToggle}
|
|
167
|
+
className={cn(
|
|
168
|
+
"flex w-full items-center gap-2 px-2.5 py-1.5 text-left text-xs text-muted-foreground transition-colors",
|
|
169
|
+
"hover:bg-muted/50",
|
|
170
|
+
"cursor-pointer",
|
|
171
|
+
)}
|
|
172
|
+
>
|
|
173
|
+
<span className="shrink-0 text-primary/70" aria-hidden="true">
|
|
174
|
+
<BotIcon />
|
|
175
|
+
</span>
|
|
176
|
+
<span className="min-w-0 flex-1 truncate">{displayLabel}</span>
|
|
177
|
+
<span
|
|
178
|
+
className={cn(
|
|
179
|
+
"shrink-0 rounded px-1 py-0.5 text-[10px] font-medium leading-none",
|
|
180
|
+
statusInfo.badgeClass,
|
|
181
|
+
)}
|
|
182
|
+
>
|
|
183
|
+
{statusInfo.label}
|
|
184
|
+
</span>
|
|
185
|
+
{duration && (
|
|
186
|
+
<span className="shrink-0 tabular-nums text-muted-foreground">
|
|
187
|
+
{duration}
|
|
188
|
+
</span>
|
|
189
|
+
)}
|
|
190
|
+
<ChevronIcon expanded={expanded} />
|
|
191
|
+
</button>
|
|
192
|
+
|
|
193
|
+
{/* Active todo preview — visible when collapsed */}
|
|
194
|
+
{collapsedPreview && !expanded && (
|
|
195
|
+
<div className="flex items-center gap-1.5 px-2.5 pb-1.5 text-xs text-muted-foreground">
|
|
196
|
+
<span className="ml-[20px] shrink-0" aria-hidden="true">
|
|
197
|
+
{isRunning && activeTodo ? (
|
|
198
|
+
<TodoInProgressIcon />
|
|
199
|
+
) : (
|
|
200
|
+
<TodoCompletedSmallIcon />
|
|
201
|
+
)}
|
|
202
|
+
</span>
|
|
203
|
+
<span className="min-w-0 truncate">{collapsedPreview}</span>
|
|
204
|
+
</div>
|
|
205
|
+
)}
|
|
206
|
+
|
|
207
|
+
{/* Expanded content — CSS grid-rows animation */}
|
|
208
|
+
<div
|
|
209
|
+
className={cn(
|
|
210
|
+
"grid transition-[grid-template-rows] duration-150 ease-out",
|
|
211
|
+
expanded ? "grid-rows-[1fr]" : "grid-rows-[0fr]",
|
|
212
|
+
)}
|
|
213
|
+
>
|
|
214
|
+
<div className="overflow-hidden">
|
|
215
|
+
{expanded && (
|
|
216
|
+
<div className="border-t border-border/50 px-2.5 pb-2 pt-1.5">
|
|
217
|
+
<SubAgentThreadContent
|
|
218
|
+
threadItems={threadItems}
|
|
219
|
+
todos={sub.todos}
|
|
220
|
+
isFailed={isFailed}
|
|
221
|
+
error={sub.error}
|
|
222
|
+
/>
|
|
223
|
+
</div>
|
|
224
|
+
)}
|
|
225
|
+
</div>
|
|
226
|
+
</div>
|
|
227
|
+
</div>
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// ---------------------------------------------------------------------------
|
|
232
|
+
// Flat content — no toggle (used inside ToolCallItem detail panel)
|
|
233
|
+
// ---------------------------------------------------------------------------
|
|
234
|
+
|
|
235
|
+
interface FlatContentProps {
|
|
236
|
+
readonly sub: SubAgentExecution;
|
|
237
|
+
readonly statusInfo: SubAgentStatusInfo;
|
|
238
|
+
readonly StatusIcon: () => React.JSX.Element;
|
|
239
|
+
readonly duration: string | null;
|
|
240
|
+
readonly isFailed: boolean;
|
|
241
|
+
readonly threadItems: SubAgentThreadItem[];
|
|
242
|
+
readonly className?: string;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function FlatContent({
|
|
246
|
+
sub,
|
|
247
|
+
statusInfo,
|
|
248
|
+
StatusIcon,
|
|
249
|
+
duration,
|
|
250
|
+
isFailed,
|
|
251
|
+
threadItems,
|
|
252
|
+
className,
|
|
253
|
+
}: FlatContentProps) {
|
|
45
254
|
return (
|
|
46
255
|
<div
|
|
47
256
|
className={cn(
|
|
@@ -55,7 +264,7 @@ export function SubAgentSection({
|
|
|
55
264
|
className={cn("shrink-0", statusInfo.colorClass)}
|
|
56
265
|
aria-hidden="true"
|
|
57
266
|
>
|
|
58
|
-
<
|
|
267
|
+
<StatusIcon />
|
|
59
268
|
</span>
|
|
60
269
|
<span className="font-medium text-foreground">
|
|
61
270
|
{sub.name}
|
|
@@ -72,7 +281,39 @@ export function SubAgentSection({
|
|
|
72
281
|
)}
|
|
73
282
|
</div>
|
|
74
283
|
|
|
75
|
-
|
|
284
|
+
<SubAgentThreadContent
|
|
285
|
+
threadItems={threadItems}
|
|
286
|
+
todos={sub.todos}
|
|
287
|
+
isFailed={isFailed}
|
|
288
|
+
error={sub.error}
|
|
289
|
+
/>
|
|
290
|
+
</div>
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// ---------------------------------------------------------------------------
|
|
295
|
+
// Shared thread content renderer
|
|
296
|
+
// ---------------------------------------------------------------------------
|
|
297
|
+
|
|
298
|
+
interface SubAgentThreadContentProps {
|
|
299
|
+
readonly threadItems: SubAgentThreadItem[];
|
|
300
|
+
readonly todos?: { readonly [key: string]: TodoItem };
|
|
301
|
+
readonly isFailed: boolean;
|
|
302
|
+
readonly error: string;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function SubAgentThreadContent({
|
|
306
|
+
threadItems,
|
|
307
|
+
todos,
|
|
308
|
+
isFailed,
|
|
309
|
+
error,
|
|
310
|
+
}: SubAgentThreadContentProps) {
|
|
311
|
+
const hasTodos = todos != null && Object.keys(todos).length > 0;
|
|
312
|
+
|
|
313
|
+
return (
|
|
314
|
+
<>
|
|
315
|
+
{hasTodos && <TodoList todos={todos!} className="pb-1" />}
|
|
316
|
+
|
|
76
317
|
{threadItems.length > 0 && (
|
|
77
318
|
<div className="flex flex-col gap-1 pb-1">
|
|
78
319
|
{threadItems.map((item) => {
|
|
@@ -93,13 +334,12 @@ export function SubAgentSection({
|
|
|
93
334
|
</div>
|
|
94
335
|
)}
|
|
95
336
|
|
|
96
|
-
{
|
|
97
|
-
{isFailed && sub.error && (
|
|
337
|
+
{isFailed && error && (
|
|
98
338
|
<div className="rounded-md border border-destructive/20 bg-destructive/5 px-2 py-1.5 text-xs text-destructive">
|
|
99
|
-
{
|
|
339
|
+
{error}
|
|
100
340
|
</div>
|
|
101
341
|
)}
|
|
102
|
-
|
|
342
|
+
</>
|
|
103
343
|
);
|
|
104
344
|
}
|
|
105
345
|
|
|
@@ -142,6 +382,7 @@ function buildSubAgentThreadItems(
|
|
|
142
382
|
interface SubAgentStatusInfo {
|
|
143
383
|
label: string;
|
|
144
384
|
colorClass: string;
|
|
385
|
+
badgeClass: string;
|
|
145
386
|
icon: () => React.JSX.Element;
|
|
146
387
|
}
|
|
147
388
|
|
|
@@ -149,31 +390,37 @@ const SUB_AGENT_STATUS_MAP: Record<SubAgentStatus, SubAgentStatusInfo> = {
|
|
|
149
390
|
[SubAgentStatus.SUB_AGENT_STATUS_UNSPECIFIED]: {
|
|
150
391
|
label: "Unknown",
|
|
151
392
|
colorClass: "text-muted-foreground",
|
|
393
|
+
badgeClass: "bg-muted text-muted-foreground",
|
|
152
394
|
icon: DotIcon,
|
|
153
395
|
},
|
|
154
396
|
[SubAgentStatus.SUB_AGENT_PENDING]: {
|
|
155
397
|
label: "Pending",
|
|
156
398
|
colorClass: "text-muted-foreground",
|
|
399
|
+
badgeClass: "bg-muted text-muted-foreground",
|
|
157
400
|
icon: DotIcon,
|
|
158
401
|
},
|
|
159
402
|
[SubAgentStatus.SUB_AGENT_IN_PROGRESS]: {
|
|
160
403
|
label: "Running",
|
|
161
404
|
colorClass: "text-foreground",
|
|
405
|
+
badgeClass: "bg-muted text-foreground",
|
|
162
406
|
icon: SpinnerIcon,
|
|
163
407
|
},
|
|
164
408
|
[SubAgentStatus.SUB_AGENT_COMPLETED]: {
|
|
165
409
|
label: "Completed",
|
|
166
410
|
colorClass: "text-success",
|
|
411
|
+
badgeClass: "bg-success/15 text-success",
|
|
167
412
|
icon: CheckCircleIcon,
|
|
168
413
|
},
|
|
169
414
|
[SubAgentStatus.SUB_AGENT_FAILED]: {
|
|
170
415
|
label: "Failed",
|
|
171
416
|
colorClass: "text-destructive",
|
|
417
|
+
badgeClass: "bg-destructive/15 text-destructive",
|
|
172
418
|
icon: XCircleIcon,
|
|
173
419
|
},
|
|
174
420
|
[SubAgentStatus.SUB_AGENT_CANCELLED]: {
|
|
175
421
|
label: "Cancelled",
|
|
176
422
|
colorClass: "text-muted-foreground",
|
|
423
|
+
badgeClass: "bg-muted text-muted-foreground",
|
|
177
424
|
icon: XCircleIcon,
|
|
178
425
|
},
|
|
179
426
|
};
|
|
@@ -185,8 +432,8 @@ const SUB_AGENT_STATUS_MAP: Record<SubAgentStatus, SubAgentStatusInfo> = {
|
|
|
185
432
|
function SpinnerIcon() {
|
|
186
433
|
return (
|
|
187
434
|
<svg
|
|
188
|
-
width="
|
|
189
|
-
height="
|
|
435
|
+
width="12"
|
|
436
|
+
height="12"
|
|
190
437
|
viewBox="0 0 12 12"
|
|
191
438
|
fill="none"
|
|
192
439
|
stroke="currentColor"
|
|
@@ -201,8 +448,8 @@ function SpinnerIcon() {
|
|
|
201
448
|
function CheckCircleIcon() {
|
|
202
449
|
return (
|
|
203
450
|
<svg
|
|
204
|
-
width="
|
|
205
|
-
height="
|
|
451
|
+
width="12"
|
|
452
|
+
height="12"
|
|
206
453
|
viewBox="0 0 12 12"
|
|
207
454
|
fill="none"
|
|
208
455
|
stroke="currentColor"
|
|
@@ -219,8 +466,8 @@ function CheckCircleIcon() {
|
|
|
219
466
|
function XCircleIcon() {
|
|
220
467
|
return (
|
|
221
468
|
<svg
|
|
222
|
-
width="
|
|
223
|
-
height="
|
|
469
|
+
width="12"
|
|
470
|
+
height="12"
|
|
224
471
|
viewBox="0 0 12 12"
|
|
225
472
|
fill="none"
|
|
226
473
|
stroke="currentColor"
|
|
@@ -241,3 +488,63 @@ function DotIcon() {
|
|
|
241
488
|
</svg>
|
|
242
489
|
);
|
|
243
490
|
}
|
|
491
|
+
|
|
492
|
+
function TodoCompletedSmallIcon() {
|
|
493
|
+
return (
|
|
494
|
+
<svg
|
|
495
|
+
width="12"
|
|
496
|
+
height="12"
|
|
497
|
+
viewBox="0 0 12 12"
|
|
498
|
+
fill="none"
|
|
499
|
+
stroke="currentColor"
|
|
500
|
+
strokeWidth="2"
|
|
501
|
+
strokeLinecap="round"
|
|
502
|
+
strokeLinejoin="round"
|
|
503
|
+
>
|
|
504
|
+
<path d="M2.5 6L5 8.5L9.5 3.5" />
|
|
505
|
+
</svg>
|
|
506
|
+
);
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
function BotIcon() {
|
|
510
|
+
return (
|
|
511
|
+
<svg
|
|
512
|
+
width="12"
|
|
513
|
+
height="12"
|
|
514
|
+
viewBox="0 0 16 16"
|
|
515
|
+
fill="none"
|
|
516
|
+
stroke="currentColor"
|
|
517
|
+
strokeWidth="1.5"
|
|
518
|
+
strokeLinecap="round"
|
|
519
|
+
strokeLinejoin="round"
|
|
520
|
+
>
|
|
521
|
+
<rect x="2" y="6" width="12" height="8" rx="2" />
|
|
522
|
+
<path d="M5.5 10H5.51M10.5 10H10.51" strokeWidth="2" />
|
|
523
|
+
<path d="M8 2V6" />
|
|
524
|
+
<circle cx="8" cy="1.5" r="1" />
|
|
525
|
+
<path d="M0.5 9.5H2M14 9.5H15.5" />
|
|
526
|
+
</svg>
|
|
527
|
+
);
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
function ChevronIcon({ expanded }: { expanded: boolean }) {
|
|
531
|
+
return (
|
|
532
|
+
<svg
|
|
533
|
+
width="12"
|
|
534
|
+
height="12"
|
|
535
|
+
viewBox="0 0 12 12"
|
|
536
|
+
fill="none"
|
|
537
|
+
stroke="currentColor"
|
|
538
|
+
strokeWidth="1.5"
|
|
539
|
+
strokeLinecap="round"
|
|
540
|
+
strokeLinejoin="round"
|
|
541
|
+
className={cn(
|
|
542
|
+
"shrink-0 text-muted-foreground transition-transform duration-150",
|
|
543
|
+
expanded && "rotate-90",
|
|
544
|
+
)}
|
|
545
|
+
aria-hidden="true"
|
|
546
|
+
>
|
|
547
|
+
<path d="M4.5 2.5L7.5 6L4.5 9.5" />
|
|
548
|
+
</svg>
|
|
549
|
+
);
|
|
550
|
+
}
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useMemo } from "react";
|
|
4
|
+
import type { TodoItem } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/todo_pb";
|
|
5
|
+
import { TodoStatus } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
6
|
+
import { cn } from "@stigmer/theme";
|
|
7
|
+
|
|
8
|
+
export interface TodoListProps {
|
|
9
|
+
/**
|
|
10
|
+
* Todo items keyed by ID. Accepts the proto map shape directly
|
|
11
|
+
* (`execution.status.todos` or `subAgentExecution.todos`).
|
|
12
|
+
*/
|
|
13
|
+
readonly todos: { readonly [key: string]: TodoItem };
|
|
14
|
+
readonly className?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const STATUS_SORT_ORDER: ReadonlyMap<TodoStatus, number> = new Map([
|
|
18
|
+
[TodoStatus.TODO_IN_PROGRESS, 0],
|
|
19
|
+
[TodoStatus.TODO_PENDING, 1],
|
|
20
|
+
[TodoStatus.TODO_COMPLETED, 2],
|
|
21
|
+
[TodoStatus.TODO_CANCELLED, 3],
|
|
22
|
+
]);
|
|
23
|
+
|
|
24
|
+
function todoSortKey(item: TodoItem): number {
|
|
25
|
+
return STATUS_SORT_ORDER.get(item.status) ?? 4;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Renders a sorted checklist of {@link TodoItem} entries.
|
|
30
|
+
*
|
|
31
|
+
* Shared by {@link ExecutionProgress} (main agent sidebar widget)
|
|
32
|
+
* and {@link SubAgentSection} (sub-agent expanded content).
|
|
33
|
+
*
|
|
34
|
+
* Items are sorted by activity: in-progress first, then pending,
|
|
35
|
+
* completed, and cancelled. Each row shows a status icon and the
|
|
36
|
+
* task description.
|
|
37
|
+
*
|
|
38
|
+
* All visual properties flow through `--stgm-*` tokens.
|
|
39
|
+
*/
|
|
40
|
+
export function TodoList({ todos, className }: TodoListProps) {
|
|
41
|
+
const sortedTodos = useMemo(() => {
|
|
42
|
+
const items = Object.values(todos);
|
|
43
|
+
if (items.length === 0) return [];
|
|
44
|
+
return items.slice().sort((a, b) => todoSortKey(a) - todoSortKey(b));
|
|
45
|
+
}, [todos]);
|
|
46
|
+
|
|
47
|
+
if (sortedTodos.length === 0) return null;
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<ul
|
|
51
|
+
role="list"
|
|
52
|
+
className={cn("flex flex-col gap-1", className)}
|
|
53
|
+
aria-label="Tasks"
|
|
54
|
+
>
|
|
55
|
+
{sortedTodos.map((item) => (
|
|
56
|
+
<TodoRow key={item.id} item={item} />
|
|
57
|
+
))}
|
|
58
|
+
</ul>
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// ---------------------------------------------------------------------------
|
|
63
|
+
// Exported for reuse in collapsed sub-agent preview
|
|
64
|
+
// ---------------------------------------------------------------------------
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Returns the first in-progress todo from a todos map, or `null`.
|
|
68
|
+
* Used by {@link SubAgentSection} to show an active task preview
|
|
69
|
+
* in the collapsed summary row.
|
|
70
|
+
*/
|
|
71
|
+
export function findActiveTodo(
|
|
72
|
+
todos: { readonly [key: string]: TodoItem } | undefined,
|
|
73
|
+
): TodoItem | null {
|
|
74
|
+
if (!todos) return null;
|
|
75
|
+
for (const item of Object.values(todos)) {
|
|
76
|
+
if (item.status === TodoStatus.TODO_IN_PROGRESS) return item;
|
|
77
|
+
}
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Builds a compact completion summary string (e.g. "3/5 completed").
|
|
83
|
+
* Returns `null` when there are no todos.
|
|
84
|
+
*/
|
|
85
|
+
export function todoCompletionSummary(
|
|
86
|
+
todos: { readonly [key: string]: TodoItem } | undefined,
|
|
87
|
+
): string | null {
|
|
88
|
+
if (!todos) return null;
|
|
89
|
+
const items = Object.values(todos);
|
|
90
|
+
if (items.length === 0) return null;
|
|
91
|
+
const completed = items.filter(
|
|
92
|
+
(t) => t.status === TodoStatus.TODO_COMPLETED,
|
|
93
|
+
).length;
|
|
94
|
+
return `${completed}/${items.length} completed`;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// ---------------------------------------------------------------------------
|
|
98
|
+
// TodoRow — single todo item with status icon
|
|
99
|
+
// ---------------------------------------------------------------------------
|
|
100
|
+
|
|
101
|
+
function TodoRow({ item }: { readonly item: TodoItem }) {
|
|
102
|
+
const Icon = TODO_ICONS[item.status] ?? TodoPendingIcon;
|
|
103
|
+
const colorClass = TODO_COLORS[item.status] ?? "text-muted-foreground";
|
|
104
|
+
const cancelled = item.status === TodoStatus.TODO_CANCELLED;
|
|
105
|
+
|
|
106
|
+
return (
|
|
107
|
+
<li className="flex items-start gap-1.5 text-xs">
|
|
108
|
+
<span className={cn("mt-0.5 shrink-0", colorClass)} aria-hidden="true">
|
|
109
|
+
<Icon />
|
|
110
|
+
</span>
|
|
111
|
+
<span
|
|
112
|
+
className={cn(
|
|
113
|
+
"min-w-0 break-words",
|
|
114
|
+
cancelled ? "text-muted-foreground line-through" : "text-foreground",
|
|
115
|
+
)}
|
|
116
|
+
>
|
|
117
|
+
{item.content}
|
|
118
|
+
</span>
|
|
119
|
+
</li>
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// ---------------------------------------------------------------------------
|
|
124
|
+
// Status icon / color mapping
|
|
125
|
+
// ---------------------------------------------------------------------------
|
|
126
|
+
|
|
127
|
+
const TODO_ICONS: Partial<Record<TodoStatus, () => React.JSX.Element>> = {
|
|
128
|
+
[TodoStatus.TODO_PENDING]: TodoPendingIcon,
|
|
129
|
+
[TodoStatus.TODO_IN_PROGRESS]: TodoInProgressIcon,
|
|
130
|
+
[TodoStatus.TODO_COMPLETED]: TodoCompletedIcon,
|
|
131
|
+
[TodoStatus.TODO_CANCELLED]: TodoCancelledIcon,
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
const TODO_COLORS: Partial<Record<TodoStatus, string>> = {
|
|
135
|
+
[TodoStatus.TODO_PENDING]: "text-muted-foreground",
|
|
136
|
+
[TodoStatus.TODO_IN_PROGRESS]: "text-foreground",
|
|
137
|
+
[TodoStatus.TODO_COMPLETED]: "text-success",
|
|
138
|
+
[TodoStatus.TODO_CANCELLED]: "text-muted-foreground",
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
// ---------------------------------------------------------------------------
|
|
142
|
+
// Inline SVG icons — no external icon dependency in SDK
|
|
143
|
+
// ---------------------------------------------------------------------------
|
|
144
|
+
|
|
145
|
+
function TodoPendingIcon() {
|
|
146
|
+
return (
|
|
147
|
+
<svg
|
|
148
|
+
width="12"
|
|
149
|
+
height="12"
|
|
150
|
+
viewBox="0 0 12 12"
|
|
151
|
+
fill="none"
|
|
152
|
+
stroke="currentColor"
|
|
153
|
+
strokeWidth="1.5"
|
|
154
|
+
>
|
|
155
|
+
<circle cx="6" cy="6" r="4.5" />
|
|
156
|
+
</svg>
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** Exported for reuse in SubAgentSection collapsed preview. */
|
|
161
|
+
export function TodoInProgressIcon() {
|
|
162
|
+
return (
|
|
163
|
+
<span className="relative flex h-3 w-3 items-center justify-center">
|
|
164
|
+
<span className="absolute inline-flex h-2 w-2 animate-ping rounded-full bg-current opacity-75" />
|
|
165
|
+
<span className="relative inline-flex h-2 w-2 rounded-full bg-current" />
|
|
166
|
+
</span>
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function TodoCompletedIcon() {
|
|
171
|
+
return (
|
|
172
|
+
<svg
|
|
173
|
+
width="12"
|
|
174
|
+
height="12"
|
|
175
|
+
viewBox="0 0 12 12"
|
|
176
|
+
fill="none"
|
|
177
|
+
stroke="currentColor"
|
|
178
|
+
strokeWidth="2"
|
|
179
|
+
strokeLinecap="round"
|
|
180
|
+
strokeLinejoin="round"
|
|
181
|
+
>
|
|
182
|
+
<path d="M2.5 6L5 8.5L9.5 3.5" />
|
|
183
|
+
</svg>
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function TodoCancelledIcon() {
|
|
188
|
+
return (
|
|
189
|
+
<svg
|
|
190
|
+
width="12"
|
|
191
|
+
height="12"
|
|
192
|
+
viewBox="0 0 12 12"
|
|
193
|
+
fill="none"
|
|
194
|
+
stroke="currentColor"
|
|
195
|
+
strokeWidth="2"
|
|
196
|
+
strokeLinecap="round"
|
|
197
|
+
strokeLinejoin="round"
|
|
198
|
+
>
|
|
199
|
+
<path d="M3 3L9 9M9 3L3 9" />
|
|
200
|
+
</svg>
|
|
201
|
+
);
|
|
202
|
+
}
|
|
@@ -187,7 +187,7 @@ export function ToolCallItem({
|
|
|
187
187
|
{expanded && (
|
|
188
188
|
<div className="px-2.5 pb-2.5 pt-1">
|
|
189
189
|
{isSubAgent ? (
|
|
190
|
-
<SubAgentSection subAgentExecution={subAgentExecution} />
|
|
190
|
+
<SubAgentSection subAgentExecution={subAgentExecution} collapsible={false} />
|
|
191
191
|
) : (
|
|
192
192
|
<ToolCallDetail toolCall={toolCall} />
|
|
193
193
|
)}
|