@flanksource/clicky-ui 0.3.35 → 0.3.36
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/dist/ai.cjs +2 -1
- package/dist/ai.cjs.map +1 -1
- package/dist/ai.js +2 -1
- package/dist/ai.js.map +1 -1
- package/dist/data/TaskHeaderExtras.cjs +35 -0
- package/dist/data/TaskHeaderExtras.cjs.map +1 -0
- package/dist/data/TaskHeaderExtras.d.ts +27 -0
- package/dist/data/TaskHeaderExtras.d.ts.map +1 -0
- package/dist/data/TaskHeaderExtras.js +35 -0
- package/dist/data/TaskHeaderExtras.js.map +1 -0
- package/dist/data/TaskManager.cjs +18 -6
- package/dist/data/TaskManager.cjs.map +1 -1
- package/dist/data/TaskManager.d.ts +13 -1
- package/dist/data/TaskManager.d.ts.map +1 -1
- package/dist/data/TaskManager.js +18 -6
- package/dist/data/TaskManager.js.map +1 -1
- package/dist/data/TaskProcessDetails.cjs +4 -1
- package/dist/data/TaskProcessDetails.cjs.map +1 -1
- package/dist/data/TaskProcessDetails.d.ts.map +1 -1
- package/dist/data/TaskProcessDetails.js +4 -1
- package/dist/data/TaskProcessDetails.js.map +1 -1
- package/dist/data/TaskProcessMetadata.cjs +50 -0
- package/dist/data/TaskProcessMetadata.cjs.map +1 -0
- package/dist/data/TaskProcessMetadata.d.ts +20 -0
- package/dist/data/TaskProcessMetadata.d.ts.map +1 -0
- package/dist/data/TaskProcessMetadata.js +50 -0
- package/dist/data/TaskProcessMetadata.js.map +1 -0
- package/dist/data/TaskProgress.cjs +22 -31
- package/dist/data/TaskProgress.cjs.map +1 -1
- package/dist/data/TaskProgress.d.ts +14 -1
- package/dist/data/TaskProgress.d.ts.map +1 -1
- package/dist/data/TaskProgress.js +22 -31
- package/dist/data/TaskProgress.js.map +1 -1
- package/dist/data/TaskSnapshot.cjs.map +1 -1
- package/dist/data/TaskSnapshot.d.ts +22 -0
- package/dist/data/TaskSnapshot.d.ts.map +1 -1
- package/dist/data/TaskSnapshot.js.map +1 -1
- package/dist/data/TaskStreamTabs.cjs +78 -0
- package/dist/data/TaskStreamTabs.cjs.map +1 -0
- package/dist/data/TaskStreamTabs.d.ts +35 -0
- package/dist/data/TaskStreamTabs.d.ts.map +1 -0
- package/dist/data/TaskStreamTabs.js +78 -0
- package/dist/data/TaskStreamTabs.js.map +1 -0
- package/dist/data/ai/SessionInspector.approvals-model.cjs +9 -0
- package/dist/data/ai/SessionInspector.approvals-model.cjs.map +1 -0
- package/dist/data/ai/SessionInspector.approvals-model.d.ts +17 -0
- package/dist/data/ai/SessionInspector.approvals-model.d.ts.map +1 -0
- package/dist/data/ai/SessionInspector.approvals-model.js +9 -0
- package/dist/data/ai/SessionInspector.approvals-model.js.map +1 -0
- package/dist/data/ai/SessionInspector.approvals.cjs +7 -11
- package/dist/data/ai/SessionInspector.approvals.cjs.map +1 -1
- package/dist/data/ai/SessionInspector.approvals.d.ts +2 -10
- package/dist/data/ai/SessionInspector.approvals.d.ts.map +1 -1
- package/dist/data/ai/SessionInspector.approvals.js +4 -8
- package/dist/data/ai/SessionInspector.approvals.js.map +1 -1
- package/dist/data/ai/SessionInspector.cjs +2 -2
- package/dist/data/ai/SessionInspector.cjs.map +1 -1
- package/dist/data/ai/SessionInspector.d.ts +1 -1
- package/dist/data/ai/SessionInspector.d.ts.map +1 -1
- package/dist/data/ai/SessionInspector.js +1 -1
- package/dist/data/ai/SessionInspector.js.map +1 -1
- package/dist/data/ai/SessionInspector.panel-parts.cjs +0 -13
- package/dist/data/ai/SessionInspector.panel-parts.cjs.map +1 -1
- package/dist/data/ai/SessionInspector.panel-parts.d.ts +4 -5
- package/dist/data/ai/SessionInspector.panel-parts.d.ts.map +1 -1
- package/dist/data/ai/SessionInspector.panel-parts.js +1 -14
- package/dist/data/ai/SessionInspector.panel-parts.js.map +1 -1
- package/dist/data/ai/SessionInspector.panel-values.cjs +18 -0
- package/dist/data/ai/SessionInspector.panel-values.cjs.map +1 -0
- package/dist/data/ai/SessionInspector.panel-values.d.ts +13 -0
- package/dist/data/ai/SessionInspector.panel-values.d.ts.map +1 -0
- package/dist/data/ai/SessionInspector.panel-values.js +18 -0
- package/dist/data/ai/SessionInspector.panel-values.js.map +1 -0
- package/dist/data/ai/SessionInspector.panels.cjs +27 -26
- package/dist/data/ai/SessionInspector.panels.cjs.map +1 -1
- package/dist/data/ai/SessionInspector.panels.d.ts.map +1 -1
- package/dist/data/ai/SessionInspector.panels.js +2 -1
- package/dist/data/ai/SessionInspector.panels.js.map +1 -1
- package/dist/data/ai/index.d.ts +2 -1
- package/dist/data/ai/index.d.ts.map +1 -1
- package/dist/data/task-metadata.cjs +36 -0
- package/dist/data/task-metadata.cjs.map +1 -0
- package/dist/data/task-metadata.d.ts +39 -0
- package/dist/data/task-metadata.d.ts.map +1 -0
- package/dist/data/task-metadata.js +36 -0
- package/dist/data/task-metadata.js.map +1 -0
- package/dist/data/task-streams.cjs +39 -0
- package/dist/data/task-streams.cjs.map +1 -0
- package/dist/data/task-streams.d.ts +60 -0
- package/dist/data/task-streams.d.ts.map +1 -0
- package/dist/data/task-streams.js +39 -0
- package/dist/data/task-streams.js.map +1 -0
- package/dist/data/version-info.cjs +3 -3
- package/dist/data/version-info.js +3 -3
- package/dist/data.cjs +14 -0
- package/dist/data.cjs.map +1 -1
- package/dist/data.d.ts +6 -1
- package/dist/data.d.ts.map +1 -1
- package/dist/data.js +14 -0
- package/dist/data.js.map +1 -1
- package/dist/hooks/use-task-run.cjs +13 -15
- package/dist/hooks/use-task-run.cjs.map +1 -1
- package/dist/hooks/use-task-run.d.ts.map +1 -1
- package/dist/hooks/use-task-run.js +14 -16
- package/dist/hooks/use-task-run.js.map +1 -1
- package/dist/index.cjs +14 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -1
- package/dist/styles.css +1 -1
- package/package.json +1 -1
|
@@ -6,11 +6,12 @@ import { CopyButton } from "../components/CopyButton.js";
|
|
|
6
6
|
import { SplitButton } from "../components/SplitButton.js";
|
|
7
7
|
import { useCopyFlash } from "../components/clipboard.js";
|
|
8
8
|
import { cn } from "../lib/utils.js";
|
|
9
|
-
import { AnsiHtml } from "./AnsiHtml.js";
|
|
10
9
|
import { Icon } from "./Icon.js";
|
|
11
10
|
import { ProgressBar } from "./ProgressBar.js";
|
|
12
11
|
import { TaskProcessDetailsView } from "./TaskProcessDetails.js";
|
|
13
12
|
import { TaskExecDetailsView } from "./TaskExecDetails.js";
|
|
13
|
+
import { TaskMetadataChips } from "./TaskHeaderExtras.js";
|
|
14
|
+
import { TaskStreamTabs } from "./TaskStreamTabs.js";
|
|
14
15
|
import { isFailedOrWarn, taskGroupJson, taskGroupErrors, taskGroupErrorCount, taskGroupMarkdown, taskMarkdown } from "./task-copy.js";
|
|
15
16
|
import { isTaskExecDetails } from "./task-exec-details.js";
|
|
16
17
|
import { isTaskProcessDetails } from "./task-process-details.js";
|
|
@@ -32,7 +33,9 @@ function TaskProgress({
|
|
|
32
33
|
onControl,
|
|
33
34
|
onTaskControl,
|
|
34
35
|
metricsBaseUrl,
|
|
35
|
-
connectionStatus
|
|
36
|
+
connectionStatus,
|
|
37
|
+
extraTabs,
|
|
38
|
+
headerExtra
|
|
36
39
|
}) {
|
|
37
40
|
const groups = snapshots.filter((s) => s.type === "group");
|
|
38
41
|
const tasks = snapshots.filter((s) => s.type === "task");
|
|
@@ -53,7 +56,9 @@ function TaskProgress({
|
|
|
53
56
|
...onControl ? { onControl } : {},
|
|
54
57
|
...onTaskControl ? { onTaskControl } : {},
|
|
55
58
|
...metricsBaseUrl ? { metricsBaseUrl } : {},
|
|
56
|
-
...connectionStatus ? { connectionStatus } : {}
|
|
59
|
+
...connectionStatus ? { connectionStatus } : {},
|
|
60
|
+
...extraTabs ? { extraTabs } : {},
|
|
61
|
+
...headerExtra ? { headerExtra } : {}
|
|
57
62
|
},
|
|
58
63
|
g.groupId || g.id
|
|
59
64
|
))
|
|
@@ -66,7 +71,9 @@ function TaskGroupCard({
|
|
|
66
71
|
onControl,
|
|
67
72
|
onTaskControl,
|
|
68
73
|
metricsBaseUrl,
|
|
69
|
-
connectionStatus
|
|
74
|
+
connectionStatus,
|
|
75
|
+
extraTabs,
|
|
76
|
+
headerExtra
|
|
70
77
|
}) {
|
|
71
78
|
const [showAll, setShowAll] = useState(false);
|
|
72
79
|
const { state: copyState, copy } = useCopyFlash();
|
|
@@ -171,7 +178,9 @@ function TaskGroupCard({
|
|
|
171
178
|
group: g,
|
|
172
179
|
...onTaskControl ? { onTaskControl } : {},
|
|
173
180
|
...metricsBaseUrl ? { metricsBaseUrl } : {},
|
|
174
|
-
...connectionStatus ? { connectionStatus } : {}
|
|
181
|
+
...connectionStatus ? { connectionStatus } : {},
|
|
182
|
+
...extraTabs ? { extraTabs } : {},
|
|
183
|
+
...headerExtra ? { headerExtra } : {}
|
|
175
184
|
},
|
|
176
185
|
t.id
|
|
177
186
|
))
|
|
@@ -182,12 +191,16 @@ function TaskRow({
|
|
|
182
191
|
group,
|
|
183
192
|
onTaskControl,
|
|
184
193
|
metricsBaseUrl,
|
|
185
|
-
connectionStatus
|
|
194
|
+
connectionStatus,
|
|
195
|
+
extraTabs: contributeTabs,
|
|
196
|
+
headerExtra
|
|
186
197
|
}) {
|
|
187
198
|
const [expanded, setExpanded] = useState(false);
|
|
188
199
|
const logs = t.logs ?? [];
|
|
189
200
|
const hasLogs = logs.length > 0;
|
|
190
|
-
const
|
|
201
|
+
const extraTabs = (contributeTabs == null ? void 0 : contributeTabs(t, group)) ?? [];
|
|
202
|
+
const header = (headerExtra == null ? void 0 : headerExtra(t, group)) ?? /* @__PURE__ */ jsx(TaskMetadataChips, { task: t });
|
|
203
|
+
const hasOutput = !!t.stdout || !!t.stderr || extraTabs.length > 0;
|
|
191
204
|
const execDetails = isTaskExecDetails(t.details) ? t.details : void 0;
|
|
192
205
|
const processDetails = isTaskProcessDetails(t.details) ? t.details : void 0;
|
|
193
206
|
const expandable = hasLogs || hasOutput || execDetails !== void 0 || processDetails !== void 0;
|
|
@@ -219,6 +232,7 @@ function TaskRow({
|
|
|
219
232
|
latestWarn && /* @__PURE__ */ jsx("span", { className: cn("truncate text-xs font-normal", logLevelColor("warn")), children: latestWarn.message }),
|
|
220
233
|
expandable && /* @__PURE__ */ jsx("span", { className: "inline-flex shrink-0 items-center rounded-full bg-muted px-1.5 text-[10px] font-medium text-muted-foreground", children: logs.length + (t.stdout ? 1 : 0) + (t.stderr ? 1 : 0) + (execDetails || processDetails ? 1 : 0) })
|
|
221
234
|
] }),
|
|
235
|
+
header,
|
|
222
236
|
/* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-1", children: [
|
|
223
237
|
t.duration && /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: t.duration }),
|
|
224
238
|
/* @__PURE__ */ jsx(
|
|
@@ -273,35 +287,12 @@ function TaskRow({
|
|
|
273
287
|
/* @__PURE__ */ jsx("span", { className: "mr-1 font-mono text-muted-foreground", children: l.level.substring(0, 3) }),
|
|
274
288
|
l.message
|
|
275
289
|
] }, i)) }),
|
|
276
|
-
expanded &&
|
|
277
|
-
t.stdout && /* @__PURE__ */ jsx(TaskStream, { label: "stdout", text: t.stdout, ...t.stdoutTruncated ? { truncated: true } : {} }),
|
|
278
|
-
t.stderr && /* @__PURE__ */ jsx(TaskStream, { label: "stderr", text: t.stderr, ...t.stderrTruncated ? { truncated: true } : {}, error: true })
|
|
279
|
-
] })
|
|
290
|
+
expanded && /* @__PURE__ */ jsx(TaskStreamTabs, { task: t, ...extraTabs.length > 0 ? { extraTabs } : {} })
|
|
280
291
|
] })
|
|
281
292
|
]
|
|
282
293
|
}
|
|
283
294
|
);
|
|
284
295
|
}
|
|
285
|
-
function TaskStream({
|
|
286
|
-
label,
|
|
287
|
-
text,
|
|
288
|
-
truncated,
|
|
289
|
-
error
|
|
290
|
-
}) {
|
|
291
|
-
return /* @__PURE__ */ jsxs("div", { className: "overflow-hidden rounded border bg-black", children: [
|
|
292
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between border-b border-white/10 px-2 py-1 text-[10px] text-gray-400", children: [
|
|
293
|
-
/* @__PURE__ */ jsx("span", { children: label }),
|
|
294
|
-
truncated && /* @__PURE__ */ jsx("span", { children: "showing latest 1 MiB" })
|
|
295
|
-
] }),
|
|
296
|
-
/* @__PURE__ */ jsx(
|
|
297
|
-
AnsiHtml,
|
|
298
|
-
{
|
|
299
|
-
text,
|
|
300
|
-
className: cn("max-h-64 overflow-auto whitespace-pre-wrap p-2 text-xs text-gray-100", error && "text-red-300")
|
|
301
|
-
}
|
|
302
|
-
)
|
|
303
|
-
] });
|
|
304
|
-
}
|
|
305
296
|
function TaskControls({
|
|
306
297
|
target,
|
|
307
298
|
onControl,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TaskProgress.js","sources":["../../src/data/TaskProgress.tsx"],"sourcesContent":["import { taskWorkProgress } from \"./task-work-progress\";\nimport { useState } from \"react\";\nimport { Button } from \"../components/button\";\nimport { CopyButton } from \"../components/CopyButton\";\nimport { SplitButton } from \"../components/SplitButton\";\nimport { useCopyFlash } from \"../components/clipboard\";\nimport { UiCheck, UiCopy, UiJson, UiPlay, UiRestart, UiStop, UiWarningTriangle } from \"../icons\";\nimport { cn } from \"../lib/utils\";\nimport type { DropdownMenuItem } from \"../overlay/DropdownMenu\";\nimport { AnsiHtml } from \"./AnsiHtml\";\nimport { Icon } from \"./Icon\";\nimport { ProgressBar } from \"./ProgressBar\";\nimport { TaskProcessDetailsView } from \"./TaskProcessDetails\";\nimport { TaskExecDetailsView } from \"./TaskExecDetails\";\nimport type { LogEntry, TaskControlAction, TaskSnapshot } from \"./TaskSnapshot\";\nimport {\n isFailedOrWarn,\n taskGroupErrorCount,\n taskGroupErrors,\n taskGroupJson,\n taskGroupMarkdown,\n taskMarkdown,\n} from \"./task-copy\";\nimport { isTaskExecDetails } from \"./task-exec-details\";\nimport { isTaskProcessDetails } from \"./task-process-details\";\nimport {\n bucketTasks,\n logLevelColor,\n taskSegments,\n taskStatusBg,\n taskStatusColor,\n taskStatusIcon,\n} from \"./task-status\";\n\n// TaskProgress renders one or more clicky task runs (groups) and their child\n// tasks: a segmented progress bar per group plus collapsible per-task rows with\n// status icon, duration, error, and expandable logs. Presentational only —\n// callers feed it snapshots from useTaskRun (SSE) or any other source.\n\nconst MAX_COMPLETED = 5;\nconst MAX_PENDING = 3;\n\nexport interface TaskProgressProps {\n snapshots: TaskSnapshot[];\n title?: string;\n /** Tighter spacing for embedding in a panel. */\n compact?: boolean;\n className?: string;\n onControl?: (action: TaskControlAction, group: TaskSnapshot) => void | Promise<void>;\n onTaskControl?: (\n action: TaskControlAction,\n task: TaskSnapshot,\n group: TaskSnapshot,\n ) => void | Promise<void>;\n metricsBaseUrl?: string;\n connectionStatus?: string;\n}\n\nexport function TaskProgress({\n snapshots,\n title,\n compact,\n className,\n onControl,\n onTaskControl,\n metricsBaseUrl,\n connectionStatus,\n}: TaskProgressProps) {\n const groups = snapshots.filter((s) => s.type === \"group\");\n const tasks = snapshots.filter((s) => s.type === \"task\");\n\n if (groups.length === 0) {\n return (\n <div className={cn(\"text-sm text-muted-foreground\", className)}>\n {title ? `${title}: ` : \"\"}No tasks yet.\n </div>\n );\n }\n\n return (\n <div className={cn(compact ? \"space-y-2\" : \"space-y-3\", className)}>\n {title && <h2 className=\"text-sm font-semibold\">{title}</h2>}\n {groups.map((g) => (\n <TaskGroupCard\n key={g.groupId || g.id}\n group={g}\n tasks={tasks.filter((t) => g.groupId ? t.groupId === g.groupId : t.group === g.id)}\n compact={compact}\n {...(onControl ? { onControl } : {})}\n {...(onTaskControl ? { onTaskControl } : {})}\n {...(metricsBaseUrl ? { metricsBaseUrl } : {})}\n {...(connectionStatus ? { connectionStatus } : {})}\n />\n ))}\n </div>\n );\n}\n\nfunction TaskGroupCard({\n group: g,\n tasks,\n compact,\n onControl,\n onTaskControl,\n metricsBaseUrl,\n connectionStatus,\n}: {\n group: TaskSnapshot;\n tasks: TaskSnapshot[];\n compact: boolean | undefined;\n onControl?: (action: TaskControlAction, group: TaskSnapshot) => void | Promise<void>;\n onTaskControl?: (\n action: TaskControlAction,\n task: TaskSnapshot,\n group: TaskSnapshot,\n ) => void | Promise<void>;\n metricsBaseUrl?: string;\n connectionStatus?: string;\n}) {\n const [showAll, setShowAll] = useState(false);\n const { state: copyState, copy } = useCopyFlash();\n const work = taskWorkProgress(g);\n const total = g.work ? work.total : g.total ?? tasks.length;\n const counts = g.work ? work.counts : bucketTasks(tasks);\n const done = counts.ok + counts.warn + counts.fail + counts.canceled;\n const progress = g.work ? work.label : total > 0 ? `${done}/${total}` : \"\";\n\n const running = tasks.filter((t) => t.status === \"running\");\n const pending = tasks.filter((t) => t.status === \"pending\");\n const completed = tasks.filter((t) => t.status !== \"running\" && t.status !== \"pending\");\n const alwaysShow = completed.filter(isFailedOrWarn);\n const collapsible = completed.filter((t) => !isFailedOrWarn(t));\n\n const hiddenCompleted = collapsible.length - MAX_COMPLETED;\n const collapseCompleted = !showAll && hiddenCompleted > 0;\n const visibleSuccess = collapseCompleted ? collapsible.slice(-MAX_COMPLETED) : collapsible;\n\n const hiddenPending = pending.length - MAX_PENDING;\n const collapsePending = !showAll && hiddenPending > 0;\n const visiblePending = collapsePending ? pending.slice(0, MAX_PENDING) : pending;\n\n const isTerminal = g.status !== \"running\" && g.status !== \"pending\";\n\n const copyItems: DropdownMenuItem[] = [\n {\n label: \"Copy as JSON\",\n icon: UiJson,\n onSelect: () => copy(taskGroupJson(g, tasks)),\n },\n {\n label: \"Copy errors only\",\n icon: UiWarningTriangle,\n disabled: taskGroupErrorCount(tasks) === 0,\n onSelect: () => copy(taskGroupErrors(g, tasks)),\n },\n ];\n\n return (\n <div className={cn(\"rounded-lg border bg-card\", compact ? \"p-3\" : \"p-4\")}>\n <div className=\"mb-3 flex items-center justify-between gap-3\">\n <h3 className=\"flex min-w-0 items-center gap-1.5 text-sm font-semibold\">\n <Icon\n icon={taskStatusIcon(g.status)}\n className={cn(\n \"shrink-0\",\n taskStatusColor(g.status),\n g.status === \"running\" && \"animate-spin\",\n )}\n />\n <span className=\"truncate\">{g.name}</span>\n {progress && (\n <span className=\"shrink-0 text-xs text-muted-foreground\">{progress}</span>\n )}\n {g.kind && (\n <span className=\"shrink-0 rounded-full bg-muted px-1.5 py-0 text-[10px] text-muted-foreground\">\n {g.kind}\n </span>\n )}\n </h3>\n <div className=\"flex shrink-0 items-center gap-1\">\n {isTerminal && (\n <span\n className={cn(\n \"rounded-full px-2 py-0.5 text-xs\",\n taskStatusColor(g.status),\n taskStatusBg(g.status),\n )}\n >\n {g.status}\n </span>\n )}\n <SplitButton\n label={copyState === \"copied\" ? \"Copied\" : copyState === \"error\" ? \"Copy failed\" : \"Copy\"}\n icon={copyState === \"copied\" ? UiCheck : UiCopy}\n variant=\"ghost\"\n size=\"sm\"\n title=\"More copy options\"\n onClick={() => copy(taskGroupMarkdown(g, tasks))}\n items={copyItems}\n />\n {onControl && <TaskControls target={g} onControl={onControl} />}\n </div>\n </div>\n\n {total > 0 && (\n <div className=\"mb-3\">\n <ProgressBar segments={taskSegments(counts)} total={total} height=\"h-1.5\" />\n </div>\n )}\n\n {isTaskProcessDetails(g.details) && (\n <TaskProcessDetailsView\n details={g.details}\n {...(metricsBaseUrl ? { metricsBaseUrl } : {})}\n {...(connectionStatus ? { connectionStatus } : {})}\n />\n )}\n\n {!isTaskProcessDetails(g.details) && isTaskExecDetails(g.details) && (\n <TaskExecDetailsView details={g.details} />\n )}\n\n {hiddenCompleted > 0 || hiddenPending > 0 ? (\n <button\n type=\"button\"\n className=\"border-b py-1.5 text-xs text-muted-foreground hover:text-foreground\"\n onClick={() => setShowAll((v) => !v)}\n >\n {showAll\n ? \"▲ collapse\"\n : `... ${(collapseCompleted ? hiddenCompleted : 0) + (collapsePending ? hiddenPending : 0)} more tasks`}\n </button>\n ) : null}\n\n {[...visibleSuccess, ...alwaysShow, ...running, ...visiblePending].map((t) => (\n <TaskRow\n key={t.id}\n task={t}\n group={g}\n {...(onTaskControl ? { onTaskControl } : {})}\n {...(metricsBaseUrl ? { metricsBaseUrl } : {})}\n {...(connectionStatus ? { connectionStatus } : {})}\n />\n ))}\n </div>\n );\n}\n\nfunction TaskRow({\n task: t,\n group,\n onTaskControl,\n metricsBaseUrl,\n connectionStatus,\n}: {\n task: TaskSnapshot;\n group: TaskSnapshot;\n metricsBaseUrl?: string;\n connectionStatus?: string;\n onTaskControl?: (\n action: TaskControlAction,\n task: TaskSnapshot,\n group: TaskSnapshot,\n ) => void | Promise<void>;\n}) {\n const [expanded, setExpanded] = useState(false);\n const logs: LogEntry[] = t.logs ?? [];\n const hasLogs = logs.length > 0;\n const hasOutput = !!t.stdout || !!t.stderr;\n const execDetails = isTaskExecDetails(t.details) ? t.details : undefined;\n const processDetails = isTaskProcessDetails(t.details) ? t.details : undefined;\n const expandable = hasLogs || hasOutput || execDetails !== undefined || processDetails !== undefined;\n // Promote the latest warning message inline so a `warning` row shows its\n // reason without expanding. Suppressed when an error is already shown.\n const latestWarn = t.error ? undefined : logs.filter((l) => l.level === \"warn\").at(-1);\n // Bounded per-task progress (e.g. records emitted in a phase): render an x/y\n // count, a percentage, and a thin bar while the task runs.\n const done = t.progress ?? 0;\n // Only surface bounded progress once work has actually started — an untouched\n // task should not render a \"0/100 · 0%\" placeholder.\n const hasProgress = typeof t.maxValue === \"number\" && t.maxValue > 0 && done > 0;\n const pct = hasProgress ? Math.round((done / (t.maxValue as number)) * 100) : 0;\n\n return (\n <div\n className={cn(\n \"group flex items-start gap-3 border-b py-2 last:border-0\",\n expandable && \"-mx-1 cursor-pointer rounded px-1 hover:bg-muted/50\",\n )}\n onClick={expandable ? () => setExpanded((v) => !v) : undefined}\n >\n <Icon\n icon={taskStatusIcon(t.status)}\n className={cn(taskStatusColor(t.status), \"mt-0.5 text-lg\", t.status === \"running\" && \"animate-spin\")}\n />\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex items-center justify-between gap-2\">\n <span className=\"flex min-w-0 items-center gap-1.5 text-sm font-medium\">\n <span className=\"truncate\">{t.name}</span>\n {t.error && <span className=\"truncate text-xs font-normal text-red-500\">{t.error}</span>}\n {latestWarn && (\n <span className={cn(\"truncate text-xs font-normal\", logLevelColor(\"warn\"))}>\n {latestWarn.message}\n </span>\n )}\n {expandable && (\n <span className=\"inline-flex shrink-0 items-center rounded-full bg-muted px-1.5 text-[10px] font-medium text-muted-foreground\">\n {logs.length + (t.stdout ? 1 : 0) + (t.stderr ? 1 : 0) + (execDetails || processDetails ? 1 : 0)}\n </span>\n )}\n </span>\n <div className=\"flex shrink-0 items-center gap-1\">\n {t.duration && <span className=\"text-xs text-muted-foreground\">{t.duration}</span>}\n <CopyButton\n value={() => taskMarkdown(t)}\n label={`Copy ${t.name}`}\n className=\"opacity-0 transition-opacity group-hover:opacity-100 focus-visible:opacity-100\"\n iconClassName=\"text-xs\"\n />\n {onTaskControl && (\n <TaskControls\n target={t}\n labelSuffix={t.name}\n stopPropagation\n onControl={(action) => onTaskControl(action, t, group)}\n />\n )}\n </div>\n </div>\n {(t.description || hasProgress) && (\n <div className=\"mt-0.5 flex items-center gap-2\">\n {t.description && (\n <span className=\"min-w-0 flex-1 truncate text-xs text-muted-foreground\">{t.description}</span>\n )}\n {hasProgress && (\n <span className=\"shrink-0 text-xs tabular-nums text-muted-foreground\">\n {done}/{t.maxValue} · {pct}%\n </span>\n )}\n </div>\n )}\n {hasProgress && t.status === \"running\" && (\n <div className=\"mt-1\">\n <ProgressBar\n segments={[{ count: done, color: \"bg-blue-500\", label: \"done\" }]}\n total={t.maxValue as number}\n height=\"h-1\"\n />\n </div>\n )}\n {expanded && execDetails && <TaskExecDetailsView details={execDetails} />}\n {expanded && processDetails && (\n <TaskProcessDetailsView\n details={processDetails}\n {...(metricsBaseUrl ? { metricsBaseUrl } : {})}\n {...(connectionStatus ? { connectionStatus } : {})}\n />\n )}\n {expanded && hasLogs && (\n <div className=\"mt-1 ml-1 max-h-48 space-y-0.5 overflow-y-auto border-l-2 pl-2\">\n {logs.map((l, i) => (\n <div key={i} className={cn(\"text-xs\", logLevelColor(l.level))}>\n <span className=\"mr-1 font-mono text-muted-foreground\">{l.level.substring(0, 3)}</span>\n {l.message}\n </div>\n ))}\n </div>\n )}\n {expanded && hasOutput && (\n <div className=\"mt-2 space-y-2\">\n {t.stdout && <TaskStream label=\"stdout\" text={t.stdout} {...(t.stdoutTruncated ? { truncated: true } : {})} />}\n {t.stderr && <TaskStream label=\"stderr\" text={t.stderr} {...(t.stderrTruncated ? { truncated: true } : {})} error />}\n </div>\n )}\n </div>\n </div>\n );\n}\n\nfunction TaskStream({\n label,\n text,\n truncated,\n error,\n}: {\n label: string;\n text: string;\n truncated?: boolean;\n error?: boolean;\n}) {\n return (\n <div className=\"overflow-hidden rounded border bg-black\">\n <div className=\"flex items-center justify-between border-b border-white/10 px-2 py-1 text-[10px] text-gray-400\">\n <span>{label}</span>\n {truncated && <span>showing latest 1 MiB</span>}\n </div>\n <AnsiHtml\n text={text}\n className={cn(\"max-h-64 overflow-auto whitespace-pre-wrap p-2 text-xs text-gray-100\", error && \"text-red-300\")}\n />\n </div>\n );\n}\n\nfunction TaskControls({\n target,\n onControl,\n labelSuffix,\n stopPropagation,\n}: {\n target: TaskSnapshot;\n onControl: (action: TaskControlAction, target: TaskSnapshot) => void | Promise<void>;\n labelSuffix?: string;\n stopPropagation?: boolean;\n}) {\n const [busy, setBusy] = useState<TaskControlAction | null>(null);\n const [error, setError] = useState(\"\");\n const icons = { start: UiPlay, stop: UiStop, restart: UiRestart, drain: UiStop };\n const invoke = async (action: TaskControlAction) => {\n setBusy(action);\n setError(\"\");\n try {\n await onControl(action, target);\n } catch (cause) {\n setError(cause instanceof Error ? cause.message : `Failed to ${action} task`);\n } finally {\n setBusy(null);\n }\n };\n return (\n <>\n {(target.controls ?? []).map((action) => {\n const ControlIcon = icons[action];\n const actionLabel = action === \"drain\" ? \"Stop after current\" : action[0]?.toUpperCase() + action.slice(1);\n const label = labelSuffix ? `${actionLabel} ${labelSuffix}` : actionLabel;\n return (\n <Button\n key={action}\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n aria-label={label}\n title={label}\n disabled={busy !== null}\n onClick={(event) => {\n if (stopPropagation) event.stopPropagation();\n void invoke(action);\n }}\n >\n <ControlIcon />\n </Button>\n );\n })}\n {error && <span role=\"alert\" className=\"text-xs text-red-600\">{error}</span>}\n </>\n );\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"TaskProgress.js","sources":["../../src/data/TaskProgress.tsx"],"sourcesContent":["import { taskWorkProgress } from \"./task-work-progress\";\nimport { useState } from \"react\";\nimport { Button } from \"../components/button\";\nimport { CopyButton } from \"../components/CopyButton\";\nimport { SplitButton } from \"../components/SplitButton\";\nimport { useCopyFlash } from \"../components/clipboard\";\nimport { UiCheck, UiCopy, UiJson, UiPlay, UiRestart, UiStop, UiWarningTriangle } from \"../icons\";\nimport { cn } from \"../lib/utils\";\nimport type { DropdownMenuItem } from \"../overlay/DropdownMenu\";\nimport { Icon } from \"./Icon\";\nimport { ProgressBar } from \"./ProgressBar\";\nimport { TaskProcessDetailsView } from \"./TaskProcessDetails\";\nimport { TaskExecDetailsView } from \"./TaskExecDetails\";\nimport type { LogEntry, TaskControlAction, TaskSnapshot } from \"./TaskSnapshot\";\nimport { type TaskHeaderExtra, TaskMetadataChips } from \"./TaskHeaderExtras\";\nimport { type TaskExtraTabs, TaskStreamTabs } from \"./TaskStreamTabs\";\nimport {\n isFailedOrWarn,\n taskGroupErrorCount,\n taskGroupErrors,\n taskGroupJson,\n taskGroupMarkdown,\n taskMarkdown,\n} from \"./task-copy\";\nimport { isTaskExecDetails } from \"./task-exec-details\";\nimport { isTaskProcessDetails } from \"./task-process-details\";\nimport {\n bucketTasks,\n logLevelColor,\n taskSegments,\n taskStatusBg,\n taskStatusColor,\n taskStatusIcon,\n} from \"./task-status\";\n\n// TaskProgress renders one or more clicky task runs (groups) and their child\n// tasks: a segmented progress bar per group plus collapsible per-task rows with\n// status icon, duration, error, and expandable logs. Presentational only —\n// callers feed it snapshots from useTaskRun (SSE) or any other source.\n\nconst MAX_COMPLETED = 5;\nconst MAX_PENDING = 3;\n\nexport interface TaskProgressProps {\n snapshots: TaskSnapshot[];\n title?: string;\n /** Tighter spacing for embedding in a panel. */\n compact?: boolean;\n className?: string;\n onControl?: (action: TaskControlAction, group: TaskSnapshot) => void | Promise<void>;\n onTaskControl?: (\n action: TaskControlAction,\n task: TaskSnapshot,\n group: TaskSnapshot,\n ) => void | Promise<void>;\n metricsBaseUrl?: string;\n connectionStatus?: string;\n /**\n * Contributes host-owned output panes alongside a task's own stdout/stderr —\n * an agent's session transcript, say, which this module has no way to fetch.\n */\n extraTabs?: TaskExtraTabs;\n /**\n * Replaces the content beside a task's name in its header row. Returning null\n * for a task falls back to the default, which chips the scalar values of its\n * process metadata.\n */\n headerExtra?: TaskHeaderExtra;\n}\n\nexport function TaskProgress({\n snapshots,\n title,\n compact,\n className,\n onControl,\n onTaskControl,\n metricsBaseUrl,\n connectionStatus,\n extraTabs,\n headerExtra,\n}: TaskProgressProps) {\n const groups = snapshots.filter((s) => s.type === \"group\");\n const tasks = snapshots.filter((s) => s.type === \"task\");\n\n if (groups.length === 0) {\n return (\n <div className={cn(\"text-sm text-muted-foreground\", className)}>\n {title ? `${title}: ` : \"\"}No tasks yet.\n </div>\n );\n }\n\n return (\n <div className={cn(compact ? \"space-y-2\" : \"space-y-3\", className)}>\n {title && <h2 className=\"text-sm font-semibold\">{title}</h2>}\n {groups.map((g) => (\n <TaskGroupCard\n key={g.groupId || g.id}\n group={g}\n tasks={tasks.filter((t) => g.groupId ? t.groupId === g.groupId : t.group === g.id)}\n compact={compact}\n {...(onControl ? { onControl } : {})}\n {...(onTaskControl ? { onTaskControl } : {})}\n {...(metricsBaseUrl ? { metricsBaseUrl } : {})}\n {...(connectionStatus ? { connectionStatus } : {})}\n {...(extraTabs ? { extraTabs } : {})}\n {...(headerExtra ? { headerExtra } : {})}\n />\n ))}\n </div>\n );\n}\n\nfunction TaskGroupCard({\n group: g,\n tasks,\n compact,\n onControl,\n onTaskControl,\n metricsBaseUrl,\n connectionStatus,\n extraTabs,\n headerExtra,\n}: {\n group: TaskSnapshot;\n tasks: TaskSnapshot[];\n compact: boolean | undefined;\n extraTabs?: TaskExtraTabs;\n headerExtra?: TaskHeaderExtra;\n onControl?: (action: TaskControlAction, group: TaskSnapshot) => void | Promise<void>;\n onTaskControl?: (\n action: TaskControlAction,\n task: TaskSnapshot,\n group: TaskSnapshot,\n ) => void | Promise<void>;\n metricsBaseUrl?: string;\n connectionStatus?: string;\n}) {\n const [showAll, setShowAll] = useState(false);\n const { state: copyState, copy } = useCopyFlash();\n const work = taskWorkProgress(g);\n const total = g.work ? work.total : g.total ?? tasks.length;\n const counts = g.work ? work.counts : bucketTasks(tasks);\n const done = counts.ok + counts.warn + counts.fail + counts.canceled;\n const progress = g.work ? work.label : total > 0 ? `${done}/${total}` : \"\";\n\n const running = tasks.filter((t) => t.status === \"running\");\n const pending = tasks.filter((t) => t.status === \"pending\");\n const completed = tasks.filter((t) => t.status !== \"running\" && t.status !== \"pending\");\n const alwaysShow = completed.filter(isFailedOrWarn);\n const collapsible = completed.filter((t) => !isFailedOrWarn(t));\n\n const hiddenCompleted = collapsible.length - MAX_COMPLETED;\n const collapseCompleted = !showAll && hiddenCompleted > 0;\n const visibleSuccess = collapseCompleted ? collapsible.slice(-MAX_COMPLETED) : collapsible;\n\n const hiddenPending = pending.length - MAX_PENDING;\n const collapsePending = !showAll && hiddenPending > 0;\n const visiblePending = collapsePending ? pending.slice(0, MAX_PENDING) : pending;\n\n const isTerminal = g.status !== \"running\" && g.status !== \"pending\";\n\n const copyItems: DropdownMenuItem[] = [\n {\n label: \"Copy as JSON\",\n icon: UiJson,\n onSelect: () => copy(taskGroupJson(g, tasks)),\n },\n {\n label: \"Copy errors only\",\n icon: UiWarningTriangle,\n disabled: taskGroupErrorCount(tasks) === 0,\n onSelect: () => copy(taskGroupErrors(g, tasks)),\n },\n ];\n\n return (\n <div className={cn(\"rounded-lg border bg-card\", compact ? \"p-3\" : \"p-4\")}>\n <div className=\"mb-3 flex items-center justify-between gap-3\">\n <h3 className=\"flex min-w-0 items-center gap-1.5 text-sm font-semibold\">\n <Icon\n icon={taskStatusIcon(g.status)}\n className={cn(\n \"shrink-0\",\n taskStatusColor(g.status),\n g.status === \"running\" && \"animate-spin\",\n )}\n />\n <span className=\"truncate\">{g.name}</span>\n {progress && (\n <span className=\"shrink-0 text-xs text-muted-foreground\">{progress}</span>\n )}\n {g.kind && (\n <span className=\"shrink-0 rounded-full bg-muted px-1.5 py-0 text-[10px] text-muted-foreground\">\n {g.kind}\n </span>\n )}\n </h3>\n <div className=\"flex shrink-0 items-center gap-1\">\n {isTerminal && (\n <span\n className={cn(\n \"rounded-full px-2 py-0.5 text-xs\",\n taskStatusColor(g.status),\n taskStatusBg(g.status),\n )}\n >\n {g.status}\n </span>\n )}\n <SplitButton\n label={copyState === \"copied\" ? \"Copied\" : copyState === \"error\" ? \"Copy failed\" : \"Copy\"}\n icon={copyState === \"copied\" ? UiCheck : UiCopy}\n variant=\"ghost\"\n size=\"sm\"\n title=\"More copy options\"\n onClick={() => copy(taskGroupMarkdown(g, tasks))}\n items={copyItems}\n />\n {onControl && <TaskControls target={g} onControl={onControl} />}\n </div>\n </div>\n\n {total > 0 && (\n <div className=\"mb-3\">\n <ProgressBar segments={taskSegments(counts)} total={total} height=\"h-1.5\" />\n </div>\n )}\n\n {isTaskProcessDetails(g.details) && (\n <TaskProcessDetailsView\n details={g.details}\n {...(metricsBaseUrl ? { metricsBaseUrl } : {})}\n {...(connectionStatus ? { connectionStatus } : {})}\n />\n )}\n\n {!isTaskProcessDetails(g.details) && isTaskExecDetails(g.details) && (\n <TaskExecDetailsView details={g.details} />\n )}\n\n {hiddenCompleted > 0 || hiddenPending > 0 ? (\n <button\n type=\"button\"\n className=\"border-b py-1.5 text-xs text-muted-foreground hover:text-foreground\"\n onClick={() => setShowAll((v) => !v)}\n >\n {showAll\n ? \"▲ collapse\"\n : `... ${(collapseCompleted ? hiddenCompleted : 0) + (collapsePending ? hiddenPending : 0)} more tasks`}\n </button>\n ) : null}\n\n {[...visibleSuccess, ...alwaysShow, ...running, ...visiblePending].map((t) => (\n <TaskRow\n key={t.id}\n task={t}\n group={g}\n {...(onTaskControl ? { onTaskControl } : {})}\n {...(metricsBaseUrl ? { metricsBaseUrl } : {})}\n {...(connectionStatus ? { connectionStatus } : {})}\n {...(extraTabs ? { extraTabs } : {})}\n {...(headerExtra ? { headerExtra } : {})}\n />\n ))}\n </div>\n );\n}\n\nfunction TaskRow({\n task: t,\n group,\n onTaskControl,\n metricsBaseUrl,\n connectionStatus,\n extraTabs: contributeTabs,\n headerExtra,\n}: {\n task: TaskSnapshot;\n group: TaskSnapshot;\n metricsBaseUrl?: string;\n connectionStatus?: string;\n extraTabs?: TaskExtraTabs;\n headerExtra?: TaskHeaderExtra;\n onTaskControl?: (\n action: TaskControlAction,\n task: TaskSnapshot,\n group: TaskSnapshot,\n ) => void | Promise<void>;\n}) {\n const [expanded, setExpanded] = useState(false);\n const logs: LogEntry[] = t.logs ?? [];\n const hasLogs = logs.length > 0;\n const extraTabs = contributeTabs?.(t, group) ?? [];\n // A host that has nothing to add for this task returns null, and the default\n // metadata chips take over — so enriching one kind of task never costs the\n // rest their header.\n const header = headerExtra?.(t, group) ?? <TaskMetadataChips task={t} />;\n const hasOutput = !!t.stdout || !!t.stderr || extraTabs.length > 0;\n const execDetails = isTaskExecDetails(t.details) ? t.details : undefined;\n const processDetails = isTaskProcessDetails(t.details) ? t.details : undefined;\n const expandable = hasLogs || hasOutput || execDetails !== undefined || processDetails !== undefined;\n // Promote the latest warning message inline so a `warning` row shows its\n // reason without expanding. Suppressed when an error is already shown.\n const latestWarn = t.error ? undefined : logs.filter((l) => l.level === \"warn\").at(-1);\n // Bounded per-task progress (e.g. records emitted in a phase): render an x/y\n // count, a percentage, and a thin bar while the task runs.\n const done = t.progress ?? 0;\n // Only surface bounded progress once work has actually started — an untouched\n // task should not render a \"0/100 · 0%\" placeholder.\n const hasProgress = typeof t.maxValue === \"number\" && t.maxValue > 0 && done > 0;\n const pct = hasProgress ? Math.round((done / (t.maxValue as number)) * 100) : 0;\n\n return (\n <div\n className={cn(\n \"group flex items-start gap-3 border-b py-2 last:border-0\",\n expandable && \"-mx-1 cursor-pointer rounded px-1 hover:bg-muted/50\",\n )}\n onClick={expandable ? () => setExpanded((v) => !v) : undefined}\n >\n <Icon\n icon={taskStatusIcon(t.status)}\n className={cn(taskStatusColor(t.status), \"mt-0.5 text-lg\", t.status === \"running\" && \"animate-spin\")}\n />\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex items-center justify-between gap-2\">\n <span className=\"flex min-w-0 items-center gap-1.5 text-sm font-medium\">\n <span className=\"truncate\">{t.name}</span>\n {t.error && <span className=\"truncate text-xs font-normal text-red-500\">{t.error}</span>}\n {latestWarn && (\n <span className={cn(\"truncate text-xs font-normal\", logLevelColor(\"warn\"))}>\n {latestWarn.message}\n </span>\n )}\n {expandable && (\n <span className=\"inline-flex shrink-0 items-center rounded-full bg-muted px-1.5 text-[10px] font-medium text-muted-foreground\">\n {logs.length + (t.stdout ? 1 : 0) + (t.stderr ? 1 : 0) + (execDetails || processDetails ? 1 : 0)}\n </span>\n )}\n </span>\n {header}\n <div className=\"flex shrink-0 items-center gap-1\">\n {t.duration && <span className=\"text-xs text-muted-foreground\">{t.duration}</span>}\n <CopyButton\n value={() => taskMarkdown(t)}\n label={`Copy ${t.name}`}\n className=\"opacity-0 transition-opacity group-hover:opacity-100 focus-visible:opacity-100\"\n iconClassName=\"text-xs\"\n />\n {onTaskControl && (\n <TaskControls\n target={t}\n labelSuffix={t.name}\n stopPropagation\n onControl={(action) => onTaskControl(action, t, group)}\n />\n )}\n </div>\n </div>\n {(t.description || hasProgress) && (\n <div className=\"mt-0.5 flex items-center gap-2\">\n {t.description && (\n <span className=\"min-w-0 flex-1 truncate text-xs text-muted-foreground\">{t.description}</span>\n )}\n {hasProgress && (\n <span className=\"shrink-0 text-xs tabular-nums text-muted-foreground\">\n {done}/{t.maxValue} · {pct}%\n </span>\n )}\n </div>\n )}\n {hasProgress && t.status === \"running\" && (\n <div className=\"mt-1\">\n <ProgressBar\n segments={[{ count: done, color: \"bg-blue-500\", label: \"done\" }]}\n total={t.maxValue as number}\n height=\"h-1\"\n />\n </div>\n )}\n {expanded && execDetails && <TaskExecDetailsView details={execDetails} />}\n {expanded && processDetails && (\n <TaskProcessDetailsView\n details={processDetails}\n {...(metricsBaseUrl ? { metricsBaseUrl } : {})}\n {...(connectionStatus ? { connectionStatus } : {})}\n />\n )}\n {expanded && hasLogs && (\n <div className=\"mt-1 ml-1 max-h-48 space-y-0.5 overflow-y-auto border-l-2 pl-2\">\n {logs.map((l, i) => (\n <div key={i} className={cn(\"text-xs\", logLevelColor(l.level))}>\n <span className=\"mr-1 font-mono text-muted-foreground\">{l.level.substring(0, 3)}</span>\n {l.message}\n </div>\n ))}\n </div>\n )}\n {expanded && <TaskStreamTabs task={t} {...(extraTabs.length > 0 ? { extraTabs } : {})} />}\n </div>\n </div>\n );\n}\n\nfunction TaskControls({\n target,\n onControl,\n labelSuffix,\n stopPropagation,\n}: {\n target: TaskSnapshot;\n onControl: (action: TaskControlAction, target: TaskSnapshot) => void | Promise<void>;\n labelSuffix?: string;\n stopPropagation?: boolean;\n}) {\n const [busy, setBusy] = useState<TaskControlAction | null>(null);\n const [error, setError] = useState(\"\");\n const icons = { start: UiPlay, stop: UiStop, restart: UiRestart, drain: UiStop };\n const invoke = async (action: TaskControlAction) => {\n setBusy(action);\n setError(\"\");\n try {\n await onControl(action, target);\n } catch (cause) {\n setError(cause instanceof Error ? cause.message : `Failed to ${action} task`);\n } finally {\n setBusy(null);\n }\n };\n return (\n <>\n {(target.controls ?? []).map((action) => {\n const ControlIcon = icons[action];\n const actionLabel = action === \"drain\" ? \"Stop after current\" : action[0]?.toUpperCase() + action.slice(1);\n const label = labelSuffix ? `${actionLabel} ${labelSuffix}` : actionLabel;\n return (\n <Button\n key={action}\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n aria-label={label}\n title={label}\n disabled={busy !== null}\n onClick={(event) => {\n if (stopPropagation) event.stopPropagation();\n void invoke(action);\n }}\n >\n <ControlIcon />\n </Button>\n );\n })}\n {error && <span role=\"alert\" className=\"text-xs text-red-600\">{error}</span>}\n </>\n );\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TaskSnapshot.cjs","sources":["../../src/data/TaskSnapshot.ts"],"sourcesContent":["// TaskSnapshot mirrors the Go `task.TaskSnapshot` wire format emitted by clicky's\n// JSONHandler / SSEHandler (github.com/flanksource/clicky/task/snapshot.go). Field\n// names match the JSON tags exactly. This module is generic — it carries no\n// application-specific concepts.\n\nexport interface LogEntry {\n level: string;\n message: string;\n}\n\nexport type TaskControlAction = \"start\" | \"stop\" | \"restart\" | \"drain\";\n\nexport interface TaskWorkProgress {\n total: number;\n completed: number;\n cached: number;\n failed: number;\n running: number;\n canceled: number;\n unstarted: number;\n}\n\nexport interface TaskResourceSnapshot {\n cpuPercent: number;\n rssBytes: number;\n vmsBytes: number;\n openFiles: number;\n sampledAt: string;\n}\n\nexport interface TaskProcessSample {\n pid: number;\n ppid: number;\n command: string;\n status?: string;\n isRoot?: boolean;\n cpuPercent: number;\n rssBytes: number;\n vmsBytes: number;\n openFiles: number;\n}\n\nexport interface TaskProcessDetails {\n pid?: number;\n command: string;\n args?: string[];\n status: string;\n started?: string;\n exitCode?: number;\n ports?: number[];\n restarts: number;\n restartPolicy: string;\n maxRestarts?: number;\n latest: TaskResourceSnapshot;\n peak: TaskResourceSnapshot;\n metrics: Record<string, string>;\n tree?: TaskProcessSample[];\n}\n\nexport interface TaskExecDetails {\n command: string;\n args?: string[];\n pid?: number;\n status: string;\n exitCode: number;\n started?: string;\n /** Go time.Duration encoded as nanoseconds. */\n duration?: number;\n [key: string]: unknown;\n}\n\nexport interface TaskSnapshot {\n id: string;\n name: string;\n /** \"task\" or \"group\". */\n type: \"task\" | \"group\";\n /** Parent group NAME (groups: empty). */\n group?: string;\n status: string;\n duration?: string;\n error?: string;\n /** Latest log line. */\n message?: string;\n logs?: LogEntry[];\n /** Group aggregates. */\n total?: number;\n completed?: number;\n failed?: number;\n running?: number;\n canceled?: number;\n work?: TaskWorkProgress;\n /** Per-task (type === \"task\") live stage label and bounded progress. */\n description?: string;\n progress?: number;\n /** Progress denominator; 0/undefined means the task has no bounded progress. */\n maxValue?: number;\n /** Stable parent-run id (tasks) or the run's own id (groups). */\n groupId?: string;\n kind?: string;\n labels?: Record<string, string>;\n owner?: string;\n /** RFC3339. */\n startedAt?: string;\n /** RFC3339. */\n finishedAt?: string;\n href?: string;\n controls?: TaskControlAction[];\n stdout?: string;\n stderr?: string;\n stdoutTruncated?: boolean;\n stderrTruncated?: boolean;\n details?: TaskProcessDetails | TaskExecDetails | Record<string, unknown>;\n}\n\n// TaskRunMeta mirrors the Go `task.RunMeta` listing summary returned by\n// GET {prefix}/tasks. Named TaskRunMeta to avoid colliding with the unrelated\n// diagnostics `RunMeta` already exported by this package.\nexport interface TaskRunMeta {\n id: string;\n name: string;\n kind?: string;\n labels?: Record<string, string>;\n owner?: string;\n status: string;\n startedAt?: string;\n finishedAt?: string;\n total: number;\n completed: number;\n failed: number;\n running: number;\n canceled?: number;\n work?: TaskWorkProgress;\n href?: string;\n controls?: TaskControlAction[];\n}\n\n// Terminal run/group statuses (not running/pending). Used to decide when an SSE\n// stream is done or when polling should stop.\nconst TERMINAL_STATUSES = new Set([\n \"success\",\n \"failed\",\n \"warning\",\n \"canceled\",\n \"PASS\",\n \"FAIL\",\n \"ERR\",\n \"SKIP\",\n]);\n\nexport function isTerminalStatus(status: string | undefined): boolean {\n return !!status && TERMINAL_STATUSES.has(status);\n}\n\n// allGroupsTerminal reports whether every group snapshot has reached a terminal\n// status — i.e. the run(s) are complete and an SSE stream / poll can stop.\nexport function allGroupsTerminal(snapshots: TaskSnapshot[]): boolean {\n const groups = snapshots.filter((s) => s.type === \"group\");\n if (groups.length === 0) return false;\n return groups.every((g) => isTerminalStatus(g.status));\n}\n"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"TaskSnapshot.cjs","sources":["../../src/data/TaskSnapshot.ts"],"sourcesContent":["// TaskSnapshot mirrors the Go `task.TaskSnapshot` wire format emitted by clicky's\n// JSONHandler / SSEHandler (github.com/flanksource/clicky/task/snapshot.go). Field\n// names match the JSON tags exactly. This module is generic — it carries no\n// application-specific concepts.\n\nexport interface LogEntry {\n level: string;\n message: string;\n}\n\nexport type TaskControlAction = \"start\" | \"stop\" | \"restart\" | \"drain\";\n\nexport interface TaskWorkProgress {\n total: number;\n completed: number;\n cached: number;\n failed: number;\n running: number;\n canceled: number;\n unstarted: number;\n}\n\nexport interface TaskResourceSnapshot {\n cpuPercent: number;\n rssBytes: number;\n vmsBytes: number;\n openFiles: number;\n sampledAt: string;\n}\n\nexport interface TaskProcessSample {\n pid: number;\n ppid: number;\n command: string;\n status?: string;\n isRoot?: boolean;\n cpuPercent: number;\n rssBytes: number;\n vmsBytes: number;\n openFiles: number;\n}\n\n/** Runaway bounds a supervised process is killed for exceeding. */\nexport interface TaskResourceLimits {\n maxRssBytes?: number;\n maxCpuPercent?: number;\n cpuSampleCount?: number;\n /** Sampling cadence as a Go duration string, e.g. \"2s\". */\n interval?: string;\n}\n\nexport interface TaskProcessDetails {\n pid?: number;\n command: string;\n args?: string[];\n status: string;\n started?: string;\n exitCode?: number;\n ports?: number[];\n restarts: number;\n restartPolicy: string;\n maxRestarts?: number;\n limits?: TaskResourceLimits;\n latest: TaskResourceSnapshot;\n peak: TaskResourceSnapshot;\n metrics: Record<string, string>;\n tree?: TaskProcessSample[];\n /**\n * Caller-supplied context, re-read on every snapshot. Any JSON the producer\n * chooses to send — flat values render inline, anything structured renders as\n * JSON. This module gives the shape no meaning beyond \"show it\".\n */\n metadata?: unknown;\n}\n\nexport interface TaskExecDetails {\n command: string;\n args?: string[];\n pid?: number;\n status: string;\n exitCode: number;\n started?: string;\n /** Go time.Duration encoded as nanoseconds. */\n duration?: number;\n [key: string]: unknown;\n}\n\nexport interface TaskSnapshot {\n id: string;\n name: string;\n /** \"task\" or \"group\". */\n type: \"task\" | \"group\";\n /** Parent group NAME (groups: empty). */\n group?: string;\n status: string;\n duration?: string;\n error?: string;\n /** Latest log line. */\n message?: string;\n logs?: LogEntry[];\n /** Group aggregates. */\n total?: number;\n completed?: number;\n failed?: number;\n running?: number;\n canceled?: number;\n work?: TaskWorkProgress;\n /** Per-task (type === \"task\") live stage label and bounded progress. */\n description?: string;\n progress?: number;\n /** Progress denominator; 0/undefined means the task has no bounded progress. */\n maxValue?: number;\n /** Stable parent-run id (tasks) or the run's own id (groups). */\n groupId?: string;\n kind?: string;\n labels?: Record<string, string>;\n owner?: string;\n /** RFC3339. */\n startedAt?: string;\n /** RFC3339. */\n finishedAt?: string;\n href?: string;\n controls?: TaskControlAction[];\n stdout?: string;\n stderr?: string;\n /**\n * Absolute stream offset of the first retained byte — how much the server's\n * bounded buffer has already discarded ahead of `stdout`/`stderr`. Non-zero\n * exactly when the matching `*Truncated` flag is set.\n */\n stdoutOffset?: number;\n stderrOffset?: number;\n stdoutTruncated?: boolean;\n stderrTruncated?: boolean;\n details?: TaskProcessDetails | TaskExecDetails | Record<string, unknown>;\n}\n\n// TaskRunMeta mirrors the Go `task.RunMeta` listing summary returned by\n// GET {prefix}/tasks. Named TaskRunMeta to avoid colliding with the unrelated\n// diagnostics `RunMeta` already exported by this package.\nexport interface TaskRunMeta {\n id: string;\n name: string;\n kind?: string;\n labels?: Record<string, string>;\n owner?: string;\n status: string;\n startedAt?: string;\n finishedAt?: string;\n total: number;\n completed: number;\n failed: number;\n running: number;\n canceled?: number;\n work?: TaskWorkProgress;\n href?: string;\n controls?: TaskControlAction[];\n}\n\n// Terminal run/group statuses (not running/pending). Used to decide when an SSE\n// stream is done or when polling should stop.\nconst TERMINAL_STATUSES = new Set([\n \"success\",\n \"failed\",\n \"warning\",\n \"canceled\",\n \"PASS\",\n \"FAIL\",\n \"ERR\",\n \"SKIP\",\n]);\n\nexport function isTerminalStatus(status: string | undefined): boolean {\n return !!status && TERMINAL_STATUSES.has(status);\n}\n\n// allGroupsTerminal reports whether every group snapshot has reached a terminal\n// status — i.e. the run(s) are complete and an SSE stream / poll can stop.\nexport function allGroupsTerminal(snapshots: TaskSnapshot[]): boolean {\n const groups = snapshots.filter((s) => s.type === \"group\");\n if (groups.length === 0) return false;\n return groups.every((g) => isTerminalStatus(g.status));\n}\n"],"names":[],"mappings":";;AAiKA,MAAM,wCAAwB,IAAI;AAAA,EAChC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAEM,SAAS,iBAAiB,QAAqC;AACpE,SAAO,CAAC,CAAC,UAAU,kBAAkB,IAAI,MAAM;AACjD;AAIO,SAAS,kBAAkB,WAAoC;AACpE,QAAM,SAAS,UAAU,OAAO,CAAC,MAAM,EAAE,SAAS,OAAO;AACzD,MAAI,OAAO,WAAW,EAAG,QAAO;AAChC,SAAO,OAAO,MAAM,CAAC,MAAM,iBAAiB,EAAE,MAAM,CAAC;AACvD;;;"}
|
|
@@ -30,6 +30,14 @@ export interface TaskProcessSample {
|
|
|
30
30
|
vmsBytes: number;
|
|
31
31
|
openFiles: number;
|
|
32
32
|
}
|
|
33
|
+
/** Runaway bounds a supervised process is killed for exceeding. */
|
|
34
|
+
export interface TaskResourceLimits {
|
|
35
|
+
maxRssBytes?: number;
|
|
36
|
+
maxCpuPercent?: number;
|
|
37
|
+
cpuSampleCount?: number;
|
|
38
|
+
/** Sampling cadence as a Go duration string, e.g. "2s". */
|
|
39
|
+
interval?: string;
|
|
40
|
+
}
|
|
33
41
|
export interface TaskProcessDetails {
|
|
34
42
|
pid?: number;
|
|
35
43
|
command: string;
|
|
@@ -41,10 +49,17 @@ export interface TaskProcessDetails {
|
|
|
41
49
|
restarts: number;
|
|
42
50
|
restartPolicy: string;
|
|
43
51
|
maxRestarts?: number;
|
|
52
|
+
limits?: TaskResourceLimits;
|
|
44
53
|
latest: TaskResourceSnapshot;
|
|
45
54
|
peak: TaskResourceSnapshot;
|
|
46
55
|
metrics: Record<string, string>;
|
|
47
56
|
tree?: TaskProcessSample[];
|
|
57
|
+
/**
|
|
58
|
+
* Caller-supplied context, re-read on every snapshot. Any JSON the producer
|
|
59
|
+
* chooses to send — flat values render inline, anything structured renders as
|
|
60
|
+
* JSON. This module gives the shape no meaning beyond "show it".
|
|
61
|
+
*/
|
|
62
|
+
metadata?: unknown;
|
|
48
63
|
}
|
|
49
64
|
export interface TaskExecDetails {
|
|
50
65
|
command: string;
|
|
@@ -95,6 +110,13 @@ export interface TaskSnapshot {
|
|
|
95
110
|
controls?: TaskControlAction[];
|
|
96
111
|
stdout?: string;
|
|
97
112
|
stderr?: string;
|
|
113
|
+
/**
|
|
114
|
+
* Absolute stream offset of the first retained byte — how much the server's
|
|
115
|
+
* bounded buffer has already discarded ahead of `stdout`/`stderr`. Non-zero
|
|
116
|
+
* exactly when the matching `*Truncated` flag is set.
|
|
117
|
+
*/
|
|
118
|
+
stdoutOffset?: number;
|
|
119
|
+
stderrOffset?: number;
|
|
98
120
|
stdoutTruncated?: boolean;
|
|
99
121
|
stderrTruncated?: boolean;
|
|
100
122
|
details?: TaskProcessDetails | TaskExecDetails | Record<string, unknown>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TaskSnapshot.d.ts","sourceRoot":"","sources":["../../src/data/TaskSnapshot.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,QAAQ;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,MAAM,iBAAiB,GAAG,OAAO,GAAG,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC;AAEvE,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,oBAAoB;IACnC,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,iBAAiB;IAChC,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,kBAAkB;IACjC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,oBAAoB,CAAC;IAC7B,IAAI,EAAE,oBAAoB,CAAC;IAC3B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,IAAI,CAAC,EAAE,iBAAiB,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"TaskSnapshot.d.ts","sourceRoot":"","sources":["../../src/data/TaskSnapshot.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TaskSnapshot.js","sources":["../../src/data/TaskSnapshot.ts"],"sourcesContent":["// TaskSnapshot mirrors the Go `task.TaskSnapshot` wire format emitted by clicky's\n// JSONHandler / SSEHandler (github.com/flanksource/clicky/task/snapshot.go). Field\n// names match the JSON tags exactly. This module is generic — it carries no\n// application-specific concepts.\n\nexport interface LogEntry {\n level: string;\n message: string;\n}\n\nexport type TaskControlAction = \"start\" | \"stop\" | \"restart\" | \"drain\";\n\nexport interface TaskWorkProgress {\n total: number;\n completed: number;\n cached: number;\n failed: number;\n running: number;\n canceled: number;\n unstarted: number;\n}\n\nexport interface TaskResourceSnapshot {\n cpuPercent: number;\n rssBytes: number;\n vmsBytes: number;\n openFiles: number;\n sampledAt: string;\n}\n\nexport interface TaskProcessSample {\n pid: number;\n ppid: number;\n command: string;\n status?: string;\n isRoot?: boolean;\n cpuPercent: number;\n rssBytes: number;\n vmsBytes: number;\n openFiles: number;\n}\n\nexport interface TaskProcessDetails {\n pid?: number;\n command: string;\n args?: string[];\n status: string;\n started?: string;\n exitCode?: number;\n ports?: number[];\n restarts: number;\n restartPolicy: string;\n maxRestarts?: number;\n latest: TaskResourceSnapshot;\n peak: TaskResourceSnapshot;\n metrics: Record<string, string>;\n tree?: TaskProcessSample[];\n}\n\nexport interface TaskExecDetails {\n command: string;\n args?: string[];\n pid?: number;\n status: string;\n exitCode: number;\n started?: string;\n /** Go time.Duration encoded as nanoseconds. */\n duration?: number;\n [key: string]: unknown;\n}\n\nexport interface TaskSnapshot {\n id: string;\n name: string;\n /** \"task\" or \"group\". */\n type: \"task\" | \"group\";\n /** Parent group NAME (groups: empty). */\n group?: string;\n status: string;\n duration?: string;\n error?: string;\n /** Latest log line. */\n message?: string;\n logs?: LogEntry[];\n /** Group aggregates. */\n total?: number;\n completed?: number;\n failed?: number;\n running?: number;\n canceled?: number;\n work?: TaskWorkProgress;\n /** Per-task (type === \"task\") live stage label and bounded progress. */\n description?: string;\n progress?: number;\n /** Progress denominator; 0/undefined means the task has no bounded progress. */\n maxValue?: number;\n /** Stable parent-run id (tasks) or the run's own id (groups). */\n groupId?: string;\n kind?: string;\n labels?: Record<string, string>;\n owner?: string;\n /** RFC3339. */\n startedAt?: string;\n /** RFC3339. */\n finishedAt?: string;\n href?: string;\n controls?: TaskControlAction[];\n stdout?: string;\n stderr?: string;\n stdoutTruncated?: boolean;\n stderrTruncated?: boolean;\n details?: TaskProcessDetails | TaskExecDetails | Record<string, unknown>;\n}\n\n// TaskRunMeta mirrors the Go `task.RunMeta` listing summary returned by\n// GET {prefix}/tasks. Named TaskRunMeta to avoid colliding with the unrelated\n// diagnostics `RunMeta` already exported by this package.\nexport interface TaskRunMeta {\n id: string;\n name: string;\n kind?: string;\n labels?: Record<string, string>;\n owner?: string;\n status: string;\n startedAt?: string;\n finishedAt?: string;\n total: number;\n completed: number;\n failed: number;\n running: number;\n canceled?: number;\n work?: TaskWorkProgress;\n href?: string;\n controls?: TaskControlAction[];\n}\n\n// Terminal run/group statuses (not running/pending). Used to decide when an SSE\n// stream is done or when polling should stop.\nconst TERMINAL_STATUSES = new Set([\n \"success\",\n \"failed\",\n \"warning\",\n \"canceled\",\n \"PASS\",\n \"FAIL\",\n \"ERR\",\n \"SKIP\",\n]);\n\nexport function isTerminalStatus(status: string | undefined): boolean {\n return !!status && TERMINAL_STATUSES.has(status);\n}\n\n// allGroupsTerminal reports whether every group snapshot has reached a terminal\n// status — i.e. the run(s) are complete and an SSE stream / poll can stop.\nexport function allGroupsTerminal(snapshots: TaskSnapshot[]): boolean {\n const groups = snapshots.filter((s) => s.type === \"group\");\n if (groups.length === 0) return false;\n return groups.every((g) => isTerminalStatus(g.status));\n}\n"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"TaskSnapshot.js","sources":["../../src/data/TaskSnapshot.ts"],"sourcesContent":["// TaskSnapshot mirrors the Go `task.TaskSnapshot` wire format emitted by clicky's\n// JSONHandler / SSEHandler (github.com/flanksource/clicky/task/snapshot.go). Field\n// names match the JSON tags exactly. This module is generic — it carries no\n// application-specific concepts.\n\nexport interface LogEntry {\n level: string;\n message: string;\n}\n\nexport type TaskControlAction = \"start\" | \"stop\" | \"restart\" | \"drain\";\n\nexport interface TaskWorkProgress {\n total: number;\n completed: number;\n cached: number;\n failed: number;\n running: number;\n canceled: number;\n unstarted: number;\n}\n\nexport interface TaskResourceSnapshot {\n cpuPercent: number;\n rssBytes: number;\n vmsBytes: number;\n openFiles: number;\n sampledAt: string;\n}\n\nexport interface TaskProcessSample {\n pid: number;\n ppid: number;\n command: string;\n status?: string;\n isRoot?: boolean;\n cpuPercent: number;\n rssBytes: number;\n vmsBytes: number;\n openFiles: number;\n}\n\n/** Runaway bounds a supervised process is killed for exceeding. */\nexport interface TaskResourceLimits {\n maxRssBytes?: number;\n maxCpuPercent?: number;\n cpuSampleCount?: number;\n /** Sampling cadence as a Go duration string, e.g. \"2s\". */\n interval?: string;\n}\n\nexport interface TaskProcessDetails {\n pid?: number;\n command: string;\n args?: string[];\n status: string;\n started?: string;\n exitCode?: number;\n ports?: number[];\n restarts: number;\n restartPolicy: string;\n maxRestarts?: number;\n limits?: TaskResourceLimits;\n latest: TaskResourceSnapshot;\n peak: TaskResourceSnapshot;\n metrics: Record<string, string>;\n tree?: TaskProcessSample[];\n /**\n * Caller-supplied context, re-read on every snapshot. Any JSON the producer\n * chooses to send — flat values render inline, anything structured renders as\n * JSON. This module gives the shape no meaning beyond \"show it\".\n */\n metadata?: unknown;\n}\n\nexport interface TaskExecDetails {\n command: string;\n args?: string[];\n pid?: number;\n status: string;\n exitCode: number;\n started?: string;\n /** Go time.Duration encoded as nanoseconds. */\n duration?: number;\n [key: string]: unknown;\n}\n\nexport interface TaskSnapshot {\n id: string;\n name: string;\n /** \"task\" or \"group\". */\n type: \"task\" | \"group\";\n /** Parent group NAME (groups: empty). */\n group?: string;\n status: string;\n duration?: string;\n error?: string;\n /** Latest log line. */\n message?: string;\n logs?: LogEntry[];\n /** Group aggregates. */\n total?: number;\n completed?: number;\n failed?: number;\n running?: number;\n canceled?: number;\n work?: TaskWorkProgress;\n /** Per-task (type === \"task\") live stage label and bounded progress. */\n description?: string;\n progress?: number;\n /** Progress denominator; 0/undefined means the task has no bounded progress. */\n maxValue?: number;\n /** Stable parent-run id (tasks) or the run's own id (groups). */\n groupId?: string;\n kind?: string;\n labels?: Record<string, string>;\n owner?: string;\n /** RFC3339. */\n startedAt?: string;\n /** RFC3339. */\n finishedAt?: string;\n href?: string;\n controls?: TaskControlAction[];\n stdout?: string;\n stderr?: string;\n /**\n * Absolute stream offset of the first retained byte — how much the server's\n * bounded buffer has already discarded ahead of `stdout`/`stderr`. Non-zero\n * exactly when the matching `*Truncated` flag is set.\n */\n stdoutOffset?: number;\n stderrOffset?: number;\n stdoutTruncated?: boolean;\n stderrTruncated?: boolean;\n details?: TaskProcessDetails | TaskExecDetails | Record<string, unknown>;\n}\n\n// TaskRunMeta mirrors the Go `task.RunMeta` listing summary returned by\n// GET {prefix}/tasks. Named TaskRunMeta to avoid colliding with the unrelated\n// diagnostics `RunMeta` already exported by this package.\nexport interface TaskRunMeta {\n id: string;\n name: string;\n kind?: string;\n labels?: Record<string, string>;\n owner?: string;\n status: string;\n startedAt?: string;\n finishedAt?: string;\n total: number;\n completed: number;\n failed: number;\n running: number;\n canceled?: number;\n work?: TaskWorkProgress;\n href?: string;\n controls?: TaskControlAction[];\n}\n\n// Terminal run/group statuses (not running/pending). Used to decide when an SSE\n// stream is done or when polling should stop.\nconst TERMINAL_STATUSES = new Set([\n \"success\",\n \"failed\",\n \"warning\",\n \"canceled\",\n \"PASS\",\n \"FAIL\",\n \"ERR\",\n \"SKIP\",\n]);\n\nexport function isTerminalStatus(status: string | undefined): boolean {\n return !!status && TERMINAL_STATUSES.has(status);\n}\n\n// allGroupsTerminal reports whether every group snapshot has reached a terminal\n// status — i.e. the run(s) are complete and an SSE stream / poll can stop.\nexport function allGroupsTerminal(snapshots: TaskSnapshot[]): boolean {\n const groups = snapshots.filter((s) => s.type === \"group\");\n if (groups.length === 0) return false;\n return groups.every((g) => isTerminalStatus(g.status));\n}\n"],"names":[],"mappings":"AAiKA,MAAM,wCAAwB,IAAI;AAAA,EAChC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAEM,SAAS,iBAAiB,QAAqC;AACpE,SAAO,CAAC,CAAC,UAAU,kBAAkB,IAAI,MAAM;AACjD;AAIO,SAAS,kBAAkB,WAAoC;AACpE,QAAM,SAAS,UAAU,OAAO,CAAC,MAAM,EAAE,SAAS,OAAO;AACzD,MAAI,OAAO,WAAW,EAAG,QAAO;AAChC,SAAO,OAAO,MAAM,CAAC,MAAM,iBAAiB,EAAE,MAAM,CAAC;AACvD;"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const jsxRuntime = require("react/jsx-runtime");
|
|
4
|
+
const react = require("react");
|
|
5
|
+
const utils = require("../lib/utils.cjs");
|
|
6
|
+
const AnsiHtml = require("./AnsiHtml.cjs");
|
|
7
|
+
const BUILT_IN_TAB_IDS = ["stdout", "stderr"];
|
|
8
|
+
const warnedTabIds = /* @__PURE__ */ new Set();
|
|
9
|
+
function warnTakenTabId(id) {
|
|
10
|
+
if (warnedTabIds.has(id)) return;
|
|
11
|
+
warnedTabIds.add(id);
|
|
12
|
+
console.warn(`TaskStreamTabs: dropped an extra tab claiming the id "${id}", which is already taken`);
|
|
13
|
+
}
|
|
14
|
+
function uniqueHostTabs(extraTabs) {
|
|
15
|
+
const taken = new Set(BUILT_IN_TAB_IDS);
|
|
16
|
+
const tabs = [];
|
|
17
|
+
for (const tab of extraTabs) {
|
|
18
|
+
if (taken.has(tab.id)) {
|
|
19
|
+
warnTakenTabId(tab.id);
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
taken.add(tab.id);
|
|
23
|
+
tabs.push(tab);
|
|
24
|
+
}
|
|
25
|
+
return tabs;
|
|
26
|
+
}
|
|
27
|
+
function TaskStreamPane({ text, truncated, error }) {
|
|
28
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
29
|
+
truncated && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-2 pt-1 text-[10px] text-gray-400", children: "showing latest 1 MiB" }),
|
|
30
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
31
|
+
AnsiHtml.AnsiHtml,
|
|
32
|
+
{
|
|
33
|
+
text,
|
|
34
|
+
className: utils.cn("max-h-64 overflow-auto whitespace-pre-wrap p-2 text-xs text-gray-100", error && "text-red-300")
|
|
35
|
+
}
|
|
36
|
+
)
|
|
37
|
+
] });
|
|
38
|
+
}
|
|
39
|
+
function TaskStreamTabs({ task, extraTabs }) {
|
|
40
|
+
var _a;
|
|
41
|
+
const tabs = uniqueHostTabs(extraTabs ?? []);
|
|
42
|
+
const stdout = task.stdout;
|
|
43
|
+
const stderr = task.stderr;
|
|
44
|
+
if (stdout) {
|
|
45
|
+
tabs.push({
|
|
46
|
+
id: "stdout",
|
|
47
|
+
render: () => /* @__PURE__ */ jsxRuntime.jsx(TaskStreamPane, { text: stdout, ...task.stdoutTruncated ? { truncated: true } : {} })
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
if (stderr) {
|
|
51
|
+
tabs.push({
|
|
52
|
+
id: "stderr",
|
|
53
|
+
render: () => /* @__PURE__ */ jsxRuntime.jsx(TaskStreamPane, { text: stderr, ...task.stderrTruncated ? { truncated: true } : {}, error: true })
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
const [selected, setSelected] = react.useState((_a = tabs[0]) == null ? void 0 : _a.id);
|
|
57
|
+
const active = tabs.find((tab) => tab.id === selected) ?? tabs[0];
|
|
58
|
+
if (!active) return null;
|
|
59
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-2 overflow-hidden rounded border bg-black", onClick: (event) => event.stopPropagation(), children: [
|
|
60
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-1 border-b border-white/10 px-1 py-1 text-[10px]", children: tabs.map((tab) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
61
|
+
"button",
|
|
62
|
+
{
|
|
63
|
+
type: "button",
|
|
64
|
+
"aria-current": tab.id === active.id,
|
|
65
|
+
className: utils.cn(
|
|
66
|
+
"rounded px-2 py-0.5",
|
|
67
|
+
tab.id === active.id ? "bg-white/10 text-gray-100" : "text-gray-400 hover:text-gray-200"
|
|
68
|
+
),
|
|
69
|
+
onClick: () => setSelected(tab.id),
|
|
70
|
+
children: tab.label ?? tab.id
|
|
71
|
+
},
|
|
72
|
+
tab.id
|
|
73
|
+
)) }),
|
|
74
|
+
active.render()
|
|
75
|
+
] });
|
|
76
|
+
}
|
|
77
|
+
exports.TaskStreamTabs = TaskStreamTabs;
|
|
78
|
+
//# sourceMappingURL=TaskStreamTabs.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TaskStreamTabs.cjs","sources":["../../src/data/TaskStreamTabs.tsx"],"sourcesContent":["import { type ReactNode, useState } from \"react\";\n\nimport { cn } from \"../lib/utils\";\nimport { AnsiHtml } from \"./AnsiHtml\";\nimport type { TaskSnapshot } from \"./TaskSnapshot\";\n\n/**\n * A pane of output for a task, contributed by the host. It exists because the\n * raw stdout of a process is not always the output worth reading: an agent's\n * stdout is its JSON-RPC transport, and the readable account of what it did\n * lives somewhere only the host knows how to fetch.\n */\nexport interface TaskStreamTab {\n /** Stable across renders; also the tab's label when no `label` is given. */\n id: string;\n label?: string;\n render: () => ReactNode;\n}\n\n/**\n * Contributes host-owned panes for one task. Return [] for tasks it cannot\n * serve. The group is passed alongside because identity — kind, labels, href —\n * lives on the group snapshot, not on the child task row.\n *\n * Ids must be unique, and `stdout` and `stderr` belong to the process's own\n * streams: a pane claiming one of those would take the place of output nothing\n * else can show.\n */\nexport type TaskExtraTabs = (task: TaskSnapshot, group: TaskSnapshot) => TaskStreamTab[];\n\n/** Ids this component renders itself, from the task's own streams. */\nconst BUILT_IN_TAB_IDS = [\"stdout\", \"stderr\"];\n\n/** Said once per id: a task row re-renders on every frame the run sends. */\nconst warnedTabIds = new Set<string>();\n\nfunction warnTakenTabId(id: string) {\n if (warnedTabIds.has(id)) return;\n warnedTabIds.add(id);\n console.warn(`TaskStreamTabs: dropped an extra tab claiming the id \"${id}\", which is already taken`);\n}\n\n/**\n * Host panes that can be told apart. A tab is addressed by its id — it is the\n * React key, and what a click selects — so a repeat of one, or of a built-in\n * stream's, does not name a second pane: it hides the first one that answers to\n * that id. Dropping the later claim keeps every remaining pane reachable, and\n * says so, rather than rendering a strip whose tabs do not all work.\n */\nfunction uniqueHostTabs(extraTabs: TaskStreamTab[]): TaskStreamTab[] {\n const taken = new Set(BUILT_IN_TAB_IDS);\n const tabs: TaskStreamTab[] = [];\n for (const tab of extraTabs) {\n if (taken.has(tab.id)) {\n warnTakenTabId(tab.id);\n continue;\n }\n taken.add(tab.id);\n tabs.push(tab);\n }\n return tabs;\n}\n\nfunction TaskStreamPane({ text, truncated, error }: { text: string; truncated?: boolean; error?: boolean }) {\n return (\n <>\n {truncated && <div className=\"px-2 pt-1 text-[10px] text-gray-400\">showing latest 1 MiB</div>}\n <AnsiHtml\n text={text}\n className={cn(\"max-h-64 overflow-auto whitespace-pre-wrap p-2 text-xs text-gray-100\", error && \"text-red-300\")}\n />\n </>\n );\n}\n\n/**\n * TaskStreamTabs puts every account of what a task is doing behind one tab\n * strip: whatever the host contributes first, then the process's own streams.\n * A single stream still renders as one labelled pane, so the common case does\n * not grow a tab strip it has no use for.\n */\nexport function TaskStreamTabs({ task, extraTabs }: { task: TaskSnapshot; extraTabs?: TaskStreamTab[] }) {\n const tabs: TaskStreamTab[] = uniqueHostTabs(extraTabs ?? []);\n const stdout = task.stdout;\n const stderr = task.stderr;\n if (stdout) {\n tabs.push({\n id: \"stdout\",\n render: () => <TaskStreamPane text={stdout} {...(task.stdoutTruncated ? { truncated: true } : {})} />,\n });\n }\n if (stderr) {\n tabs.push({\n id: \"stderr\",\n render: () => <TaskStreamPane text={stderr} {...(task.stderrTruncated ? { truncated: true } : {})} error />,\n });\n }\n\n const [selected, setSelected] = useState(tabs[0]?.id);\n const active = tabs.find((tab) => tab.id === selected) ?? tabs[0];\n if (!active) return null;\n\n return (\n <div className=\"mt-2 overflow-hidden rounded border bg-black\" onClick={(event) => event.stopPropagation()}>\n <div className=\"flex items-center gap-1 border-b border-white/10 px-1 py-1 text-[10px]\">\n {tabs.map((tab) => (\n <button\n key={tab.id}\n type=\"button\"\n aria-current={tab.id === active.id}\n className={cn(\n \"rounded px-2 py-0.5\",\n tab.id === active.id ? \"bg-white/10 text-gray-100\" : \"text-gray-400 hover:text-gray-200\",\n )}\n onClick={() => setSelected(tab.id)}\n >\n {tab.label ?? tab.id}\n </button>\n ))}\n </div>\n {active.render()}\n </div>\n );\n}\n"],"names":["jsxs","Fragment","jsx","AnsiHtml","cn","useState"],"mappings":";;;;;;AA+BA,MAAM,mBAAmB,CAAC,UAAU,QAAQ;AAG5C,MAAM,mCAAmB,IAAA;AAEzB,SAAS,eAAe,IAAY;AAClC,MAAI,aAAa,IAAI,EAAE,EAAG;AAC1B,eAAa,IAAI,EAAE;AACnB,UAAQ,KAAK,yDAAyD,EAAE,2BAA2B;AACrG;AASA,SAAS,eAAe,WAA6C;AACnE,QAAM,QAAQ,IAAI,IAAI,gBAAgB;AACtC,QAAM,OAAwB,CAAA;AAC9B,aAAW,OAAO,WAAW;AAC3B,QAAI,MAAM,IAAI,IAAI,EAAE,GAAG;AACrB,qBAAe,IAAI,EAAE;AACrB;AAAA,IACF;AACA,UAAM,IAAI,IAAI,EAAE;AAChB,SAAK,KAAK,GAAG;AAAA,EACf;AACA,SAAO;AACT;AAEA,SAAS,eAAe,EAAE,MAAM,WAAW,SAAiE;AAC1G,SACEA,2BAAAA,KAAAC,qBAAA,EACG,UAAA;AAAA,IAAA,aAAaC,2BAAAA,IAAC,OAAA,EAAI,WAAU,uCAAsC,UAAA,wBAAoB;AAAA,IACvFA,2BAAAA;AAAAA,MAACC,SAAAA;AAAAA,MAAA;AAAA,QACC;AAAA,QACA,WAAWC,MAAAA,GAAG,wEAAwE,SAAS,cAAc;AAAA,MAAA;AAAA,IAAA;AAAA,EAC/G,GACF;AAEJ;AAQO,SAAS,eAAe,EAAE,MAAM,aAAkE;;AACvG,QAAM,OAAwB,eAAe,aAAa,EAAE;AAC5D,QAAM,SAAS,KAAK;AACpB,QAAM,SAAS,KAAK;AACpB,MAAI,QAAQ;AACV,SAAK,KAAK;AAAA,MACR,IAAI;AAAA,MACJ,QAAQ,MAAMF,2BAAAA,IAAC,gBAAA,EAAe,MAAM,QAAS,GAAI,KAAK,kBAAkB,EAAE,WAAW,KAAA,IAAS,CAAA,EAAC,CAAI;AAAA,IAAA,CACpG;AAAA,EACH;AACA,MAAI,QAAQ;AACV,SAAK,KAAK;AAAA,MACR,IAAI;AAAA,MACJ,QAAQ,MAAMA,2BAAAA,IAAC,gBAAA,EAAe,MAAM,QAAS,GAAI,KAAK,kBAAkB,EAAE,WAAW,KAAA,IAAS,CAAA,GAAK,OAAK,KAAA,CAAC;AAAA,IAAA,CAC1G;AAAA,EACH;AAEA,QAAM,CAAC,UAAU,WAAW,IAAIG,MAAAA,UAAS,UAAK,CAAC,MAAN,mBAAS,EAAE;AACpD,QAAM,SAAS,KAAK,KAAK,CAAC,QAAQ,IAAI,OAAO,QAAQ,KAAK,KAAK,CAAC;AAChE,MAAI,CAAC,OAAQ,QAAO;AAEpB,SACEL,gCAAC,SAAI,WAAU,gDAA+C,SAAS,CAAC,UAAU,MAAM,gBAAA,GACtF,UAAA;AAAA,IAAAE,+BAAC,SAAI,WAAU,0EACZ,UAAA,KAAK,IAAI,CAAC,QACTA,2BAAAA;AAAAA,MAAC;AAAA,MAAA;AAAA,QAEC,MAAK;AAAA,QACL,gBAAc,IAAI,OAAO,OAAO;AAAA,QAChC,WAAWE,MAAAA;AAAAA,UACT;AAAA,UACA,IAAI,OAAO,OAAO,KAAK,8BAA8B;AAAA,QAAA;AAAA,QAEvD,SAAS,MAAM,YAAY,IAAI,EAAE;AAAA,QAEhC,UAAA,IAAI,SAAS,IAAI;AAAA,MAAA;AAAA,MATb,IAAI;AAAA,IAAA,CAWZ,GACH;AAAA,IACC,OAAO,OAAA;AAAA,EAAO,GACjB;AAEJ;;"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
import { TaskSnapshot } from './TaskSnapshot';
|
|
3
|
+
/**
|
|
4
|
+
* A pane of output for a task, contributed by the host. It exists because the
|
|
5
|
+
* raw stdout of a process is not always the output worth reading: an agent's
|
|
6
|
+
* stdout is its JSON-RPC transport, and the readable account of what it did
|
|
7
|
+
* lives somewhere only the host knows how to fetch.
|
|
8
|
+
*/
|
|
9
|
+
export interface TaskStreamTab {
|
|
10
|
+
/** Stable across renders; also the tab's label when no `label` is given. */
|
|
11
|
+
id: string;
|
|
12
|
+
label?: string;
|
|
13
|
+
render: () => ReactNode;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Contributes host-owned panes for one task. Return [] for tasks it cannot
|
|
17
|
+
* serve. The group is passed alongside because identity — kind, labels, href —
|
|
18
|
+
* lives on the group snapshot, not on the child task row.
|
|
19
|
+
*
|
|
20
|
+
* Ids must be unique, and `stdout` and `stderr` belong to the process's own
|
|
21
|
+
* streams: a pane claiming one of those would take the place of output nothing
|
|
22
|
+
* else can show.
|
|
23
|
+
*/
|
|
24
|
+
export type TaskExtraTabs = (task: TaskSnapshot, group: TaskSnapshot) => TaskStreamTab[];
|
|
25
|
+
/**
|
|
26
|
+
* TaskStreamTabs puts every account of what a task is doing behind one tab
|
|
27
|
+
* strip: whatever the host contributes first, then the process's own streams.
|
|
28
|
+
* A single stream still renders as one labelled pane, so the common case does
|
|
29
|
+
* not grow a tab strip it has no use for.
|
|
30
|
+
*/
|
|
31
|
+
export declare function TaskStreamTabs({ task, extraTabs }: {
|
|
32
|
+
task: TaskSnapshot;
|
|
33
|
+
extraTabs?: TaskStreamTab[];
|
|
34
|
+
}): import("react").JSX.Element | null;
|
|
35
|
+
//# sourceMappingURL=TaskStreamTabs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TaskStreamTabs.d.ts","sourceRoot":"","sources":["../../src/data/TaskStreamTabs.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,SAAS,EAAY,MAAM,OAAO,CAAC;AAIjD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAEnD;;;;;GAKG;AACH,MAAM,WAAW,aAAa;IAC5B,4EAA4E;IAC5E,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,SAAS,CAAC;CACzB;AAED;;;;;;;;GAQG;AACH,MAAM,MAAM,aAAa,GAAG,CAAC,IAAI,EAAE,YAAY,EAAE,KAAK,EAAE,YAAY,KAAK,aAAa,EAAE,CAAC;AA+CzF;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,SAAS,CAAC,EAAE,aAAa,EAAE,CAAA;CAAE,sCA0CtG"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { jsxs, jsx, Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from "react";
|
|
3
|
+
import { cn } from "../lib/utils.js";
|
|
4
|
+
import { AnsiHtml } from "./AnsiHtml.js";
|
|
5
|
+
const BUILT_IN_TAB_IDS = ["stdout", "stderr"];
|
|
6
|
+
const warnedTabIds = /* @__PURE__ */ new Set();
|
|
7
|
+
function warnTakenTabId(id) {
|
|
8
|
+
if (warnedTabIds.has(id)) return;
|
|
9
|
+
warnedTabIds.add(id);
|
|
10
|
+
console.warn(`TaskStreamTabs: dropped an extra tab claiming the id "${id}", which is already taken`);
|
|
11
|
+
}
|
|
12
|
+
function uniqueHostTabs(extraTabs) {
|
|
13
|
+
const taken = new Set(BUILT_IN_TAB_IDS);
|
|
14
|
+
const tabs = [];
|
|
15
|
+
for (const tab of extraTabs) {
|
|
16
|
+
if (taken.has(tab.id)) {
|
|
17
|
+
warnTakenTabId(tab.id);
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
taken.add(tab.id);
|
|
21
|
+
tabs.push(tab);
|
|
22
|
+
}
|
|
23
|
+
return tabs;
|
|
24
|
+
}
|
|
25
|
+
function TaskStreamPane({ text, truncated, error }) {
|
|
26
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
27
|
+
truncated && /* @__PURE__ */ jsx("div", { className: "px-2 pt-1 text-[10px] text-gray-400", children: "showing latest 1 MiB" }),
|
|
28
|
+
/* @__PURE__ */ jsx(
|
|
29
|
+
AnsiHtml,
|
|
30
|
+
{
|
|
31
|
+
text,
|
|
32
|
+
className: cn("max-h-64 overflow-auto whitespace-pre-wrap p-2 text-xs text-gray-100", error && "text-red-300")
|
|
33
|
+
}
|
|
34
|
+
)
|
|
35
|
+
] });
|
|
36
|
+
}
|
|
37
|
+
function TaskStreamTabs({ task, extraTabs }) {
|
|
38
|
+
var _a;
|
|
39
|
+
const tabs = uniqueHostTabs(extraTabs ?? []);
|
|
40
|
+
const stdout = task.stdout;
|
|
41
|
+
const stderr = task.stderr;
|
|
42
|
+
if (stdout) {
|
|
43
|
+
tabs.push({
|
|
44
|
+
id: "stdout",
|
|
45
|
+
render: () => /* @__PURE__ */ jsx(TaskStreamPane, { text: stdout, ...task.stdoutTruncated ? { truncated: true } : {} })
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
if (stderr) {
|
|
49
|
+
tabs.push({
|
|
50
|
+
id: "stderr",
|
|
51
|
+
render: () => /* @__PURE__ */ jsx(TaskStreamPane, { text: stderr, ...task.stderrTruncated ? { truncated: true } : {}, error: true })
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
const [selected, setSelected] = useState((_a = tabs[0]) == null ? void 0 : _a.id);
|
|
55
|
+
const active = tabs.find((tab) => tab.id === selected) ?? tabs[0];
|
|
56
|
+
if (!active) return null;
|
|
57
|
+
return /* @__PURE__ */ jsxs("div", { className: "mt-2 overflow-hidden rounded border bg-black", onClick: (event) => event.stopPropagation(), children: [
|
|
58
|
+
/* @__PURE__ */ jsx("div", { className: "flex items-center gap-1 border-b border-white/10 px-1 py-1 text-[10px]", children: tabs.map((tab) => /* @__PURE__ */ jsx(
|
|
59
|
+
"button",
|
|
60
|
+
{
|
|
61
|
+
type: "button",
|
|
62
|
+
"aria-current": tab.id === active.id,
|
|
63
|
+
className: cn(
|
|
64
|
+
"rounded px-2 py-0.5",
|
|
65
|
+
tab.id === active.id ? "bg-white/10 text-gray-100" : "text-gray-400 hover:text-gray-200"
|
|
66
|
+
),
|
|
67
|
+
onClick: () => setSelected(tab.id),
|
|
68
|
+
children: tab.label ?? tab.id
|
|
69
|
+
},
|
|
70
|
+
tab.id
|
|
71
|
+
)) }),
|
|
72
|
+
active.render()
|
|
73
|
+
] });
|
|
74
|
+
}
|
|
75
|
+
export {
|
|
76
|
+
TaskStreamTabs
|
|
77
|
+
};
|
|
78
|
+
//# sourceMappingURL=TaskStreamTabs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TaskStreamTabs.js","sources":["../../src/data/TaskStreamTabs.tsx"],"sourcesContent":["import { type ReactNode, useState } from \"react\";\n\nimport { cn } from \"../lib/utils\";\nimport { AnsiHtml } from \"./AnsiHtml\";\nimport type { TaskSnapshot } from \"./TaskSnapshot\";\n\n/**\n * A pane of output for a task, contributed by the host. It exists because the\n * raw stdout of a process is not always the output worth reading: an agent's\n * stdout is its JSON-RPC transport, and the readable account of what it did\n * lives somewhere only the host knows how to fetch.\n */\nexport interface TaskStreamTab {\n /** Stable across renders; also the tab's label when no `label` is given. */\n id: string;\n label?: string;\n render: () => ReactNode;\n}\n\n/**\n * Contributes host-owned panes for one task. Return [] for tasks it cannot\n * serve. The group is passed alongside because identity — kind, labels, href —\n * lives on the group snapshot, not on the child task row.\n *\n * Ids must be unique, and `stdout` and `stderr` belong to the process's own\n * streams: a pane claiming one of those would take the place of output nothing\n * else can show.\n */\nexport type TaskExtraTabs = (task: TaskSnapshot, group: TaskSnapshot) => TaskStreamTab[];\n\n/** Ids this component renders itself, from the task's own streams. */\nconst BUILT_IN_TAB_IDS = [\"stdout\", \"stderr\"];\n\n/** Said once per id: a task row re-renders on every frame the run sends. */\nconst warnedTabIds = new Set<string>();\n\nfunction warnTakenTabId(id: string) {\n if (warnedTabIds.has(id)) return;\n warnedTabIds.add(id);\n console.warn(`TaskStreamTabs: dropped an extra tab claiming the id \"${id}\", which is already taken`);\n}\n\n/**\n * Host panes that can be told apart. A tab is addressed by its id — it is the\n * React key, and what a click selects — so a repeat of one, or of a built-in\n * stream's, does not name a second pane: it hides the first one that answers to\n * that id. Dropping the later claim keeps every remaining pane reachable, and\n * says so, rather than rendering a strip whose tabs do not all work.\n */\nfunction uniqueHostTabs(extraTabs: TaskStreamTab[]): TaskStreamTab[] {\n const taken = new Set(BUILT_IN_TAB_IDS);\n const tabs: TaskStreamTab[] = [];\n for (const tab of extraTabs) {\n if (taken.has(tab.id)) {\n warnTakenTabId(tab.id);\n continue;\n }\n taken.add(tab.id);\n tabs.push(tab);\n }\n return tabs;\n}\n\nfunction TaskStreamPane({ text, truncated, error }: { text: string; truncated?: boolean; error?: boolean }) {\n return (\n <>\n {truncated && <div className=\"px-2 pt-1 text-[10px] text-gray-400\">showing latest 1 MiB</div>}\n <AnsiHtml\n text={text}\n className={cn(\"max-h-64 overflow-auto whitespace-pre-wrap p-2 text-xs text-gray-100\", error && \"text-red-300\")}\n />\n </>\n );\n}\n\n/**\n * TaskStreamTabs puts every account of what a task is doing behind one tab\n * strip: whatever the host contributes first, then the process's own streams.\n * A single stream still renders as one labelled pane, so the common case does\n * not grow a tab strip it has no use for.\n */\nexport function TaskStreamTabs({ task, extraTabs }: { task: TaskSnapshot; extraTabs?: TaskStreamTab[] }) {\n const tabs: TaskStreamTab[] = uniqueHostTabs(extraTabs ?? []);\n const stdout = task.stdout;\n const stderr = task.stderr;\n if (stdout) {\n tabs.push({\n id: \"stdout\",\n render: () => <TaskStreamPane text={stdout} {...(task.stdoutTruncated ? { truncated: true } : {})} />,\n });\n }\n if (stderr) {\n tabs.push({\n id: \"stderr\",\n render: () => <TaskStreamPane text={stderr} {...(task.stderrTruncated ? { truncated: true } : {})} error />,\n });\n }\n\n const [selected, setSelected] = useState(tabs[0]?.id);\n const active = tabs.find((tab) => tab.id === selected) ?? tabs[0];\n if (!active) return null;\n\n return (\n <div className=\"mt-2 overflow-hidden rounded border bg-black\" onClick={(event) => event.stopPropagation()}>\n <div className=\"flex items-center gap-1 border-b border-white/10 px-1 py-1 text-[10px]\">\n {tabs.map((tab) => (\n <button\n key={tab.id}\n type=\"button\"\n aria-current={tab.id === active.id}\n className={cn(\n \"rounded px-2 py-0.5\",\n tab.id === active.id ? \"bg-white/10 text-gray-100\" : \"text-gray-400 hover:text-gray-200\",\n )}\n onClick={() => setSelected(tab.id)}\n >\n {tab.label ?? tab.id}\n </button>\n ))}\n </div>\n {active.render()}\n </div>\n );\n}\n"],"names":[],"mappings":";;;;AA+BA,MAAM,mBAAmB,CAAC,UAAU,QAAQ;AAG5C,MAAM,mCAAmB,IAAA;AAEzB,SAAS,eAAe,IAAY;AAClC,MAAI,aAAa,IAAI,EAAE,EAAG;AAC1B,eAAa,IAAI,EAAE;AACnB,UAAQ,KAAK,yDAAyD,EAAE,2BAA2B;AACrG;AASA,SAAS,eAAe,WAA6C;AACnE,QAAM,QAAQ,IAAI,IAAI,gBAAgB;AACtC,QAAM,OAAwB,CAAA;AAC9B,aAAW,OAAO,WAAW;AAC3B,QAAI,MAAM,IAAI,IAAI,EAAE,GAAG;AACrB,qBAAe,IAAI,EAAE;AACrB;AAAA,IACF;AACA,UAAM,IAAI,IAAI,EAAE;AAChB,SAAK,KAAK,GAAG;AAAA,EACf;AACA,SAAO;AACT;AAEA,SAAS,eAAe,EAAE,MAAM,WAAW,SAAiE;AAC1G,SACE,qBAAA,UAAA,EACG,UAAA;AAAA,IAAA,aAAa,oBAAC,OAAA,EAAI,WAAU,uCAAsC,UAAA,wBAAoB;AAAA,IACvF;AAAA,MAAC;AAAA,MAAA;AAAA,QACC;AAAA,QACA,WAAW,GAAG,wEAAwE,SAAS,cAAc;AAAA,MAAA;AAAA,IAAA;AAAA,EAC/G,GACF;AAEJ;AAQO,SAAS,eAAe,EAAE,MAAM,aAAkE;;AACvG,QAAM,OAAwB,eAAe,aAAa,EAAE;AAC5D,QAAM,SAAS,KAAK;AACpB,QAAM,SAAS,KAAK;AACpB,MAAI,QAAQ;AACV,SAAK,KAAK;AAAA,MACR,IAAI;AAAA,MACJ,QAAQ,MAAM,oBAAC,gBAAA,EAAe,MAAM,QAAS,GAAI,KAAK,kBAAkB,EAAE,WAAW,KAAA,IAAS,CAAA,EAAC,CAAI;AAAA,IAAA,CACpG;AAAA,EACH;AACA,MAAI,QAAQ;AACV,SAAK,KAAK;AAAA,MACR,IAAI;AAAA,MACJ,QAAQ,MAAM,oBAAC,gBAAA,EAAe,MAAM,QAAS,GAAI,KAAK,kBAAkB,EAAE,WAAW,KAAA,IAAS,CAAA,GAAK,OAAK,KAAA,CAAC;AAAA,IAAA,CAC1G;AAAA,EACH;AAEA,QAAM,CAAC,UAAU,WAAW,IAAI,UAAS,UAAK,CAAC,MAAN,mBAAS,EAAE;AACpD,QAAM,SAAS,KAAK,KAAK,CAAC,QAAQ,IAAI,OAAO,QAAQ,KAAK,KAAK,CAAC;AAChE,MAAI,CAAC,OAAQ,QAAO;AAEpB,SACE,qBAAC,SAAI,WAAU,gDAA+C,SAAS,CAAC,UAAU,MAAM,gBAAA,GACtF,UAAA;AAAA,IAAA,oBAAC,SAAI,WAAU,0EACZ,UAAA,KAAK,IAAI,CAAC,QACT;AAAA,MAAC;AAAA,MAAA;AAAA,QAEC,MAAK;AAAA,QACL,gBAAc,IAAI,OAAO,OAAO;AAAA,QAChC,WAAW;AAAA,UACT;AAAA,UACA,IAAI,OAAO,OAAO,KAAK,8BAA8B;AAAA,QAAA;AAAA,QAEvD,SAAS,MAAM,YAAY,IAAI,EAAE;AAAA,QAEhC,UAAA,IAAI,SAAS,IAAI;AAAA,MAAA;AAAA,MATb,IAAI;AAAA,IAAA,CAWZ,GACH;AAAA,IACC,OAAO,OAAA;AAAA,EAAO,GACjB;AAEJ;"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
function pendingApprovalRequests(requests) {
|
|
4
|
+
return (requests ?? []).filter(
|
|
5
|
+
(request) => request.state === "pending" && request.kind === "tool_approval"
|
|
6
|
+
);
|
|
7
|
+
}
|
|
8
|
+
exports.pendingApprovalRequests = pendingApprovalRequests;
|
|
9
|
+
//# sourceMappingURL=SessionInspector.approvals-model.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SessionInspector.approvals-model.cjs","sources":["../../../src/data/ai/SessionInspector.approvals-model.ts"],"sourcesContent":["import type { SessionApprovalRequest } from \"./SessionViewer.unified\";\n\n/** Approve or deny one pending approval. */\nexport type ApprovalResolveAction = \"approve\" | \"deny\";\n\n/** Resolves one `session.requests[]` row. Rejecting surfaces the server's\n * refusal text inline on that row (e.g. the prompt run is no longer\n * `waiting`) instead of leaving the Approve/Deny buttons silently inert. */\nexport type ApprovalResolveHandler = (\n approvalId: string,\n action: ApprovalResolveAction,\n message?: string,\n) => void | Promise<void>;\n\n/** The subset of `session.requests[]` the approvals panel surfaces: outstanding\n * tool approvals, regardless of which surface (in-process chat or an external\n * broker) created the request.\n *\n * It lives here rather than beside the panel because it is a selector, not a\n * component, and a module that exports both loses fast refresh for everything\n * in it. Its other caller — the SessionInspector tab badge — only ever wanted\n * the count anyway. */\nexport function pendingApprovalRequests(\n requests: SessionApprovalRequest[] | undefined,\n): SessionApprovalRequest[] {\n return (requests ?? []).filter(\n (request) => request.state === \"pending\" && request.kind === \"tool_approval\",\n );\n}\n"],"names":[],"mappings":";;AAsBO,SAAS,wBACd,UAC0B;AAC1B,UAAQ,YAAY,CAAA,GAAI;AAAA,IACtB,CAAC,YAAY,QAAQ,UAAU,aAAa,QAAQ,SAAS;AAAA,EAAA;AAEjE;;"}
|