@tt-a1i/openpi 0.4.0 → 0.5.0
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/README.md +102 -40
- package/SETUP.md +22 -6
- package/assets/openpi-launch-card-v1.webp +0 -0
- package/bin/openpi.js +145 -0
- package/extensions/background-terminals/index.ts +30 -2
- package/extensions/background-terminals/src/domain.ts +2 -0
- package/extensions/background-terminals/src/manager.ts +486 -106
- package/extensions/background-terminals/src/output.ts +33 -0
- package/extensions/background-terminals/src/prompt.ts +13 -5
- package/extensions/background-terminals/src/result-delivery.ts +4 -1
- package/extensions/clear-context/index.ts +83 -0
- package/extensions/context-pivot/index.ts +16 -6
- package/extensions/cron/schedule.ts +7 -1
- package/extensions/file-mutation-display/render.ts +17 -257
- package/extensions/file-search/src/binaries.ts +57 -41
- package/extensions/git-read/index.ts +1 -3
- package/extensions/model-info/index.ts +21 -33
- package/extensions/model-info/session-metrics.ts +96 -0
- package/extensions/plan-mode/bash-policy.ts +54 -9
- package/extensions/plan-mode/index.ts +7 -2
- package/extensions/post-edit/index.ts +16 -6
- package/extensions/sessions/git-stats.ts +258 -72
- package/extensions/sessions/index.ts +153 -86
- package/extensions/sessions/preview-cache.ts +104 -0
- package/extensions/sessions/preview-loader.ts +856 -0
- package/extensions/sessions/sessions.ts +43 -4
- package/extensions/setup/index.ts +123 -127
- package/extensions/shared/activity-status.ts +30 -0
- package/extensions/shared/agent-session-page.ts +319 -0
- package/extensions/shared/agent-tool-renderer.ts +218 -0
- package/extensions/shared/agent-transcript.ts +524 -0
- package/extensions/shared/capability-intent.ts +1 -1
- package/extensions/shared/child-session.ts +437 -21
- package/extensions/shared/result-delivery.ts +34 -0
- package/extensions/shared/setup-config.ts +73 -33
- package/extensions/shared/setup-episode-state.ts +1 -1
- package/extensions/shared/terminal-text.ts +110 -23
- package/extensions/shared/text-projection.ts +72 -15
- package/extensions/shared/tool-activity.ts +382 -0
- package/extensions/shared/tool-surface.ts +29 -2
- package/extensions/shared/transcript-viewport.ts +46 -0
- package/extensions/shared/web-observer-registry.ts +390 -0
- package/extensions/shared/worktree.ts +11 -0
- package/extensions/subagents/index.ts +270 -59
- package/extensions/subagents/navigation.ts +34 -5
- package/extensions/subagents/src/backend.ts +12 -1
- package/extensions/subagents/src/backends/pi.ts +375 -66
- package/extensions/subagents/src/domain.ts +5 -0
- package/extensions/subagents/src/manager.ts +34 -2
- package/extensions/subagents/src/prompt.ts +32 -4
- package/extensions/subagents/src/result-artifact.ts +4 -0
- package/extensions/subagents/src/result-delivery.ts +7 -1
- package/extensions/subagents/src/runtime.ts +15 -1
- package/extensions/subagents/src/ui/takeover.ts +73 -257
- package/extensions/subagents/src/ui/transcript.ts +38 -535
- package/extensions/subagents/src/ui/wait-result.ts +103 -15
- package/extensions/suggestions/src/ui.ts +10 -4
- package/extensions/tasks/index.ts +0 -3
- package/extensions/ui-customization/footer.ts +0 -40
- package/extensions/ui-customization/index.ts +0 -4
- package/extensions/user-input-fold/index.ts +1 -1
- package/extensions/web/index.ts +234 -0
- package/extensions/workflows/artifacts.ts +137 -47
- package/extensions/workflows/completion-projection.ts +457 -0
- package/extensions/workflows/coordinator.ts +8 -10
- package/extensions/workflows/dashboard.ts +167 -228
- package/extensions/workflows/handoff.ts +70 -16
- package/extensions/workflows/index.ts +488 -198
- package/extensions/workflows/journal.ts +148 -13
- package/extensions/workflows/model.ts +74 -4
- package/extensions/workflows/navigation.ts +32 -8
- package/extensions/workflows/progress-projection.ts +306 -0
- package/extensions/workflows/prompt.ts +66 -6
- package/extensions/workflows/replay-safety.ts +42 -21
- package/extensions/workflows/result-delivery.ts +128 -64
- package/extensions/workflows/retention.ts +593 -0
- package/extensions/workflows/runner.ts +388 -279
- package/extensions/workflows/sandbox-child.cjs +25 -3
- package/extensions/workflows/sandbox.ts +62 -8
- package/extensions/workflows/serialization.ts +325 -17
- package/extensions/workflows/tool-renderer.ts +22 -0
- package/extensions/workflows/transcript.ts +149 -0
- package/extensions/workspace-cleanup-guard/index.ts +54 -0
- package/extensions/workspace-cleanup-guard/workspace-provenance.ts +563 -0
- package/package.json +28 -8
- package/skills/subagents/REFERENCE.md +189 -0
- package/skills/subagents/SKILL.md +1 -1
- package/skills/workflows/REFERENCE.md +4 -2
- package/web/adapter/pi-adapter.ts +661 -0
- package/web/host/browser-launcher.ts +20 -0
- package/web/host/static-assets.ts +4 -0
- package/web/host/terminal-status.ts +38 -0
- package/web/host/web-host.ts +789 -0
- package/web/http-dispatcher.ts +125 -0
- package/web/protocol/types.ts +462 -0
- package/web/runtime/pi-runtime.ts +991 -0
- package/web/runtime/types.ts +71 -0
- package/web/runtime/web-host-lease.ts +497 -0
- package/web/trace.ts +18 -0
- package/web/ui/app.js +1398 -0
- package/web/ui/index.html +139 -0
- package/web/ui/styles.css +598 -0
- package/web/vite.config.mjs +34 -0
- package/extensions/execution-convergence/active-evidence.ts +0 -129
- package/extensions/execution-convergence/index.ts +0 -442
- package/extensions/execution-convergence/workspace-provenance.ts +0 -338
- package/extensions/setup/intercom-fs-helper.cjs +0 -130
- package/extensions/setup/intercom.ts +0 -603
- package/extensions/subagents/src/backends/stub.ts +0 -303
|
@@ -18,15 +18,10 @@ import {
|
|
|
18
18
|
getAgentDir,
|
|
19
19
|
type KeybindingsManager,
|
|
20
20
|
} from "@earendil-works/pi-coding-agent";
|
|
21
|
-
import {
|
|
22
|
-
|
|
23
|
-
matchesKey,
|
|
24
|
-
type TUI,
|
|
25
|
-
truncateToWidth,
|
|
26
|
-
wrapTextWithAnsi,
|
|
27
|
-
} from "@earendil-works/pi-tui";
|
|
28
|
-
import { contextPercent } from "../shared/context-utilization.ts";
|
|
21
|
+
import { type TUI, truncateToWidth } from "@earendil-works/pi-tui";
|
|
22
|
+
import { AgentSessionPage } from "../shared/agent-session-page.ts";
|
|
29
23
|
import { fitNavigationSides } from "../shared/below-editor-navigation.ts";
|
|
24
|
+
import { contextPercent } from "../shared/context-utilization.ts";
|
|
30
25
|
import {
|
|
31
26
|
panelFrame,
|
|
32
27
|
type ScreenHint,
|
|
@@ -68,10 +63,10 @@ import {
|
|
|
68
63
|
workflowGraphRecords,
|
|
69
64
|
} from "./model.ts";
|
|
70
65
|
import { writeFileAtomic } from "./serialization.ts";
|
|
66
|
+
import { WorkflowTranscriptAdapter } from "./transcript.ts";
|
|
71
67
|
|
|
72
68
|
const NOTICE_TTL_MS = 4000;
|
|
73
69
|
const MIN_HEIGHT = 10;
|
|
74
|
-
const TRANSCRIPT_SCROLL_STEP = 20;
|
|
75
70
|
|
|
76
71
|
function wrapSelection(index: number, delta: number, length: number): number {
|
|
77
72
|
if (length === 0) return 0;
|
|
@@ -261,6 +256,10 @@ function normalizeTranscript(value: unknown): TranscriptEntry[] {
|
|
|
261
256
|
typeof entry.name === "string"
|
|
262
257
|
? sanitizeLine(entry.name, 160) || undefined
|
|
263
258
|
: undefined,
|
|
259
|
+
toolCallId:
|
|
260
|
+
typeof entry.toolCallId === "string"
|
|
261
|
+
? sanitizeLine(entry.toolCallId, 1_024) || undefined
|
|
262
|
+
: undefined,
|
|
264
263
|
isError: entry.isError === true,
|
|
265
264
|
timestamp:
|
|
266
265
|
typeof entry.timestamp === "number" ? entry.timestamp : undefined,
|
|
@@ -269,6 +268,35 @@ function normalizeTranscript(value: unknown): TranscriptEntry[] {
|
|
|
269
268
|
return transcript;
|
|
270
269
|
}
|
|
271
270
|
|
|
271
|
+
function normalizeAgentState(value: unknown): AgentRecord["state"] {
|
|
272
|
+
switch (value) {
|
|
273
|
+
case "done":
|
|
274
|
+
case "completed":
|
|
275
|
+
return "done";
|
|
276
|
+
case "error":
|
|
277
|
+
case "failed":
|
|
278
|
+
return "error";
|
|
279
|
+
case "running":
|
|
280
|
+
case "uncertain":
|
|
281
|
+
return value;
|
|
282
|
+
default:
|
|
283
|
+
return "uncertain";
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function normalizeWorkflowStatus(value: unknown): WorkflowDetails["status"] {
|
|
288
|
+
switch (value) {
|
|
289
|
+
case "running":
|
|
290
|
+
case "completed":
|
|
291
|
+
case "failed":
|
|
292
|
+
case "aborted":
|
|
293
|
+
case "uncertain":
|
|
294
|
+
return value;
|
|
295
|
+
default:
|
|
296
|
+
return "uncertain";
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
272
300
|
/** Leniently normalize a workflow.json (including runs from older tooling). */
|
|
273
301
|
export function normalizePersistedWorkflowDetails(
|
|
274
302
|
runId: string,
|
|
@@ -284,14 +312,7 @@ export function normalizePersistedWorkflowDetails(
|
|
|
284
312
|
for (const item of rawAgents) {
|
|
285
313
|
if (!item || typeof item !== "object") continue;
|
|
286
314
|
const a = item as Record<string, unknown>;
|
|
287
|
-
const state =
|
|
288
|
-
a.state === "error" || a.state === "failed"
|
|
289
|
-
? "error"
|
|
290
|
-
: a.state === "uncertain"
|
|
291
|
-
? "uncertain"
|
|
292
|
-
: a.state === "running"
|
|
293
|
-
? "running"
|
|
294
|
-
: "done";
|
|
315
|
+
const state = normalizeAgentState(a.state);
|
|
295
316
|
const index = typeof a.index === "number" ? a.index : agents.length + 1;
|
|
296
317
|
const decodedInvocation = decodeInvocationRecord(a.invocation);
|
|
297
318
|
const invocation =
|
|
@@ -407,16 +428,23 @@ export function normalizePersistedWorkflowDetails(
|
|
|
407
428
|
logs.push({
|
|
408
429
|
at: typeof entry.at === "number" ? entry.at : startedAt,
|
|
409
430
|
text: sanitizeLine(entry.text, MAX_LOG_TEXT),
|
|
431
|
+
...(entry.kind === "pipeline-drop"
|
|
432
|
+
? { kind: "pipeline-drop" as const }
|
|
433
|
+
: {}),
|
|
410
434
|
});
|
|
411
435
|
}
|
|
412
436
|
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
437
|
+
let status = normalizeWorkflowStatus(record.status);
|
|
438
|
+
if (status !== "running") {
|
|
439
|
+
for (const agent of agents) {
|
|
440
|
+
if (agent.state !== "running" && agent.state !== "uncertain") continue;
|
|
441
|
+
status = "uncertain";
|
|
442
|
+
agent.state = "uncertain";
|
|
443
|
+
agent.error =
|
|
444
|
+
agent.error ??
|
|
445
|
+
"Persisted terminal workflow contained an agent without terminal evidence";
|
|
446
|
+
}
|
|
447
|
+
}
|
|
420
448
|
|
|
421
449
|
return {
|
|
422
450
|
runId,
|
|
@@ -541,22 +569,31 @@ export function loadRunEntries(
|
|
|
541
569
|
referencedRunIds: ReadonlySet<string>,
|
|
542
570
|
/** Hide runs untouched by the current request; live runs always show. */
|
|
543
571
|
startedSince = 0,
|
|
572
|
+
/** Bounded settled projections used only if canonical disk state is unreadable. */
|
|
573
|
+
retained: ReadonlyMap<string, WorkflowDetails> = new Map(),
|
|
544
574
|
): RunEntry[] {
|
|
545
575
|
const entries: RunEntry[] = [];
|
|
546
|
-
|
|
576
|
+
const runIds = new Set([...listPersistedRunIds(), ...retained.keys()]);
|
|
577
|
+
for (const runId of runIds) {
|
|
547
578
|
const live = active.get(runId);
|
|
548
579
|
if (live) {
|
|
549
580
|
entries.push({ runId, details: live, live: true });
|
|
550
581
|
continue;
|
|
551
582
|
}
|
|
552
|
-
const
|
|
583
|
+
const persisted = readPersistedWorkflowDetails(runId, {
|
|
553
584
|
hydrateArtifacts: true,
|
|
554
585
|
});
|
|
586
|
+
const retainedDetails = retained.get(runId);
|
|
587
|
+
const details = persisted ?? retainedDetails;
|
|
555
588
|
if (!details) continue;
|
|
589
|
+
const fromRetention =
|
|
590
|
+
persisted === undefined && retainedDetails !== undefined;
|
|
556
591
|
const touchedAt = Math.max(details.startedAt, details.finishedAt ?? 0);
|
|
557
592
|
if (
|
|
558
593
|
touchedAt < startedSince ||
|
|
559
|
-
(
|
|
594
|
+
(!fromRetention &&
|
|
595
|
+
details.sessionId !== sessionId &&
|
|
596
|
+
!referencedRunIds.has(runId))
|
|
560
597
|
) {
|
|
561
598
|
continue;
|
|
562
599
|
}
|
|
@@ -677,24 +714,24 @@ export class WorkflowDashboard {
|
|
|
677
714
|
private phaseIndex = 0;
|
|
678
715
|
private agentIndex = 0;
|
|
679
716
|
private detailFocus: DetailFocus = "phases";
|
|
680
|
-
private
|
|
681
|
-
private transcriptRowCount = 0;
|
|
682
|
-
private transcriptViewportSize = 1;
|
|
717
|
+
private transcriptPage?: AgentSessionPage;
|
|
683
718
|
private current?: RunEntry;
|
|
684
719
|
private openedDirectly = false;
|
|
685
720
|
private notice?: string;
|
|
686
721
|
private noticeAt = 0;
|
|
687
722
|
private disposed = false;
|
|
688
|
-
private timer
|
|
723
|
+
private timer?: ReturnType<typeof setInterval>;
|
|
689
724
|
private tui: TUI;
|
|
690
725
|
private theme: Theme;
|
|
691
726
|
private keybindings: KeybindingsManager;
|
|
692
727
|
private getActive: () => Map<string, WorkflowDetails>;
|
|
728
|
+
private getRetained: () => ReadonlyMap<string, WorkflowDetails>;
|
|
693
729
|
private sessionId: string;
|
|
694
730
|
private referencedRunIds: ReadonlySet<string>;
|
|
695
731
|
private startedSince: number;
|
|
696
732
|
private close: () => void;
|
|
697
733
|
private onAbort?: (runId: string) => boolean;
|
|
734
|
+
private initialToolsExpanded: boolean;
|
|
698
735
|
|
|
699
736
|
constructor(
|
|
700
737
|
tui: TUI,
|
|
@@ -707,16 +744,20 @@ export class WorkflowDashboard {
|
|
|
707
744
|
close: () => void,
|
|
708
745
|
initialRunId?: string,
|
|
709
746
|
onAbort?: (runId: string) => boolean,
|
|
747
|
+
getRetained: () => ReadonlyMap<string, WorkflowDetails> = () => new Map(),
|
|
748
|
+
initialToolsExpanded = false,
|
|
710
749
|
) {
|
|
711
750
|
this.tui = tui;
|
|
712
751
|
this.theme = theme;
|
|
713
752
|
this.keybindings = keybindings;
|
|
714
753
|
this.getActive = getActive;
|
|
754
|
+
this.getRetained = getRetained;
|
|
715
755
|
this.sessionId = sessionId;
|
|
716
756
|
this.referencedRunIds = referencedRunIds;
|
|
717
757
|
this.startedSince = startedSince;
|
|
718
758
|
this.close = close;
|
|
719
759
|
this.onAbort = onAbort;
|
|
760
|
+
this.initialToolsExpanded = initialToolsExpanded;
|
|
720
761
|
this.refresh();
|
|
721
762
|
if (initialRunId) {
|
|
722
763
|
const resolution = resolveWorkflowRunTarget(
|
|
@@ -736,25 +777,44 @@ export class WorkflowDashboard {
|
|
|
736
777
|
this.noticeAt = Date.now();
|
|
737
778
|
}
|
|
738
779
|
}
|
|
780
|
+
this.refreshTimer();
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
private refreshTimer() {
|
|
784
|
+
const active = Boolean(
|
|
785
|
+
this.notice ||
|
|
786
|
+
(this.view === "list"
|
|
787
|
+
? this.entries.some(
|
|
788
|
+
(entry) => entry.live && entry.details.status === "running",
|
|
789
|
+
)
|
|
790
|
+
: this.view === "detail"
|
|
791
|
+
? this.current?.live && this.current.details.status === "running"
|
|
792
|
+
: this.current?.live && this.selectedAgent()?.state === "running"),
|
|
793
|
+
);
|
|
794
|
+
if (!active) {
|
|
795
|
+
if (this.timer) clearInterval(this.timer);
|
|
796
|
+
this.timer = undefined;
|
|
797
|
+
return;
|
|
798
|
+
}
|
|
799
|
+
if (this.timer) return;
|
|
739
800
|
this.timer = setInterval(() => {
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
this.notice
|
|
744
|
-
) {
|
|
745
|
-
this.refresh();
|
|
746
|
-
this.tui.requestRender();
|
|
747
|
-
}
|
|
801
|
+
this.refresh();
|
|
802
|
+
this.tui.requestRender();
|
|
803
|
+
this.refreshTimer();
|
|
748
804
|
}, SPINNER_INTERVAL_MS);
|
|
749
805
|
}
|
|
750
806
|
|
|
751
807
|
dispose() {
|
|
752
808
|
if (this.disposed) return;
|
|
753
809
|
this.disposed = true;
|
|
754
|
-
clearInterval(this.timer);
|
|
810
|
+
if (this.timer) clearInterval(this.timer);
|
|
811
|
+
this.timer = undefined;
|
|
812
|
+
this.transcriptPage = undefined;
|
|
755
813
|
}
|
|
756
814
|
|
|
757
|
-
invalidate() {
|
|
815
|
+
invalidate() {
|
|
816
|
+
this.transcriptPage?.invalidate();
|
|
817
|
+
}
|
|
758
818
|
|
|
759
819
|
private refresh() {
|
|
760
820
|
const selected = this.entries[this.listIndex]?.runId;
|
|
@@ -763,6 +823,7 @@ export class WorkflowDashboard {
|
|
|
763
823
|
this.sessionId,
|
|
764
824
|
this.referencedRunIds,
|
|
765
825
|
this.startedSince,
|
|
826
|
+
this.getRetained(),
|
|
766
827
|
);
|
|
767
828
|
if (selected) {
|
|
768
829
|
const index = this.entries.findIndex((e) => e.runId === selected);
|
|
@@ -819,11 +880,12 @@ export class WorkflowDashboard {
|
|
|
819
880
|
const target = path.join(runsDir(), entry.runId, "report.md");
|
|
820
881
|
try {
|
|
821
882
|
writeFileAtomic(target, buildWorkflowReport(entry.details));
|
|
822
|
-
this.
|
|
883
|
+
this.setNotice(`saved ${shortenHome(target)}`);
|
|
823
884
|
} catch (error) {
|
|
824
|
-
this.
|
|
885
|
+
this.setNotice(
|
|
886
|
+
`save failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
887
|
+
);
|
|
825
888
|
}
|
|
826
|
-
this.noticeAt = Date.now();
|
|
827
889
|
}
|
|
828
890
|
|
|
829
891
|
/** Request cancellation of a run by id, surfacing the outcome as a notice. */
|
|
@@ -842,6 +904,7 @@ export class WorkflowDashboard {
|
|
|
842
904
|
private setNotice(text: string) {
|
|
843
905
|
this.notice = text;
|
|
844
906
|
this.noticeAt = Date.now();
|
|
907
|
+
this.refreshTimer();
|
|
845
908
|
}
|
|
846
909
|
|
|
847
910
|
handleInput(data: string) {
|
|
@@ -923,57 +986,27 @@ export class WorkflowDashboard {
|
|
|
923
986
|
} else if (left || cancel) {
|
|
924
987
|
this.detailFocus = "phases";
|
|
925
988
|
} else if ((right || confirm) && this.selectedAgent()) {
|
|
926
|
-
this.
|
|
927
|
-
this.view = "transcript";
|
|
989
|
+
this.openTranscriptPage();
|
|
928
990
|
}
|
|
929
991
|
}
|
|
930
992
|
if (data === "s") this.saveReport();
|
|
931
993
|
if (data === "x") this.abortRun(this.current);
|
|
932
994
|
} else {
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
);
|
|
937
|
-
const scrollStep =
|
|
938
|
-
data === "j" || data === "k" ? TRANSCRIPT_SCROLL_STEP : 1;
|
|
939
|
-
const pageStep = Math.max(1, this.transcriptViewportSize - 2);
|
|
940
|
-
if (up) {
|
|
941
|
-
this.transcriptScroll = Math.max(0, this.transcriptScroll - scrollStep);
|
|
942
|
-
} else if (down) {
|
|
943
|
-
this.transcriptScroll = Math.min(
|
|
944
|
-
maxScroll,
|
|
945
|
-
this.transcriptScroll + scrollStep,
|
|
946
|
-
);
|
|
947
|
-
} else if (matchesKey(data, Key.ctrl("u"))) {
|
|
948
|
-
this.transcriptScroll = Math.max(0, this.transcriptScroll - pageStep);
|
|
949
|
-
} else if (matchesKey(data, Key.ctrl("d"))) {
|
|
950
|
-
this.transcriptScroll = Math.min(
|
|
951
|
-
maxScroll,
|
|
952
|
-
this.transcriptScroll + pageStep,
|
|
953
|
-
);
|
|
954
|
-
} else if (data === "g") {
|
|
955
|
-
this.transcriptScroll = 0;
|
|
956
|
-
} else if (data === "G") {
|
|
957
|
-
this.transcriptScroll = maxScroll;
|
|
958
|
-
} else if (cancel || left) {
|
|
959
|
-
this.view = "detail";
|
|
960
|
-
this.detailFocus = "agents";
|
|
961
|
-
}
|
|
995
|
+
this.transcriptPage?.handleInput(data);
|
|
996
|
+
this.refreshTimer();
|
|
997
|
+
return;
|
|
962
998
|
}
|
|
999
|
+
this.refreshTimer();
|
|
963
1000
|
this.tui.requestRender();
|
|
964
1001
|
}
|
|
965
1002
|
|
|
966
1003
|
render(width: number): string[] {
|
|
1004
|
+
if (this.view === "transcript" && this.transcriptPage) {
|
|
1005
|
+
return this.transcriptPage.render(width);
|
|
1006
|
+
}
|
|
967
1007
|
const height = Math.max(MIN_HEIGHT, this.tui.terminal.rows - 1);
|
|
968
1008
|
let lines: string[];
|
|
969
|
-
if (this.view === "
|
|
970
|
-
lines = this.renderTranscript(
|
|
971
|
-
this.current.details,
|
|
972
|
-
this.selectedAgent()!,
|
|
973
|
-
width,
|
|
974
|
-
height,
|
|
975
|
-
);
|
|
976
|
-
} else if (this.view === "detail" && this.current) {
|
|
1009
|
+
if (this.view === "detail" && this.current) {
|
|
977
1010
|
lines = this.renderDetail(this.current.details, width, height);
|
|
978
1011
|
} else {
|
|
979
1012
|
lines = this.renderList(width, height);
|
|
@@ -981,6 +1014,53 @@ export class WorkflowDashboard {
|
|
|
981
1014
|
return lines.map((line) => truncateToWidth(line, width, ""));
|
|
982
1015
|
}
|
|
983
1016
|
|
|
1017
|
+
private openTranscriptPage() {
|
|
1018
|
+
const transcriptAdapter = new WorkflowTranscriptAdapter();
|
|
1019
|
+
this.view = "transcript";
|
|
1020
|
+
this.transcriptPage = new AgentSessionPage(
|
|
1021
|
+
this.tui,
|
|
1022
|
+
this.theme,
|
|
1023
|
+
this.keybindings,
|
|
1024
|
+
{
|
|
1025
|
+
getState: () => {
|
|
1026
|
+
const details = this.current?.details;
|
|
1027
|
+
const agent = this.selectedAgent();
|
|
1028
|
+
if (!details || !agent) return undefined;
|
|
1029
|
+
return {
|
|
1030
|
+
id: agent.callId ?? `agent-${agent.index}`,
|
|
1031
|
+
title: agent.label,
|
|
1032
|
+
status: agent.state,
|
|
1033
|
+
document: transcriptAdapter.document(
|
|
1034
|
+
agent.transcript,
|
|
1035
|
+
agent.worktreePath,
|
|
1036
|
+
),
|
|
1037
|
+
metadata: [
|
|
1038
|
+
`${details.name ?? details.runId} · ${agent.phase ?? "unphased"}`,
|
|
1039
|
+
agent.model,
|
|
1040
|
+
agentContext(agent),
|
|
1041
|
+
agent.acceptance
|
|
1042
|
+
? `acceptance:${agent.acceptance.status}`
|
|
1043
|
+
: undefined,
|
|
1044
|
+
formatElapsed(agent.startedAt, agent.finishedAt),
|
|
1045
|
+
],
|
|
1046
|
+
errorText: agent.error,
|
|
1047
|
+
emptyText:
|
|
1048
|
+
"transcript unavailable (this run predates transcript capture)",
|
|
1049
|
+
};
|
|
1050
|
+
},
|
|
1051
|
+
close: () => {
|
|
1052
|
+
this.transcriptPage = undefined;
|
|
1053
|
+
this.view = "detail";
|
|
1054
|
+
this.detailFocus = "agents";
|
|
1055
|
+
this.refreshTimer();
|
|
1056
|
+
this.tui.requestRender();
|
|
1057
|
+
},
|
|
1058
|
+
},
|
|
1059
|
+
{ toolsExpanded: this.initialToolsExpanded },
|
|
1060
|
+
);
|
|
1061
|
+
this.tui.requestRender();
|
|
1062
|
+
}
|
|
1063
|
+
|
|
984
1064
|
/** Bordered panel with a title in the top border, padded to exact height. */
|
|
985
1065
|
private panel(
|
|
986
1066
|
title: string,
|
|
@@ -1313,117 +1393,6 @@ export class WorkflowDashboard {
|
|
|
1313
1393
|
lines.push(this.hintLine(hints, width));
|
|
1314
1394
|
return lines;
|
|
1315
1395
|
}
|
|
1316
|
-
|
|
1317
|
-
private transcriptRows(agent: AgentRecord, width: number): string[] {
|
|
1318
|
-
const theme = this.theme;
|
|
1319
|
-
const rows: string[] = [];
|
|
1320
|
-
if (agent.transcript.length === 0) {
|
|
1321
|
-
return [
|
|
1322
|
-
theme.fg(
|
|
1323
|
-
"dim",
|
|
1324
|
-
" transcript unavailable (this run predates transcript capture)",
|
|
1325
|
-
),
|
|
1326
|
-
];
|
|
1327
|
-
}
|
|
1328
|
-
|
|
1329
|
-
for (const entry of agent.transcript) {
|
|
1330
|
-
// Tool calls are one-liners: the arrow shows direction, the accent name
|
|
1331
|
-
// says what ran, and the arguments collapse to a single compact line
|
|
1332
|
-
// instead of a vertical JSON block.
|
|
1333
|
-
if (entry.role === "tool") {
|
|
1334
|
-
const name = entry.name ? sanitizeLine(entry.name, 160) : "unknown";
|
|
1335
|
-
const args = compactInlineJson(entry.text);
|
|
1336
|
-
rows.push(
|
|
1337
|
-
` ${theme.fg("muted", "→")} ${theme.fg("accent", name)}${args ? theme.fg("dim", ` ${args}`) : ""}`,
|
|
1338
|
-
);
|
|
1339
|
-
continue;
|
|
1340
|
-
}
|
|
1341
|
-
const label = transcriptLabel(entry);
|
|
1342
|
-
const color = transcriptColor(entry);
|
|
1343
|
-
const marker = entry.role === "toolResult" ? "←" : "●";
|
|
1344
|
-
rows.push(
|
|
1345
|
-
` ${theme.fg(color, marker)} ${theme.bold(theme.fg(color, label))}`,
|
|
1346
|
-
);
|
|
1347
|
-
const contentWidth = Math.max(8, width - 4);
|
|
1348
|
-
const styled = theme.fg(
|
|
1349
|
-
entry.role === "thinking" || entry.role === "toolResult"
|
|
1350
|
-
? "dim"
|
|
1351
|
-
: entry.isError
|
|
1352
|
-
? "error"
|
|
1353
|
-
: "text",
|
|
1354
|
-
sanitizeTerminalText(entry.text),
|
|
1355
|
-
);
|
|
1356
|
-
for (const line of wrapTextWithAnsi(styled, contentWidth)) {
|
|
1357
|
-
rows.push(` ${line}`);
|
|
1358
|
-
}
|
|
1359
|
-
rows.push("");
|
|
1360
|
-
}
|
|
1361
|
-
return rows;
|
|
1362
|
-
}
|
|
1363
|
-
|
|
1364
|
-
private renderTranscript(
|
|
1365
|
-
details: WorkflowDetails,
|
|
1366
|
-
agent: AgentRecord,
|
|
1367
|
-
width: number,
|
|
1368
|
-
height: number,
|
|
1369
|
-
): string[] {
|
|
1370
|
-
const theme = this.theme;
|
|
1371
|
-
const lines: string[] = [];
|
|
1372
|
-
const right = theme.fg(
|
|
1373
|
-
"dim",
|
|
1374
|
-
[
|
|
1375
|
-
agent.model,
|
|
1376
|
-
agentContext(agent),
|
|
1377
|
-
agent.acceptance ? `acceptance:${agent.acceptance.status}` : undefined,
|
|
1378
|
-
formatElapsed(agent.startedAt, agent.finishedAt),
|
|
1379
|
-
]
|
|
1380
|
-
.filter(Boolean)
|
|
1381
|
-
.join(" · ") + " ",
|
|
1382
|
-
);
|
|
1383
|
-
lines.push(
|
|
1384
|
-
fitNavigationSides(
|
|
1385
|
-
` ${stateGlyph(agent.state, theme, Date.now())} ${theme.bold(theme.fg("accent", agent.label))}`,
|
|
1386
|
-
right,
|
|
1387
|
-
width,
|
|
1388
|
-
),
|
|
1389
|
-
);
|
|
1390
|
-
lines.push(
|
|
1391
|
-
fitNavigationSides(
|
|
1392
|
-
` ${theme.fg("muted", `${details.name ?? details.runId} · ${agent.phase ?? "unphased"}`)}`,
|
|
1393
|
-
theme.fg("dim", `${agent.transcript.length} entries `),
|
|
1394
|
-
width,
|
|
1395
|
-
),
|
|
1396
|
-
);
|
|
1397
|
-
|
|
1398
|
-
const panelHeight = height - 3;
|
|
1399
|
-
const bodyHeight = Math.max(1, panelHeight - 2);
|
|
1400
|
-
const rows = this.transcriptRows(agent, width - 2);
|
|
1401
|
-
this.transcriptRowCount = rows.length;
|
|
1402
|
-
this.transcriptViewportSize = bodyHeight;
|
|
1403
|
-
const maxScroll = Math.max(0, rows.length - bodyHeight);
|
|
1404
|
-
this.transcriptScroll = Math.min(this.transcriptScroll, maxScroll);
|
|
1405
|
-
const visible = rows.slice(
|
|
1406
|
-
this.transcriptScroll,
|
|
1407
|
-
this.transcriptScroll + bodyHeight,
|
|
1408
|
-
);
|
|
1409
|
-
const position =
|
|
1410
|
-
rows.length > bodyHeight
|
|
1411
|
-
? `Transcript · ${this.transcriptScroll + 1}-${Math.min(rows.length, this.transcriptScroll + bodyHeight)}/${rows.length}`
|
|
1412
|
-
: "Transcript";
|
|
1413
|
-
lines.push(...this.panel(position, visible, width, panelHeight));
|
|
1414
|
-
lines.push(
|
|
1415
|
-
this.hintLine(
|
|
1416
|
-
[
|
|
1417
|
-
["j/k", "scroll"],
|
|
1418
|
-
["ctrl-u/d", "page"],
|
|
1419
|
-
["g/G", "top/bottom"],
|
|
1420
|
-
["h/left/esc", "back"],
|
|
1421
|
-
],
|
|
1422
|
-
width,
|
|
1423
|
-
),
|
|
1424
|
-
);
|
|
1425
|
-
return lines;
|
|
1426
|
-
}
|
|
1427
1396
|
}
|
|
1428
1397
|
|
|
1429
1398
|
/**
|
|
@@ -1438,39 +1407,6 @@ function displayError(error: string) {
|
|
|
1438
1407
|
return clean;
|
|
1439
1408
|
}
|
|
1440
1409
|
|
|
1441
|
-
function transcriptLabel(entry: TranscriptEntry): string {
|
|
1442
|
-
if (entry.role === "user") return "user";
|
|
1443
|
-
if (entry.role === "assistant") return "assistant";
|
|
1444
|
-
if (entry.role === "thinking") return "thinking";
|
|
1445
|
-
const name = entry.name ? sanitizeLine(entry.name, 160) : "unknown";
|
|
1446
|
-
return name;
|
|
1447
|
-
}
|
|
1448
|
-
|
|
1449
|
-
/**
|
|
1450
|
-
* Tool arguments arrive pretty-printed over many lines; the transcript shows
|
|
1451
|
-
* them inline. Non-JSON text passes through flattened.
|
|
1452
|
-
*/
|
|
1453
|
-
function compactInlineJson(text: string) {
|
|
1454
|
-
const flat = sanitizeTerminalText(text).trim();
|
|
1455
|
-
if (!flat) return "";
|
|
1456
|
-
try {
|
|
1457
|
-
return JSON.stringify(JSON.parse(flat));
|
|
1458
|
-
} catch {
|
|
1459
|
-
return flat.replace(/\s+/g, " ");
|
|
1460
|
-
}
|
|
1461
|
-
}
|
|
1462
|
-
|
|
1463
|
-
function transcriptColor(
|
|
1464
|
-
entry: TranscriptEntry,
|
|
1465
|
-
): "accent" | "success" | "dim" | "warning" | "error" | "muted" {
|
|
1466
|
-
if (entry.isError) return "error";
|
|
1467
|
-
if (entry.role === "user") return "accent";
|
|
1468
|
-
if (entry.role === "assistant") return "success";
|
|
1469
|
-
if (entry.role === "thinking") return "dim";
|
|
1470
|
-
if (entry.role === "tool") return "warning";
|
|
1471
|
-
return "muted";
|
|
1472
|
-
}
|
|
1473
|
-
|
|
1474
1410
|
function groupGlyph(group: PhaseGroup, theme: Theme) {
|
|
1475
1411
|
if (group.agents.length === 0) return theme.fg("dim", "○");
|
|
1476
1412
|
if (group.agents.some((a) => a.state === "running"))
|
|
@@ -1489,6 +1425,7 @@ export async function showWorkflowDashboard(
|
|
|
1489
1425
|
initialRunId?: string,
|
|
1490
1426
|
startedSince = 0,
|
|
1491
1427
|
onAbort?: (runId: string) => boolean,
|
|
1428
|
+
getRetained?: () => ReadonlyMap<string, WorkflowDetails>,
|
|
1492
1429
|
) {
|
|
1493
1430
|
await ctx.ui.custom<void>(
|
|
1494
1431
|
(tui, theme, keybindings, done) => {
|
|
@@ -1506,12 +1443,14 @@ export async function showWorkflowDashboard(
|
|
|
1506
1443
|
},
|
|
1507
1444
|
initialRunId,
|
|
1508
1445
|
onAbort,
|
|
1446
|
+
getRetained,
|
|
1447
|
+
ctx.ui.getToolsExpanded(),
|
|
1509
1448
|
);
|
|
1510
1449
|
return dashboard;
|
|
1511
1450
|
},
|
|
1512
1451
|
{
|
|
1513
1452
|
overlay: true,
|
|
1514
|
-
overlayOptions: { anchor: "
|
|
1453
|
+
overlayOptions: { anchor: "top-left", width: "100%", maxHeight: "100%" },
|
|
1515
1454
|
},
|
|
1516
1455
|
);
|
|
1517
1456
|
}
|