@marcoscale98/pi-extensible-workflows 5.17.0-fork.1 → 6.2.1-fork.1
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/CHANGELOG.md +145 -0
- package/README.md +2 -2
- package/dist/src/agent-execution.d.ts +43 -10
- package/dist/src/agent-execution.js +442 -162
- package/dist/src/background-widget.d.ts +5 -1
- package/dist/src/background-widget.js +5 -10
- package/dist/src/decoders.d.ts +2 -3
- package/dist/src/decoders.js +59 -37
- package/dist/src/execution.d.ts +6 -2
- package/dist/src/execution.js +48 -7
- package/dist/src/host-delivery.js +5 -8
- package/dist/src/host-navigator.js +5 -2
- package/dist/src/host-phases.d.ts +1 -0
- package/dist/src/host-recovery.d.ts +11 -8
- package/dist/src/host-recovery.js +14 -22
- package/dist/src/host-runtime.js +56 -5
- package/dist/src/host-view.d.ts +6 -1
- package/dist/src/host-view.js +46 -20
- package/dist/src/host.d.ts +2 -2
- package/dist/src/host.js +246 -118
- package/dist/src/index.d.ts +1 -2
- package/dist/src/index.js +1985 -1690
- package/dist/src/index.js.map +3 -3
- package/dist/src/paths.d.ts +8 -5
- package/dist/src/paths.js +24 -13
- package/dist/src/persistence.d.ts +1 -0
- package/dist/src/persistence.js +1 -0
- package/dist/src/pi-runtime-runner.js +4 -1
- package/dist/src/registry.d.ts +9 -7
- package/dist/src/registry.js +29 -71
- package/dist/src/runtime/agent-runner.d.ts +2 -0
- package/dist/src/script-tools.d.ts +27 -0
- package/dist/src/script-tools.js +87 -0
- package/dist/src/session-lease.d.ts +1 -1
- package/dist/src/session-lease.js +45 -11
- package/dist/src/settings.d.ts +19 -3
- package/dist/src/settings.js +63 -50
- package/dist/src/store.d.ts +3 -1
- package/dist/src/store.js +17 -1
- package/dist/src/trajectory.d.ts +12 -3
- package/dist/src/trajectory.js +32 -11
- package/dist/src/types.d.ts +129 -45
- package/dist/src/types.js +5 -8
- package/dist/src/utils.d.ts +21 -1
- package/dist/src/utils.js +46 -6
- package/dist/src/validation.d.ts +14 -3
- package/dist/src/validation.js +143 -87
- package/dist/starter/index.js +8 -11
- package/dist/starter/index.js.map +1 -1
- package/dist/starter/review-loop.d.ts +0 -13
- package/dist/starter/review-loop.js +8 -9
- package/dist/subagents/index.js +904 -956
- package/dist/subagents/index.js.map +3 -3
- package/dist/subagents/src/contracts.d.ts +48 -31
- package/dist/subagents/src/contracts.js +20 -9
- package/dist/subagents/src/index.d.ts +1 -1
- package/dist/subagents/src/index.js +14 -8
- package/dist/subagents/src/manager.js +52 -73
- package/dist/subagents/src/navigator.js +3 -14
- package/dist/subagents/src/view.d.ts +1 -1
- package/dist/subagents/src/view.js +15 -68
- package/dist/subagents/src/worktree.js +1 -1
- package/dist/trajectory/assets/index.html +35 -13
- package/dist/trajectory/index.js +576 -932
- package/dist/trajectory/index.js.map +3 -3
- package/dist/trajectory/src/assets/index.html +35 -13
- package/dist/trajectory/src/index.js +134 -104
- package/dist/trajectory/src/server.js +268 -27
- package/dist/trajectory/src/server.js.map +3 -3
- package/dist/trajectory/src/startup-mutex.d.ts +17 -0
- package/dist/trajectory/src/startup-mutex.js +172 -0
- package/examples/workflow-extension-template/README.md +4 -7
- package/examples/workflow-extension-template/extension.test.mjs +4 -8
- package/examples/workflow-extension-template/index.js +0 -3
- package/package.json +10 -10
- package/skills/pi-extensible-workflows/SKILL.md +12 -8
- package/src/agent-execution.ts +412 -164
- package/src/background-widget.ts +5 -11
- package/src/decoders.ts +51 -37
- package/src/execution.ts +46 -9
- package/src/host-delivery.ts +5 -8
- package/src/host-navigator.ts +4 -2
- package/src/host-phases.ts +1 -1
- package/src/host-recovery.ts +23 -28
- package/src/host-runtime.ts +44 -6
- package/src/host-view.ts +41 -16
- package/src/host.ts +229 -142
- package/src/index.ts +1 -2
- package/src/paths.ts +18 -9
- package/src/persistence.ts +1 -0
- package/src/pi-runtime-runner.ts +3 -1
- package/src/registry.ts +28 -64
- package/src/runtime/agent-runner.ts +2 -0
- package/src/script-tools.ts +76 -0
- package/src/session-lease.ts +34 -6
- package/src/settings.ts +56 -50
- package/src/store.ts +18 -2
- package/src/trajectory.ts +32 -15
- package/src/types.ts +82 -34
- package/src/utils.ts +42 -5
- package/src/validation.ts +121 -80
- package/starter/index.ts +0 -1
- package/starter/review-loop.ts +8 -9
- package/starter/roles/developer.md +0 -1
- package/starter/roles/oracle.md +0 -1
- package/starter/roles/researcher.md +0 -1
- package/starter/roles/reviewer.md +0 -1
- package/starter/roles/scout.md +0 -1
- package/subagents/README.md +10 -16
- package/subagents/src/contracts.ts +21 -11
- package/subagents/src/index.ts +17 -8
- package/subagents/src/manager.ts +48 -128
- package/subagents/src/navigator.ts +4 -13
- package/subagents/src/view.ts +16 -63
- package/subagents/src/worktree.ts +1 -1
- package/trajectory/src/assets/index.html +35 -13
- package/trajectory/src/index.ts +112 -91
- package/trajectory/src/server.ts +46 -10
- package/trajectory/src/startup-mutex.ts +127 -0
- package/dist/src/roles.d.ts +0 -70
- package/dist/src/roles.js +0 -325
- package/dist/starter/roles/developer.md +0 -6
- package/dist/starter/roles/oracle.md +0 -18
- package/dist/starter/roles/researcher.md +0 -20
- package/dist/starter/roles/reviewer.md +0 -19
- package/dist/starter/roles/scout.md +0 -18
- package/examples/workflow-extension-template/roles/reviewer.md +0 -4
- package/src/roles.ts +0 -326
package/subagents/src/view.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { AgentToolResult, ExtensionAPI, ExtensionContext, Theme } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { truncateToWidth } from "@earendil-works/pi-tui";
|
|
3
|
-
import { formatCost, formatStalledDuration, sanitizeDisplayText, WORKFLOW_AGENT_STALL_THRESHOLD_MS } from "../../src/index.js";
|
|
4
|
-
import {
|
|
3
|
+
import { formatCost, formatStalledDuration, formatWorkflowRuntime, sanitizeDisplayText, WORKFLOW_AGENT_STALL_THRESHOLD_MS } from "../../src/index.js";
|
|
4
|
+
import { agentActivityLabel, stalledDuration, textBlock } from "../../src/host-view.js";
|
|
5
|
+
import { drawFrame, formatElapsed, formatTokens, mark, QUIET_MS, REPAINT_MS, type Paint } from "../../src/background-widget.js";
|
|
5
6
|
import type { SubagentRunRequest, SubagentStatus } from "./contracts.js";
|
|
6
7
|
|
|
7
8
|
const SPINNER = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"] as const;
|
|
@@ -19,19 +20,7 @@ export type SubagentRenderState = {
|
|
|
19
20
|
subagentProgressFrozenAt?: number;
|
|
20
21
|
};
|
|
21
22
|
|
|
22
|
-
function textBlock(text: string) {
|
|
23
|
-
return {
|
|
24
|
-
render(width: number): string[] { return text.split("\n").map((line) => truncateToWidth(line, Math.max(1, width), "…")); },
|
|
25
|
-
invalidate() {},
|
|
26
|
-
};
|
|
27
|
-
}
|
|
28
23
|
|
|
29
|
-
function roleName(value: unknown): string | undefined {
|
|
30
|
-
if (typeof value === "string") return value.trim() || undefined;
|
|
31
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) return undefined;
|
|
32
|
-
const name = (value as Record<string, unknown>).name;
|
|
33
|
-
return typeof name === "string" ? name.trim() || undefined : undefined;
|
|
34
|
-
}
|
|
35
24
|
|
|
36
25
|
function modeName(value: unknown): "background" | "foreground" | undefined {
|
|
37
26
|
return value === "background" || value === "foreground" ? value : undefined;
|
|
@@ -39,14 +28,11 @@ function modeName(value: unknown): "background" | "foreground" | undefined {
|
|
|
39
28
|
|
|
40
29
|
function requestMetadata(args: SubagentRenderArgs, metadataAvailable: boolean): string {
|
|
41
30
|
const mode = modeName(args.mode) ?? (metadataAvailable ? "background" : undefined);
|
|
42
|
-
|
|
43
|
-
return [mode === undefined ? undefined : `mode=${mode}`, role === undefined ? undefined : `role=${role}`].filter((value): value is string => value !== undefined).join(" ");
|
|
31
|
+
return [mode === undefined ? undefined : `mode=${mode}`].filter((value): value is string => value !== undefined).join(" ");
|
|
44
32
|
}
|
|
45
33
|
|
|
46
34
|
function label(args: SubagentRenderArgs): string {
|
|
47
35
|
if (typeof args.label === "string" && args.label.trim()) return args.label.trim();
|
|
48
|
-
const role = roleName(args.role);
|
|
49
|
-
if (role) return role;
|
|
50
36
|
if (typeof args.id === "string" && args.id) return args.id.slice(0, 8);
|
|
51
37
|
return "subagent";
|
|
52
38
|
}
|
|
@@ -73,18 +59,6 @@ function statusValue(value: unknown): SubagentStatus | undefined {
|
|
|
73
59
|
return value as SubagentStatus;
|
|
74
60
|
}
|
|
75
61
|
|
|
76
|
-
function runtime(startedAt: number | undefined, finishedAt: number | undefined, now: number): string {
|
|
77
|
-
if (startedAt === undefined) return "";
|
|
78
|
-
const seconds = Math.max(0, Math.floor(((finishedAt ?? now) - startedAt) / 1000));
|
|
79
|
-
if (seconds < 60) return `${String(seconds)}s`;
|
|
80
|
-
const minutes = Math.floor(seconds / 60);
|
|
81
|
-
const remainingSeconds = seconds % 60;
|
|
82
|
-
if (minutes < 60) return `${String(minutes)}m${remainingSeconds ? ` ${String(remainingSeconds)}s` : ""}`;
|
|
83
|
-
const hours = Math.floor(minutes / 60);
|
|
84
|
-
const remainingMinutes = minutes % 60;
|
|
85
|
-
return `${String(hours)}h${remainingMinutes ? ` ${String(remainingMinutes)}m` : ""}`;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
62
|
function stateGlyph(state: SubagentStatus["state"], spinner: string): string {
|
|
89
63
|
if (state === "running") return spinner;
|
|
90
64
|
return state === "completed" ? "✓" : "✗";
|
|
@@ -95,25 +69,6 @@ function stateColor(state: SubagentStatus["state"]): "accent" | "success" | "err
|
|
|
95
69
|
return state === "completed" ? "success" : "error";
|
|
96
70
|
}
|
|
97
71
|
|
|
98
|
-
function activity(status: SubagentStatus): string | undefined {
|
|
99
|
-
if (status.state !== "running") return undefined;
|
|
100
|
-
const current = status.progress?.activity;
|
|
101
|
-
if (current?.kind === "reasoning" || current?.kind === "text") {
|
|
102
|
-
const name = current.kind === "reasoning" ? "reasoning" : "responding";
|
|
103
|
-
const text = sanitizeDisplayText(current.text);
|
|
104
|
-
return text && text !== name ? `${name} · ${text}` : name;
|
|
105
|
-
}
|
|
106
|
-
if (current?.kind === "tool") return sanitizeDisplayText(current.text);
|
|
107
|
-
return sanitizeDisplayText([...(status.progress?.toolCalls ?? [])].reverse().find(({ state }) => state === "running")?.name ?? "");
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
function stalledDuration(status: SubagentStatus, now: number): number | undefined {
|
|
111
|
-
const lastEventAt = status.progress?.lastEventAt;
|
|
112
|
-
if (status.state !== "running" || lastEventAt === undefined || !Number.isFinite(lastEventAt)) return undefined;
|
|
113
|
-
const duration = now - lastEventAt;
|
|
114
|
-
return duration >= WORKFLOW_AGENT_STALL_THRESHOLD_MS ? duration : undefined;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
72
|
/** Tokens and cost as the workflow header shows them. */
|
|
118
73
|
function usageStats(status: SubagentStatus): string {
|
|
119
74
|
const value = status.progress?.accounting;
|
|
@@ -143,15 +98,15 @@ function boundedValue(value: unknown): string {
|
|
|
143
98
|
|
|
144
99
|
function formatSubagentProgress(status: SubagentStatus, args: SubagentRenderArgs, theme: Theme, spinner: string, now: number, expanded: boolean): string {
|
|
145
100
|
const color = stateColor(status.state);
|
|
146
|
-
const elapsed =
|
|
101
|
+
const elapsed = status.startedAt === undefined ? "" : formatWorkflowRuntime((status.finishedAt ?? now) - status.startedAt);
|
|
147
102
|
const metadata = requestMetadata(args, args.id === undefined);
|
|
148
103
|
const stats = usageStats(status);
|
|
149
104
|
const lines = [
|
|
150
|
-
`${theme.fg(color, stateGlyph(status.state,
|
|
105
|
+
`${theme.fg(color, stateGlyph(status.state, "●"))} ${theme.bold(theme.fg("accent", `Subagent: ${label({ ...args, id: status.id })}`))} ${theme.fg(color, `[${status.state}]`)}${metadata ? ` ${theme.fg("dim", metadata)}` : ""}${stats ? ` ${stats}` : ""}${elapsed ? ` runtime=${elapsed}` : ""}`,
|
|
151
106
|
];
|
|
152
|
-
const current =
|
|
153
|
-
const stalled = stalledDuration(status, now);
|
|
154
|
-
if (current) lines.push(` ${theme.fg("accent", spinner)} ${theme.fg("dim", current)}${stalled === undefined ? "" : ` ${theme.fg("warning", `- stalled? ${formatStalledDuration(stalled)}`)}`}`);
|
|
107
|
+
const current = status.state === "running" ? agentActivityLabel(status.progress ?? {}) : undefined;
|
|
108
|
+
const stalled = stalledDuration({ ...status.progress, state: status.state }, now);
|
|
109
|
+
if (current || status.state === "running") lines.push(` ${theme.fg("accent", spinner)}${current ? ` ${theme.fg("dim", current)}` : ""}${stalled === undefined ? "" : ` ${theme.fg("warning", `- stalled? ${formatStalledDuration(stalled)}`)}`}`);
|
|
155
110
|
else if (stalled !== undefined) lines.push(` ${theme.fg("warning", `stalled? ${formatStalledDuration(stalled)}`)}`);
|
|
156
111
|
if (status.error) lines.push(` ${theme.fg("error", `${status.error.code}: ${status.error.message}`)}`);
|
|
157
112
|
if (expanded) {
|
|
@@ -218,7 +173,7 @@ export function subagentProgressBlock(status: SubagentStatus, args: SubagentRend
|
|
|
218
173
|
render(width: number): string[] {
|
|
219
174
|
const now = TERMINAL_STATES.has(current.state) ? frozenAt : Date.now();
|
|
220
175
|
if (!TERMINAL_STATES.has(current.state)) frozenAt = now;
|
|
221
|
-
const frame = SPINNER[Math.floor(now /
|
|
176
|
+
const frame = SPINNER[Math.floor(now / REPAINT_MS) % SPINNER.length] ?? "◇";
|
|
222
177
|
return formatSubagentProgress(current, args, currentTheme, frame, now, expanded).split("\n").map((line) => truncateToWidth(line, Math.max(1, width), "…"));
|
|
223
178
|
},
|
|
224
179
|
invalidate() {},
|
|
@@ -236,7 +191,7 @@ export function renderSubagentResult(result: AgentToolResult<unknown>, options:
|
|
|
236
191
|
const status = state.subagentStatus;
|
|
237
192
|
|
|
238
193
|
if (status?.state === "running" && options.isPartial && !state.subagentSpinner) {
|
|
239
|
-
state.subagentSpinner = setInterval(() => { context.invalidate(); },
|
|
194
|
+
state.subagentSpinner = setInterval(() => { context.invalidate(); }, REPAINT_MS);
|
|
240
195
|
state.subagentSpinner.unref();
|
|
241
196
|
} else if ((!options.isPartial || status?.state !== "running") && state.subagentSpinner) {
|
|
242
197
|
clearInterval(state.subagentSpinner);
|
|
@@ -280,19 +235,17 @@ function widgetRow({ status, request }: WidgetRun, now: number, paint: Paint): {
|
|
|
280
235
|
}
|
|
281
236
|
|
|
282
237
|
const RECEIPT_ENTRY_TYPE = "piewf-subagent-receipt";
|
|
283
|
-
type SubagentReceipt = { readonly id: string; readonly label: string; readonly state: string; readonly model?: string; readonly
|
|
238
|
+
type SubagentReceipt = { readonly id: string; readonly label: string; readonly state: string; readonly model?: string; readonly tools?: readonly string[]; readonly input: number; readonly output: number; readonly cacheRead: number; readonly costUsd: number; readonly durationMs: number; readonly attempts: number; readonly error?: string };
|
|
284
239
|
|
|
285
240
|
function receiptFor(status: Readonly<SubagentStatus>, request: Readonly<SubagentRunRequest>): SubagentReceipt {
|
|
286
241
|
const accounting = status.progress?.accounting;
|
|
287
242
|
const model = modelName(status);
|
|
288
|
-
const role = roleName(request.role);
|
|
289
243
|
const tools = status.progress?.state?.tools ?? status.attemptDetails?.at(-1)?.setup.tools;
|
|
290
244
|
return {
|
|
291
245
|
id: status.id,
|
|
292
246
|
label: label({ ...request, id: status.id }),
|
|
293
247
|
state: status.state,
|
|
294
248
|
...(model ? { model } : {}),
|
|
295
|
-
...(role === undefined ? {} : { role }),
|
|
296
249
|
...(tools === undefined ? {} : { tools: [...tools] }),
|
|
297
250
|
input: accounting?.input ?? 0,
|
|
298
251
|
output: accounting?.output ?? 0,
|
|
@@ -310,7 +263,7 @@ export function renderSubagentReceipt(data: SubagentReceipt, expanded: boolean,
|
|
|
310
263
|
const headline = [formatTokens(data.input + data.output), formatCost(data.costUsd) || "$0.00", formatElapsed(data.durationMs), data.state].filter(Boolean).join(" · ");
|
|
311
264
|
const lines = [`${glyph} ${theme.bold(sanitizeDisplayText(data.label))} ${theme.fg("muted", headline)}`];
|
|
312
265
|
if (!expanded) return lines;
|
|
313
|
-
const meta = [data.model ?? "", data.
|
|
266
|
+
const meta = [data.model ?? "", data.attempts > 1 ? `${String(data.attempts)} attempts` : "", data.tools?.join(" ") ?? ""].filter(Boolean);
|
|
314
267
|
if (meta.length > 0) lines.push(theme.fg("muted", ` ${meta.join(" · ")}`));
|
|
315
268
|
const split = [data.input ? `in ${formatTokens(data.input)}` : "", data.output ? `out ${formatTokens(data.output)}` : "", data.cacheRead ? `cache ${formatTokens(data.cacheRead)}` : ""].filter(Boolean);
|
|
316
269
|
if (split.length > 0) lines.push(theme.fg("muted", ` ${split.join(" · ")}`));
|
|
@@ -373,7 +326,7 @@ export function createSubagentBackgroundWidget(host: ReceiptHost = {}) {
|
|
|
373
326
|
};
|
|
374
327
|
}, { placement: "belowEditor" });
|
|
375
328
|
showing = true;
|
|
376
|
-
timer = setInterval(() => { requestRender?.(); },
|
|
329
|
+
timer = setInterval(() => { requestRender?.(); }, REPAINT_MS);
|
|
377
330
|
timer.unref();
|
|
378
331
|
} catch {
|
|
379
332
|
hide();
|
|
@@ -408,9 +361,9 @@ export function createSubagentBackgroundWidget(host: ReceiptHost = {}) {
|
|
|
408
361
|
}
|
|
409
362
|
|
|
410
363
|
|
|
411
|
-
export function renderSubagentInspectCall(args: { id?: string }, theme: Theme) {
|
|
364
|
+
export function renderSubagentInspectCall(args: { id?: string; scope?: string }, theme: Theme) {
|
|
412
365
|
const title = theme.fg("toolTitle", theme.bold("subagents_inspect"));
|
|
413
|
-
return textBlock(args.id ? `${title} ${theme.fg("accent", args.id)}` : `${title} ${theme.fg("muted", "all")}`);
|
|
366
|
+
return textBlock(args.id ? `${title} ${theme.fg("accent", args.id)}` : `${title} ${theme.fg("muted", args.scope === "all" ? "all" : "session")}`);
|
|
414
367
|
}
|
|
415
368
|
|
|
416
369
|
export function renderSubagentInspectResult(result: AgentToolResult<unknown>, options: { expanded: boolean }, theme: Theme, args: { id?: string }) {
|
|
@@ -54,7 +54,7 @@ function syntheticSnapshot(context: Readonly<SubagentWorktreeContext>): LaunchSn
|
|
|
54
54
|
...(context.worktreePostCreateCommandSource === undefined ? {} : { settingsSources: { concurrency: "subagents", modelAliases: "subagents", worktreePostCreateCommand: context.worktreePostCreateCommandSource } }),
|
|
55
55
|
models: [],
|
|
56
56
|
tools: [],
|
|
57
|
-
|
|
57
|
+
agentConfigurations: {},
|
|
58
58
|
schemas: [],
|
|
59
59
|
};
|
|
60
60
|
}
|
|
@@ -142,7 +142,7 @@
|
|
|
142
142
|
.workflow-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font: 600 16px/1.2 var(--serif); }
|
|
143
143
|
.workflow-meta { color: var(--dim); font: 11px ui-monospace, Menlo, Consolas, monospace; white-space: nowrap; }
|
|
144
144
|
.agent-grid { min-width: 900px; padding: 0 12px 8px 28px; }
|
|
145
|
-
.agent-grid-head, .agent-grid-row { display: grid; grid-template-columns: minmax(220px, 2fr) minmax(140px, 1.2fr)
|
|
145
|
+
.agent-grid-head, .agent-grid-row { display: grid; grid-template-columns: minmax(220px, 2fr) minmax(140px, 1.2fr) minmax(150px, 1.5fr) 80px 72px 64px; gap: 8px; align-items: center; }
|
|
146
146
|
.agent-grid-head { padding: 4px 8px; color: var(--mute); font-size: 10px; letter-spacing: .05em; }
|
|
147
147
|
.agent-grid-row { min-height: 24px; padding: 3px 8px; cursor: pointer; font-size: 11px; border-radius: 8px; }
|
|
148
148
|
.agent-grid-row:hover { background: var(--bg-hover); }
|
|
@@ -157,6 +157,14 @@
|
|
|
157
157
|
.log-row { display: grid; grid-template-columns: 7.5rem minmax(0, 1fr); gap: 10px; padding: 5px 8px; border-top: 1px solid var(--line); font: 11px ui-monospace, Menlo, Consolas, monospace; }
|
|
158
158
|
.log-time { color: var(--mute); white-space: nowrap; }
|
|
159
159
|
.log-message { min-width: 0; white-space: pre-wrap; overflow-wrap: anywhere; }
|
|
160
|
+
.calls-list { max-height: 480px; overflow: auto; }
|
|
161
|
+
.call-row { display: grid; grid-template-columns: 16px 7.5rem minmax(80px, 160px) minmax(0, 1fr) 64px; gap: 10px; align-items: center; padding: 5px 8px; border-top: 1px solid var(--line); font: 11px ui-monospace, Menlo, Consolas, monospace; cursor: pointer; list-style: none; }
|
|
162
|
+
.call-row::-webkit-details-marker { display: none; }
|
|
163
|
+
.call-row:hover { background: var(--bg-hover); }
|
|
164
|
+
.call-row b, .call-preview { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
165
|
+
.call-preview { color: var(--dim); }
|
|
166
|
+
.call-row .num { color: var(--dim); text-align: right; }
|
|
167
|
+
.call-body { padding: 4px 8px 12px 34px; }
|
|
160
168
|
.row { display: grid; grid-template-columns: 16px 1fr; gap: 8px; align-items: center; padding: 3px 12px; cursor: pointer; }
|
|
161
169
|
.row.child { padding-left: 28px; }
|
|
162
170
|
.row:hover { background: var(--bg-hover); }
|
|
@@ -341,7 +349,7 @@
|
|
|
341
349
|
<script src="./morphdom.min.js"></script>
|
|
342
350
|
<script src="./prism.min.js"></script>
|
|
343
351
|
<script>
|
|
344
|
-
const defaultRunLayout = () => ({ swimHeight: 220, ganttCollapsed: false, agentsCollapsed: false, logsCollapsed: false });
|
|
352
|
+
const defaultRunLayout = () => ({ swimHeight: 220, ganttCollapsed: false, agentsCollapsed: false, callsCollapsed: false, logsCollapsed: false });
|
|
345
353
|
const maxSwimHeight = () => Math.max(72, window.innerHeight * 0.7);
|
|
346
354
|
function loadRunLayout() {
|
|
347
355
|
const fallback = defaultRunLayout();
|
|
@@ -354,6 +362,7 @@
|
|
|
354
362
|
swimHeight: Number.isFinite(value.swimHeight) && value.swimHeight >= 72 ? value.swimHeight : fallback.swimHeight,
|
|
355
363
|
ganttCollapsed: typeof value.ganttCollapsed === "boolean" ? value.ganttCollapsed : fallback.ganttCollapsed,
|
|
356
364
|
agentsCollapsed: typeof value.agentsCollapsed === "boolean" ? value.agentsCollapsed : fallback.agentsCollapsed,
|
|
365
|
+
callsCollapsed: typeof value.callsCollapsed === "boolean" ? value.callsCollapsed : fallback.callsCollapsed,
|
|
357
366
|
logsCollapsed: typeof value.logsCollapsed === "boolean" ? value.logsCollapsed : fallback.logsCollapsed,
|
|
358
367
|
};
|
|
359
368
|
} catch {
|
|
@@ -372,7 +381,7 @@
|
|
|
372
381
|
}
|
|
373
382
|
}
|
|
374
383
|
function saveSidebarCollapsed() { try { localStorage.setItem("traj-sidebar-collapsed", JSON.stringify([...state.sidebarCollapsed])); } catch { /* Preferences are optional. */ } }
|
|
375
|
-
const state = { publishers: [], currentPub: null, currentRun: null, currentTarget: null, currentAgent: null, pendingTarget: null, agentRange: null, agentRangeAgent: null, selectedEvent: null, timelineHighlight: null, eventsSig: null, toolPane: "summary", sysPane: "prompt", inspMode: "event", inspSig: null, socket: null, transcripts: {}, transcriptPending: {}, transcriptSources: {}, transcriptRefresh: {}, transcriptRevision: {}, transcriptStatus: {}, transcriptCacheOrder: [], actionTargets: new Map(), shareUrls: {}, runLayout: loadRunLayout(), sidebarCollapsed: loadSidebarCollapsed(), workflowArgumentsOpen: new Set() };
|
|
384
|
+
const state = { publishers: [], currentPub: null, currentRun: null, currentTarget: null, currentAgent: null, pendingTarget: null, agentRange: null, agentRangeAgent: null, selectedEvent: null, timelineHighlight: null, eventsSig: null, toolPane: "summary", sysPane: "prompt", inspMode: "event", inspSig: null, socket: null, transcripts: {}, transcriptPending: {}, transcriptSources: {}, transcriptRefresh: {}, transcriptRevision: {}, transcriptStatus: {}, transcriptCacheOrder: [], actionTargets: new Map(), shareUrls: {}, runLayout: loadRunLayout(), sidebarCollapsed: loadSidebarCollapsed(), workflowArgumentsOpen: new Set(), callsOpen: new Set() };
|
|
376
385
|
let hasAcceptedState = false;
|
|
377
386
|
let socketGeneration = 0;
|
|
378
387
|
let socketReconnect;
|
|
@@ -676,6 +685,8 @@
|
|
|
676
685
|
const style = running ? `left:${Math.max(0, left / span * 100)}%;width:0%` : barStyle(Math.max(0, left), agent.durationMs || 0, 0, span);
|
|
677
686
|
lanes.push({ key: `agent:${agent.id}`, html: `<div class="lane${agent.parentId ? " child" : ""}" data-agent="${esc(agent.id)}">${glyph(agent.state)}<div class="lab">${esc(agent.label || agent.name)}</div><div class="track"><i class="bar ${stateClass(agent.state)}" style="${style}"${running ? ` data-clock-bar-start="${agent.startedAt || start}" data-clock-bar-min="2"` : ""}></i>${toolBars}</div></div>` });
|
|
678
687
|
}
|
|
688
|
+
const calls = scriptCalls(record);
|
|
689
|
+
if (calls.length) lanes.push({ key: "calls", html: `<div class="lane crumb"><span></span><div class="lab">script</div><div class="track">${calls.map((call) => { const status = callState(record, call); const running = status === "running"; const title = `${callLabel(call)} · ${status}${running ? "" : ` · ${callDuration(call)}`}`; return `<i class="bar tool ${stateClass(status)}" style="${running ? `left:${Math.max(0, (call.startedAt - start) / span * 100)}%;width:0.5%` : barStyle(call.startedAt - start, (call.finishedAt ?? call.startedAt) - call.startedAt, 0.5, span)}"${running ? ` data-clock-bar-start="${call.startedAt}" data-clock-bar-min="0.5"` : ""} title="${esc(title)}"></i>`; }).join("")}</div></div>` });
|
|
679
690
|
return [{ key: "axis", html: `<div class="axis"><div></div><div class="ticks"><span>${fmtClock(start)}</span><span data-clock-axis="mid">${live ? "" : `+${fmtRuntime(span / 2)}`}</span><span data-clock-axis="end">${live ? "" : "end"}</span></div></div>` }, ...lanes];
|
|
680
691
|
}
|
|
681
692
|
// Only the lanes whose markup changed are re-parsed; the quantised span keeps the others byte-identical between frames.
|
|
@@ -721,15 +732,26 @@
|
|
|
721
732
|
const phaseAgents = phase.agents.flatMap((id) => { const agent = byId.get(id); return agent ? [agent] : []; });
|
|
722
733
|
const groups = agentGridGroups(phaseAgents);
|
|
723
734
|
const showScopes = groups.length > 1 || groups.some(({ label }) => label !== "Agents");
|
|
724
|
-
return `<div class="phaseband">PHASE ${esc(phase.name)}</div>${groups.map((group) => `${showScopes ? `<div class="agent-grid-scope">${esc(group.label)}</div>` : ""}${group.agents.map((agent) => { const depth = agentDepth(agent, byId); const label = agent.label || agent.name; const settled = depth ? "•" : "▾"; const indent = (showScopes ? 12 : 0) + depth * 12; const toolSummary = toolTimingSummary(timingsByAgent.get(agent.id) || []); return `<div class="agent-grid-row${agent.id === state.currentAgent ? " on" : ""}" data-agent="${esc(agent.id)}"><div class="agent-cell" style="padding-left:${indent}px">${settled} ${esc(label)}</div><div class="agent-cell">${esc(agentModel(agent))}</div><div class="agent-cell"
|
|
735
|
+
return `<div class="phaseband">PHASE ${esc(phase.name)}</div>${groups.map((group) => `${showScopes ? `<div class="agent-grid-scope">${esc(group.label)}</div>` : ""}${group.agents.map((agent) => { const depth = agentDepth(agent, byId); const label = agent.label || agent.name; const settled = depth ? "•" : "▾"; const indent = (showScopes ? 12 : 0) + depth * 12; const toolSummary = toolTimingSummary(timingsByAgent.get(agent.id) || []); return `<div class="agent-grid-row${agent.id === state.currentAgent ? " on" : ""}" data-agent="${esc(agent.id)}"><div class="agent-cell" style="padding-left:${indent}px">${settled} ${esc(label)}</div><div class="agent-cell">${esc(agentModel(agent))}</div><div class="agent-cell" title="${esc(toolSummary)}">${esc(toolSummary)}</div><div class="agent-cell num">${fmtRuntime(agent.durationMs || 0)}</div><div class="agent-cell num">${fmtTokens(accounting(agent).input + accounting(agent).output)}</div><div class="agent-cell num">${fmtCost(accounting(agent).cost)}</div></div>`; }).join("")}`).join("")}`;
|
|
725
736
|
}).join("");
|
|
726
|
-
return `<div class="agent-grid"><div class="agent-grid-head"><span>AGENT</span><span>MODEL</span><span>
|
|
737
|
+
return `<div class="agent-grid"><div class="agent-grid-head"><span>AGENT</span><span>MODEL</span><span>TOOL TIMING</span><span>DURATION</span><span>TOKENS</span><span>COST</span></div>${body}</div>`;
|
|
727
738
|
}
|
|
728
739
|
function renderLogs(record) { const events = record.run.events || []; const logs = events.filter((event) => event.type === "log"); const omitted = events.find((event) => event.type === "trajectory:truncated"); if (!logs.length && !omitted) return ""; const collapsed = state.runLayout.logsCollapsed; return `<section class="logs"><button type="button" class="sec section-toggle" data-toggle="logs" aria-expanded="${!collapsed}">${collapsed ? "▸" : "▾"} LOGS</button>${collapsed ? "" : `<div class="logs-list">${logs.map((event) => `<div class="log-row"><span class="log-time">${esc(fmtClock(event.timestamp))}</span><span class="log-message">${esc(event.message)}</span></div>`).join("")}${omitted ? `<div class="log-row"><span class="log-time">—</span><span class="log-message">${esc(omitted.omitted)} events omitted from live state</span></div>` : ""}</div>`}</section>`; }
|
|
740
|
+
const scriptCalls = (record) => (record.run.scriptCalls || []).filter((call) => call?.type !== "trajectory:truncated");
|
|
741
|
+
// The journal proves completion even if a crash prevented the finish metadata write.
|
|
742
|
+
const callState = (record, call) => call.output?.status === "completed" || call.output?.status === "available" || call.output?.status === "truncated" && call.output.kind === "result" ? "completed" : call.error ? (call.error.code === "CANCELLED" ? "stopped" : "failed") : call.finishedAt !== undefined ? "completed" : record.run.state === "running" || record.run.state === "pausing" ? "running" : "interrupted";
|
|
743
|
+
const callDuration = (call) => { if (call.finishedAt === undefined) return "—"; const ms = Math.max(0, call.finishedAt - call.startedAt); return ms < 1000 ? `${ms}ms` : fmtRuntime(ms); };
|
|
744
|
+
const callLabel = (call) => call.kind === "shell" ? "shell" : `tools.${call.name}`;
|
|
745
|
+
const callPreview = (call) => call.kind === "tool" ? call.error?.code || "" : typeof call.input === "string" ? call.input : JSON.stringify(call.input);
|
|
746
|
+
function renderCalls(record) {
|
|
747
|
+
const calls = scriptCalls(record); const omitted = (record.run.scriptCalls || []).find((call) => call?.type === "trajectory:truncated"); if (!calls.length && !omitted) return ""; const collapsed = state.runLayout.callsCollapsed;
|
|
748
|
+
const rows = calls.map((call) => { const key = `${state.currentRun}\t${call.path}`; const status = callState(record, call); const duration = status === "running" ? `<span data-clock-kind="runtime" data-clock-start="${call.startedAt}" data-clock-end="live"></span>` : call.finishedAt === undefined ? "—" : esc(callDuration(call)); const input = call.kind !== "shell" ? "" : call.inputBytes !== undefined ? `<div class="hint">Input truncated (${esc(call.inputBytes)} bytes).</div><div class="code">${esc(call.input)}</div>` : `<div class="code">${highlightCode(json(call.input), "json")}</div>`; return `<details class="call"${state.callsOpen.has(key) ? " open" : ""} data-call="${esc(key)}"><summary class="call-row">${glyph(status)}<span class="log-time">${esc(fmtClock(call.startedAt))}</span><b>${esc(callLabel(call))}</b><span class="call-preview">${esc(callPreview(call))}</span><span class="num">${duration}</span></summary><div class="call-body">${call.phase ? `<div class="kv"><div class="k">Phase</div><div>${esc(call.phase)}</div></div>` : ""}${call.kind === "shell" ? `<div class="sec">INPUT</div>${input}<div class="sec">OUTPUT</div>${renderOutputPane(call)}` : `<div class="kv"><div class="k">Status</div><div>${esc(status)}</div>${call.error ? `<div class="k">Error</div><div>${esc(call.error.code)}</div>` : ""}</div>`}</div></details>`; }).join("");
|
|
749
|
+
return `<section class="logs"><button type="button" class="sec section-toggle" data-toggle="calls" aria-expanded="${!collapsed}">${collapsed ? "▸" : "▾"} CALLS</button>${collapsed ? "" : `<div class="calls-list">${rows}${omitted ? `<div class="log-row"><span class="log-time">—</span><span class="log-message">${esc(omitted.omitted)} calls omitted from live state</span></div>` : ""}</div>`}</section>`;
|
|
750
|
+
}
|
|
729
751
|
function renderAgentSection(record, timingsByAgent) { const collapsed = state.runLayout.agentsCollapsed; return `<button type="button" class="sec section-toggle" data-toggle="agents" aria-expanded="${!collapsed}">${collapsed ? "▸" : "▾"} AGENTS</button>${collapsed ? "" : renderAgentGrid(record, timingsByAgent)}`; }
|
|
730
|
-
function renderList(record, timingsByAgent) { const run = record.run; const completed = run.agents.filter((agent) => agent.state === "completed").length; return `<section class="workflow-card"><button type="button" class="workflow-head on" data-run="${esc(state.currentRun)}">${glyph(run.state)}<span class="workflow-name">${esc(run.workflowName)}</span><span class="workflow-meta">${completed}/${run.agents.length} agents · ${fmtCost(runCost(record))} · <span data-clock-kind="runtime" data-clock-start="${startOf(record).getTime()}" data-clock-end="${run.state === "running" ? "live" : endOf(record)}"></span></span></button>${renderAgentSection(record, timingsByAgent)}${renderLogs(record)}</section>`; }
|
|
752
|
+
function renderList(record, timingsByAgent) { const run = record.run; const completed = run.agents.filter((agent) => agent.state === "completed").length; return `<section class="workflow-card"><button type="button" class="workflow-head on" data-run="${esc(state.currentRun)}">${glyph(run.state)}<span class="workflow-name">${esc(run.workflowName)}</span><span class="workflow-meta">${completed}/${run.agents.length} agents · ${fmtCost(runCost(record))} · <span data-clock-kind="runtime" data-clock-start="${startOf(record).getTime()}" data-clock-end="${run.state === "running" ? "live" : endOf(record)}"></span></span></button>${renderAgentSection(record, timingsByAgent)}${renderCalls(record)}${renderLogs(record)}</section>`; }
|
|
731
753
|
function actionButton(label, action, disabled) { return `<button class="btn-alt" data-act="${action}"${disabled ? " disabled" : ""}>${label}</button>`; }
|
|
732
|
-
function subagentAgent(subagent) { return { id: subagent.id, name: subagentLabel(subagent), label: subagentLabel(subagent),
|
|
754
|
+
function subagentAgent(subagent) { return { id: subagent.id, name: subagentLabel(subagent), label: subagentLabel(subagent), state: subagent.state, model: subagent.model, requestedModel: subagent.request.model, tools: subagent.tools || [], toolDefinitions: subagent.toolDefinitions || [], skills: subagent.request.skills || [], extensions: subagent.request.extensions || [], contextFiles: subagent.request.contextFiles || [], prompt: subagent.request.prompt, systemPrompt: subagent.attempt?.setup?.systemPrompt || subagent.request.prompt, attempts: subagent.attempts || 0, mode: subagent.mode, cwd: subagent.cwd, worktree: subagent.worktree, output: subagent.output || subagentOutputFallback(subagent), outcome: subagent.result !== undefined ? { kind: "result", value: subagent.result } : subagent.failure !== undefined ? { kind: "failure", value: subagent.failure } : subagent.attempt?.error !== undefined ? { kind: "failure", value: subagent.attempt.error } : undefined, finishedAt: subagent.finishedAt, attemptDetails: subagent.attempt ? [subagent.attempt] : [] }; }
|
|
733
755
|
function subagentOutputFallback(subagent) { if (subagent.result !== undefined) return { status: "available", value: subagent.result, bytes: 0 }; if (subagent.failure !== undefined) return { status: "failed", code: subagent.failure.code || "AGENT_FAILED", message: subagent.failure.message || "Subagent failed" }; if (subagent.state === "running") return { status: "pending" }; if (subagent.state === "completed") return { status: "unavailable" }; if (subagent.state === "stopped") return { status: "cancelled", code: subagent.error?.code || "CANCELLED", message: subagent.error?.message || "Subagent stopped" }; if (subagent.state === "failed") return { status: "failed", code: subagent.error?.code || "AGENT_FAILED", message: subagent.error?.message || "Subagent failed" }; return { status: "unavailable" }; }
|
|
734
756
|
function renderAgentStats(found, agent, isSubagent) {
|
|
735
757
|
const offline = !found.publisher.connected; const stateText = agent.state || "—"; const value = isSubagent ? subagentAccounting(found.record) : accounting(agent);
|
|
@@ -739,7 +761,7 @@
|
|
|
739
761
|
return `${glyph(stateText)} <button type="button" class="btn-alt agent-details" data-agent-details="1">Agent details</button> ${fmtCost(value.cost)} · ${fmtTokens(value.input + value.output)} · runtime ${clock}${controls ? `<span class="actions">${controls}</span>` : ""}${isSubagent && offline ? '<span class="offline">publisher offline — actions disabled</span>' : ""}`;
|
|
740
762
|
}
|
|
741
763
|
function renderWorkflowArguments(record, open = false, key = "") { const args = record.snapshot?.args; const incomplete = record.snapshotArgsTruncated === true; if (args === null || args === undefined && !incomplete) return ""; const status = incomplete ? ' <span class="ins-sub">incomplete</span>' : ""; const body = args === undefined ? '<div class="hint">Arguments unavailable in the live transport.</div>' : `<div class="code args">${highlightCode(json(args), "json")}</div>`; const warning = incomplete ? '<div class="workflow-args-warning">Arguments were truncated by the live transport; this value is incomplete.</div>' : ""; return `<details class="workflow-args"${open ? " open" : ""} data-workflow-args="${esc(key)}"><summary class="sec">ARGUMENTS${status}</summary>${body}${warning}</details>`; }
|
|
742
|
-
function renderDossier(publisher, record) { const offline = !publisher.connected; const issues = record.run.agents.filter((agent) => agent.state === "failed").map((agent) => `<div class="pending issue"><div class="ttl">${glyph(agent.state)} ${esc(agent.label || agent.name)} needs retry</div><div class="ins-sub">${esc(agent.attemptDetails?.at(-1)?.error?.message || record.run.error?.message || "agent failed")}</div><div class="actions">${actionButton("Retry", "retry", offline)}</div>${offline ? '<div class="offline">publisher offline — actions disabled</div>' : ""}</div>`).join(""); const pending = issues + (record.awaiting || []).map((item) => `<div class="pending" data-name="${esc(item.name)}"><div class="ttl">${glyph("awaiting_input")} checkpoint ${esc(item.name)}</div><div class="ins-sub">${esc(item.prompt)}</div><div class="actions">${actionButton("Approve", "checkpoint-approve", offline)}${actionButton("Reject", "checkpoint-reject", offline)}</div>${offline ? '<div class="offline">publisher offline — actions disabled</div>' : ""}</div>`).join(""); const run = record.run; const controls = (run.state === "running" || run.state === "awaiting_input" ? `${actionButton("Pause", "pause", offline)}${actionButton("Abort run", "stop", offline)}` : run.state === "pausing" ? `${actionButton("Cancel pause", "resume", offline)}${actionButton("Abort run", "stop", offline)}` : run.state === "paused" || run.state === "interrupted" || run.state === "budget_exhausted" ? `${actionButton("Resume", "resume", offline)}${actionButton("Abort run", "stop", offline)}` : "") + (window.__PIEWF_STATIC__ ? "" : actionButton("Share", "share", offline)); const rows = run.agents.map((agent) => `<tr data-agent="${esc(agent.id)}"><td>${glyph(agent.state)}</td><td><b>${esc(agent.label || agent.name)}</b
|
|
764
|
+
function renderDossier(publisher, record) { const offline = !publisher.connected; const issues = record.run.agents.filter((agent) => agent.state === "failed").map((agent) => `<div class="pending issue"><div class="ttl">${glyph(agent.state)} ${esc(agent.label || agent.name)} needs retry</div><div class="ins-sub">${esc(agent.attemptDetails?.at(-1)?.error?.message || record.run.error?.message || "agent failed")}</div><div class="actions">${actionButton("Retry", "retry", offline)}</div>${offline ? '<div class="offline">publisher offline — actions disabled</div>' : ""}</div>`).join(""); const pending = issues + (record.awaiting || []).map((item) => `<div class="pending" data-name="${esc(item.name)}"><div class="ttl">${glyph("awaiting_input")} checkpoint ${esc(item.name)}</div><div class="ins-sub">${esc(item.prompt)}</div><div class="actions">${actionButton("Approve", "checkpoint-approve", offline)}${actionButton("Reject", "checkpoint-reject", offline)}</div>${offline ? '<div class="offline">publisher offline — actions disabled</div>' : ""}</div>`).join(""); const run = record.run; const controls = (run.state === "running" || run.state === "awaiting_input" ? `${actionButton("Pause", "pause", offline)}${actionButton("Abort run", "stop", offline)}` : run.state === "pausing" ? `${actionButton("Cancel pause", "resume", offline)}${actionButton("Abort run", "stop", offline)}` : run.state === "paused" || run.state === "interrupted" || run.state === "budget_exhausted" ? `${actionButton("Resume", "resume", offline)}${actionButton("Abort run", "stop", offline)}` : "") + (window.__PIEWF_STATIC__ ? "" : actionButton("Share", "share", offline)); const rows = run.agents.map((agent) => `<tr data-agent="${esc(agent.id)}"><td>${glyph(agent.state)}</td><td><b>${esc(agent.label || agent.name)}</b></td><td class="num">${fmtTokens(accounting(agent).input + accounting(agent).output)}</td><td class="num">${fmtCost(accounting(agent).cost)}</td></tr>`).join(""); const script = record.snapshot?.script ? `<div class="sec">SCRIPT <span class="ins-sub">click to expand</span></div><div class="code script" data-script="1">${highlightCode(record.snapshot.script, "javascript")}</div>` : ""; const argumentsSection = renderWorkflowArguments(record, state.workflowArgumentsOpen.has(state.currentRun), state.currentRun); return `<div class="ins-head" data-run-panel="1">${glyph(run.state)}<b>${esc(run.workflowName)}</b><span class="ins-sub">${esc(run.state)}</span></div><div class="ins-body">${pending ? '<div class="sec">NEEDS ATTENTION</div>' + pending : ""}<div class="sec">STATE</div><div class="kv"><div class="k">Phase</div><div>${esc(run.phase || "—")}</div><div class="k">Active</div><div>${run.agents.filter((agent) => agent.state === "running").length} running · ${run.agents.filter((agent) => agent.state === "queued").length} queued · ${run.agents.filter((agent) => agent.state === "failed").length} failed</div><div class="k">Outcome</div><div>${esc(run.error ? `${run.error.code}: ${run.error.message}` : run.state === "completed" ? "completed" : attention(record) ? `${attention(record)} agent(s) need a human` : "—")}</div><div class="k">Cost</div><div>${fmtCost(runCost(record))}${run.budget?.costUsd?.hard !== undefined ? ` / ${fmtCost(run.budget.costUsd.hard)} hard` : ""}</div><div class="k">Tokens</div><div>${fmtTokens(runTokens(record))}${run.budget?.tokens?.hard !== undefined ? ` / ${fmtTokens(run.budget.tokens.hard)} hard` : ""}</div><div class="k">Started</div><div>${fmtClock(startOf(record))}</div><div class="k">Runtime</div><div data-clock-kind="runtime" data-clock-start="${startOf(record).getTime()}" data-clock-end="${run.state === "running" ? "live" : endOf(record)}"></div></div><div class="actions">${controls}</div>${state.shareUrls[run.id] ? `<div class="share-url"><input readonly value="${esc(state.shareUrls[run.id])}" /><button class="btn-alt" data-copy-share="${esc(state.shareUrls[run.id])}">Copy</button></div><div class="ins-sub">secret gist: anyone with the link can read it</div>` : ""}${script}${argumentsSection}<div class="sec">AGENTS</div><table class="acct"><thead><tr><th></th><th>agent</th><th class="num">tok</th><th class="num">cost</th></tr></thead><tbody>${rows}</tbody></table></div>`; }
|
|
743
765
|
function renderRunLayout() { const ganttCollapsed = state.runLayout.ganttCollapsed; const splitSwim = $("split-swim"); $("swim").classList.toggle("hidden", ganttCollapsed); splitSwim.classList.toggle("hidden", ganttCollapsed); const heading = $("toggle-gantt"); heading.textContent = `${ganttCollapsed ? "▸" : "▾"} GANTT`; heading.setAttribute("aria-expanded", String(!ganttCollapsed)); const swimHeight = Math.min(state.runLayout.swimHeight, maxSwimHeight()); document.documentElement.style.setProperty("--swim", `${swimHeight}px`); }
|
|
744
766
|
function clampSwimHeight() { renderRunLayout(); }
|
|
745
767
|
function renderHome() { const homePublisher = livePublishers().find((publisher) => publisher.id === state.currentPub); const hasSubagents = Boolean(homePublisher?.subagents?.length); $("canvas-pub").textContent = homePublisher ? homePublisher.id.slice(0, 8) : "—"; $("canvas-run").textContent = hasSubagents ? "SUBAGENTS" : "—"; renderedRunHtml.clear(); ganttOrder = ""; patch($("swim-content"), hasSubagents ? renderSubagentGantt(homePublisher) : ""); $("swim").dataset.timelineHasTime = hasSubagents ? "true" : "false"; $("swim").dataset.timelineRunning = String(Boolean(hasSubagents && homePublisher.subagents.some((subagent) => subagent.state === "running"))); if (hasSubagents) { const subagents = homePublisher.subagents || []; const start = Math.min(...subagents.map((subagent) => subagentStartOf(subagent))); const end = Math.max(start + 1, ...subagents.map((subagent) => subagentEndOf(subagent))); $("swim").dataset.timelineStart = String(start); $("swim").dataset.timelineSpan = String(Math.max(1, end - start)); $("swim").querySelector(":scope > .now")?.classList.remove("hidden"); } else { $("swim").querySelector(":scope > .now")?.classList.add("hidden"); } patch($("list"), hasSubagents ? '<div class="hint">Select a subagent to inspect its transcript.</div>' : ""); patch($("run-stats"), hasSubagents ? `${homePublisher.subagents.length} subagents` : ""); $("run-crumb").textContent = "—"; $("agent-crumb").textContent = "—"; patch($("agent-ticks"), ""); $("agent-timeline").dataset.timelineHasTime = "false"; $("agent-timeline").querySelector(":scope > .now")?.classList.add("hidden"); $("agent-timeline").querySelector(":scope > .brush")?.classList.add("hidden"); patch($("events"), ""); $("q").value = ""; $("reset-agent-range").classList.add("hidden"); patch($("insp"), hasSubagents ? '<div class="ins-body hint">Select a subagent.</div>' : '<div class="ins-body hint">Select a run.</div>'); state.inspSig = null; renderRunLayout(); }
|
|
@@ -764,9 +786,9 @@
|
|
|
764
786
|
const { skillFilters, skillResolved, extensionFilters, extensionResolved } = sessionResources(agent);
|
|
765
787
|
const attempt = agent.attemptDetails?.at(-1);
|
|
766
788
|
const attempts = (agent.attemptDetails || []).map((detail) => ({ attempt: detail.attempt, transport: detail.transport, cwd: detail.setup?.cwd, model: detail.setup?.model, tools: detail.setup?.tools, ...(detail.error ? { error: detail.error } : {}) }));
|
|
767
|
-
const environment = {
|
|
789
|
+
const environment = { requestedModel: agent.requestedModel, model: agent.model, cwd: agent.cwd || attempt?.setup?.cwd || selected()?.record?.run?.cwd, mode: agent.mode, contextFiles: agent.contextFiles, worktree: agent.worktree || (agent.worktreeOwner ? { owner: agent.worktreeOwner } : undefined), structuralPath: agent.structuralPath, parentId: agent.parentId, parentBreadcrumb: agent.parentBreadcrumb, handle: agent.handle, turn: agent.turn, continuity: agent.continuity, state: agent.state, attempts: agent.attempts, attempt: attempt?.attempt, attemptDetails: attempts };
|
|
768
790
|
const panes = {
|
|
769
|
-
prompt: () => `<div class="kv"><div class="k">Tokens</div><div>est. ${estTokens(prompt)}</div></div><div class="
|
|
791
|
+
prompt: () => `<div class="kv"><div class="k">Tokens</div><div>est. ${estTokens(prompt)}</div></div><div class="code">${promptAvailable ? esc(prompt) : "Prompt unavailable"}</div>`,
|
|
770
792
|
tools: () => (agent.tools || []).map((tool) => { const description = toolDescriptionOf(agent, tool); return `<div class="tool-item"><b>${esc(tool)}</b>${description ? `<span>${esc(description)}</span>` : ""}</div>`; }).join("") || '<div class="hint">No tools recorded.</div>',
|
|
771
793
|
skills: () => `<div class="sec">FILTERS</div>${nameList(skillFilters)}<div class="sec">RESOLVED</div>${resourceList(skillResolved)}`,
|
|
772
794
|
extensions: () => `<div class="sec">FILTERS</div>${nameList(extensionFilters)}<div class="sec">RESOLVED</div>${resourceList(extensionResolved)}`,
|
|
@@ -775,7 +797,7 @@
|
|
|
775
797
|
};
|
|
776
798
|
patch($("sys-pane"), (panes[state.sysPane] || panes.prompt)()); $("sys-tabs").querySelectorAll("span").forEach((tab) => tab.classList.toggle("on", tab.dataset.pane === state.sysPane));
|
|
777
799
|
}
|
|
778
|
-
function renderSystemPromptPane(agent, entry) { const prompt = typeof entry?.prompt === "string" ? entry.prompt : typeof agent.systemPrompt === "string" ? agent.systemPrompt : ""; const available = typeof entry?.prompt === "string" || typeof agent.systemPrompt === "string"; patch($("sys-pane"), `<div class="kv"><div class="k">Tokens</div><div>est. ${estTokens(prompt)}</div></div><div class="
|
|
800
|
+
function renderSystemPromptPane(agent, entry) { const prompt = typeof entry?.prompt === "string" ? entry.prompt : typeof agent.systemPrompt === "string" ? agent.systemPrompt : ""; const available = typeof entry?.prompt === "string" || typeof agent.systemPrompt === "string"; patch($("sys-pane"), `<div class="kv"><div class="k">Tokens</div><div>est. ${estTokens(prompt)}</div></div><div class="code">${available ? esc(prompt) : "System prompt unavailable"}</div>`); }
|
|
779
801
|
function renderAssistantToolCalls(entry, entries) { const toolCalls = toolCallsOf(entry); return toolCalls.length ? `<div class="assistant-tools">${toolCalls.map((call) => `<div class="assistant-tool"><span class="pill tool">${esc(call.name)}</span><div class="code">${highlightCode(json(toolArgsOf(call, entries)), "json")}</div></div>`).join("")}</div>` : ""; }
|
|
780
802
|
function renderAgentDetails(agent, root = $("insp")) { patch(root, `<div class="ins-head"><span class="pill context">AGENT</span><b class="tool-name">${esc(agent.label || agent.name)}</b><span class="ins-sub">${esc(agent.state || "")}</span></div><div class="itabs" id="sys-tabs"><span data-pane="prompt">Prompt</span><span data-pane="tools">Tools</span><span data-pane="skills">Skills</span><span data-pane="extensions">Extensions</span><span data-pane="env">Environment</span><span data-pane="output">Output</span></div><div class="ins-body"><div id="sys-pane"></div></div>`); renderSystemPane(agent); }
|
|
781
803
|
function renderInspector(detail, agent, entries, root = $("insp")) { if (!detail) { patch(root, '<div class="ins-body hint">Select an event.</div>'); return; } if (detail.kind === "tool" || detail.kind === "skill") { patch(root, `<div class="ins-head"><span class="pill ${detail.kind}">${eventLabel(detail.kind)}</span><b class="tool-name">${esc(toolNameOf(detail.entry))}</b><span class="ins-sub">${esc(detail.entry.timestamp || "")}</span></div><div class="itabs" id="tool-tabs"><span data-pane="summary">Summary</span><span data-pane="payload">Input</span><span data-pane="result">Output</span><span data-pane="schema">Schema</span><span data-pane="timing">Timing</span></div><div class="ins-body"><div id="tool-pane"></div></div>`); renderToolPane(detail, entries); return; } if (detail.kind === "system") { const prompt = typeof detail.entry.prompt === "string" ? detail.entry.prompt : agent.systemPrompt; patch(root, `<div class="ins-head"><span class="pill system">SYSTEM</span><span class="ins-sub">est. ${estTokens(prompt || "")} tok</span></div><div class="ins-body"><div id="sys-pane"></div></div>`); renderSystemPromptPane(agent, detail.entry); return; } const message = detail.entry.message || detail.entry; const body = detail.kind === "assistant" ? assistantBodyText(message.content ?? detail.entry.content) : contentText(message.content ?? detail.entry.content); const tokenRows = messageTokenRows(detail.kind, message, body); const bodyHtml = detail.kind === "assistant" ? sanitizeMarkdown(marked.parse(body)) : esc(body); const tokenHtml = tokenRows ? `<div class="kv">${tokenRows}</div>` : ""; const assistantTools = detail.kind === "assistant" ? renderAssistantToolCalls(detail.entry, entries) : ""; patch(root, `<div class="ins-head"><span class="pill ${detail.kind}">${eventLabel(detail.kind)}</span></div><div class="ins-body">${tokenHtml}<div class="${detail.kind === "assistant" ? "md" : "code"}">${bodyHtml}</div>${assistantTools}</div>`); }
|
|
@@ -862,8 +884,8 @@
|
|
|
862
884
|
});
|
|
863
885
|
}
|
|
864
886
|
$("script-close").addEventListener("click", closeScript); $("script-bg").addEventListener("click", closeScript); document.addEventListener("keydown", (event) => { if (event.key === "Escape") { closeScript(); setAgentRange(null); if (document.body.dataset.view === "run") renderRun(); else renderAgent(); } });
|
|
865
|
-
document.addEventListener("toggle", (event) => { const target = event.target; if (!(target instanceof HTMLDetailsElement)
|
|
866
|
-
document.querySelector(".app").addEventListener("click", (event) => { const target = event.target.closest("[data-sidebar-group], [data-sidebar-section], [data-copy-share], [data-act], [data-run], [data-subagent], [data-agent], [data-agent-details], [data-script], [data-pane], [data-toggle]"); if (!target) return; if (target.dataset.copyShare) { const input = target.previousElementSibling; navigator.clipboard?.writeText(target.dataset.copyShare).then(() => showToast("copied"), () => { input?.select(); showToast("press Ctrl+C to copy"); }); return; } if (target.dataset.sidebarGroup !== undefined || target.dataset.sidebarSection !== undefined) { const key = target.dataset.sidebarGroup ?? target.dataset.sidebarSection; if (state.sidebarCollapsed.has(key)) state.sidebarCollapsed.delete(key); else state.sidebarCollapsed.add(key); saveSidebarCollapsed(); renderSidebar(); return; } if (target.dataset.toggle) { if (document.body.dataset.view !== "run") return; const key = target.dataset.toggle === "gantt" ? "ganttCollapsed" : target.dataset.toggle === "agents" ? "agentsCollapsed" : target.dataset.toggle === "logs" ? "logsCollapsed" : ""; if (!key) return; state.runLayout[key] = !state.runLayout[key]; saveRunLayout(); renderRun(); return; } if (target.dataset.act) { if (target.dataset.act === "steer") { const input = $("subagent-steer"); const message = input?.value.trim(); if (!message) return; sendAction("steer", { payload: { message } }); if (input) input.value = ""; return; } const name = target.closest("[data-name]")?.dataset.name; sendAction(target.dataset.act, name ? { name } : {}); return; } if (target.dataset.agentDetails !== undefined) { state.inspMode = "agent"; state.inspSig = null; renderAgent(); return; } if (target.dataset.script) { openScript(); return; } if (target.dataset.pane) { const found = selected(); const agent = found ? found.target.kind === "subagent" ? subagentAgent(found.record) : found.record.run.agents.find((candidate) => candidate.id === state.currentAgent) : undefined; const entries = found && agent ? agentEntries(found.record, agent) : []; if (target.closest("#tool-tabs")) { state.toolPane = target.dataset.pane; state.inspMode = "event"; renderInspector(entries[state.selectedEvent || 0] ? entryDetails(entries[state.selectedEvent || 0], agent, entries) : undefined, agent, entries); } else { state.sysPane = target.dataset.pane; state.inspMode = "agent"; if (agent) renderSystemPane(agent); } return; } if (target.dataset.run) { selectRun(target.dataset.run); return; } if (target.dataset.subagent) { selectSubagent(target.dataset.subagent); return; } if (target.dataset.agent && document.body.dataset.view === "run") { state.currentAgent = target.dataset.agent; state.selectedEvent = null; state.inspMode = "event"; setView("agent"); } });
|
|
887
|
+
document.addEventListener("toggle", (event) => { const target = event.target; if (!(target instanceof HTMLDetailsElement)) return; const key = target.dataset.workflowArgs ?? target.dataset.call; const open = target.dataset.workflowArgs !== undefined ? state.workflowArgumentsOpen : target.dataset.call !== undefined ? state.callsOpen : undefined; if (!open) return; if (target.open) open.add(key); else open.delete(key); }, true);
|
|
888
|
+
document.querySelector(".app").addEventListener("click", (event) => { const target = event.target.closest("[data-sidebar-group], [data-sidebar-section], [data-copy-share], [data-act], [data-run], [data-subagent], [data-agent], [data-agent-details], [data-script], [data-pane], [data-toggle]"); if (!target) return; if (target.dataset.copyShare) { const input = target.previousElementSibling; navigator.clipboard?.writeText(target.dataset.copyShare).then(() => showToast("copied"), () => { input?.select(); showToast("press Ctrl+C to copy"); }); return; } if (target.dataset.sidebarGroup !== undefined || target.dataset.sidebarSection !== undefined) { const key = target.dataset.sidebarGroup ?? target.dataset.sidebarSection; if (state.sidebarCollapsed.has(key)) state.sidebarCollapsed.delete(key); else state.sidebarCollapsed.add(key); saveSidebarCollapsed(); renderSidebar(); return; } if (target.dataset.toggle) { if (document.body.dataset.view !== "run") return; const key = target.dataset.toggle === "gantt" ? "ganttCollapsed" : target.dataset.toggle === "agents" ? "agentsCollapsed" : target.dataset.toggle === "calls" ? "callsCollapsed" : target.dataset.toggle === "logs" ? "logsCollapsed" : ""; if (!key) return; state.runLayout[key] = !state.runLayout[key]; saveRunLayout(); renderRun(); return; } if (target.dataset.act) { if (target.dataset.act === "steer") { const input = $("subagent-steer"); const message = input?.value.trim(); if (!message) return; sendAction("steer", { payload: { message } }); if (input) input.value = ""; return; } const name = target.closest("[data-name]")?.dataset.name; sendAction(target.dataset.act, name ? { name } : {}); return; } if (target.dataset.agentDetails !== undefined) { state.inspMode = "agent"; state.inspSig = null; renderAgent(); return; } if (target.dataset.script) { openScript(); return; } if (target.dataset.pane) { const found = selected(); const agent = found ? found.target.kind === "subagent" ? subagentAgent(found.record) : found.record.run.agents.find((candidate) => candidate.id === state.currentAgent) : undefined; const entries = found && agent ? agentEntries(found.record, agent) : []; if (target.closest("#tool-tabs")) { state.toolPane = target.dataset.pane; state.inspMode = "event"; renderInspector(entries[state.selectedEvent || 0] ? entryDetails(entries[state.selectedEvent || 0], agent, entries) : undefined, agent, entries); } else { state.sysPane = target.dataset.pane; state.inspMode = "agent"; if (agent) renderSystemPane(agent); } return; } if (target.dataset.run) { selectRun(target.dataset.run); return; } if (target.dataset.subagent) { selectSubagent(target.dataset.subagent); return; } if (target.dataset.agent && document.body.dataset.view === "run") { state.currentAgent = target.dataset.agent; state.selectedEvent = null; state.inspMode = "event"; setView("agent"); } });
|
|
867
889
|
document.addEventListener("visibilitychange", () => { sendFocus(); if (!document.hidden) scheduleRender(); });
|
|
868
890
|
// Phones show one sheet at a time, so picking something moves to the sheet that shows its result.
|
|
869
891
|
const phone = matchMedia("(max-width: 900px)");
|