@llblab/pi-actors 0.42.3 → 0.43.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/AGENTS.md +121 -175
- package/CHANGELOG.md +12 -0
- package/README.md +113 -276
- package/dist/fixtures/protocol/control-endpoint.json +6 -0
- package/dist/fixtures/protocol/control-record.json +9 -0
- package/dist/fixtures/protocol/recipe-summary.json +4 -12
- package/dist/fixtures/protocol/trace-event.json +9 -0
- package/dist/lib/async-runs.d.ts +14 -38
- package/dist/lib/async-runs.js +158 -108
- package/dist/lib/control.d.ts +12 -0
- package/dist/lib/control.js +84 -0
- package/dist/lib/execution-sessions.d.ts +17 -0
- package/dist/lib/execution-sessions.js +85 -0
- package/dist/lib/file-state.d.ts +1 -0
- package/dist/lib/file-state.js +17 -5
- package/dist/lib/inspector-actions.d.ts +2 -2
- package/dist/lib/inspector-actions.js +2 -2
- package/dist/lib/inspector-command.js +3 -3
- package/dist/lib/inspector-overlay.d.ts +52 -70
- package/dist/lib/inspector-overlay.js +532 -905
- package/dist/lib/inspector.d.ts +3 -71
- package/dist/lib/inspector.js +19 -665
- package/dist/lib/limits.d.ts +4 -2
- package/dist/lib/limits.js +4 -2
- package/dist/lib/observability.d.ts +16 -16
- package/dist/lib/observability.js +43 -82
- package/dist/lib/prompts.d.ts +1 -1
- package/dist/lib/prompts.js +2 -2
- package/dist/lib/recipe-control.d.ts +7 -0
- package/dist/lib/recipe-control.js +39 -0
- package/dist/lib/recipes-discovery.js +2 -0
- package/dist/lib/recipes-references.d.ts +1 -14
- package/dist/lib/recipes-references.js +6 -21
- package/dist/lib/review-projection.js +1 -5
- package/dist/lib/run-ui-runtime.js +2 -2
- package/dist/lib/runs-control-delivery.d.ts +21 -0
- package/dist/lib/runs-control-delivery.js +127 -0
- package/dist/lib/runs-controls.d.ts +35 -0
- package/dist/lib/runs-controls.js +144 -0
- package/dist/lib/runs-retention.d.ts +7 -0
- package/dist/lib/runs-retention.js +27 -3
- package/dist/lib/runs-start.js +4 -2
- package/dist/lib/runs-status.js +11 -6
- package/dist/lib/runs-trace.d.ts +24 -0
- package/dist/lib/runs-trace.js +98 -0
- package/dist/lib/runtime-notifier.d.ts +1 -1
- package/dist/lib/runtime-notifier.js +1 -1
- package/dist/lib/tools-inspect.d.ts +3 -3
- package/dist/lib/tools-inspect.js +203 -708
- package/dist/lib/tools-local.js +2 -10
- package/dist/lib/tools-message.d.ts +7 -7
- package/dist/lib/tools-message.js +95 -396
- package/dist/lib/tools-response.d.ts +1 -4
- package/dist/lib/tools-response.js +5 -39
- package/dist/lib/tools-spawn.js +16 -28
- package/dist/lib/tools.js +1 -2
- package/dist/lib/trace-projection.d.ts +22 -0
- package/dist/lib/trace-projection.js +165 -0
- package/dist/recipes/draft-review.json +0 -10
- package/dist/recipes/lens-swarm.json +0 -14
- package/dist/recipes/music-player.json +10 -19
- package/dist/recipes/pipeline-architect-coordinator.json +0 -11
- package/dist/recipes/pipeline-artifact-bundle.json +1 -22
- package/dist/recipes/pipeline-artifact-report.json +1 -18
- package/dist/recipes/pipeline-artifact-write.json +1 -18
- package/dist/recipes/pipeline-async-run-ops.json +0 -12
- package/dist/recipes/pipeline-checkpoint-continuation.json +0 -14
- package/dist/recipes/pipeline-development-tasking.json +0 -12
- package/dist/recipes/pipeline-docs-maintenance.json +0 -12
- package/dist/recipes/pipeline-media-library.json +0 -12
- package/dist/recipes/pipeline-quorum-review.json +0 -12
- package/dist/recipes/pipeline-release-readiness.json +0 -12
- package/dist/recipes/pipeline-release-summary.json +0 -12
- package/dist/recipes/pipeline-repo-health.json +0 -12
- package/dist/recipes/pipeline-research-synthesis.json +0 -11
- package/dist/recipes/pipeline-review-readiness.json +0 -12
- package/dist/recipes/resource-locker.json +27 -0
- package/dist/recipes/subagent-artifact.json +0 -9
- package/dist/recipes/subagent-checkpoint.json +0 -10
- package/dist/recipes/subagent-conflict-report.json +0 -11
- package/dist/recipes/subagent-contradiction-map.json +0 -11
- package/dist/recipes/subagent-critic.json +0 -11
- package/dist/recipes/subagent-evidence-map.json +0 -11
- package/dist/recipes/subagent-followup.json +0 -10
- package/dist/recipes/subagent-judge.json +0 -11
- package/dist/recipes/subagent-merge.json +0 -11
- package/dist/recipes/subagent-normalize.json +0 -11
- package/dist/recipes/subagent-plan.json +0 -11
- package/dist/recipes/subagent-preflight.json +0 -11
- package/dist/recipes/subagent-prompt.json +0 -10
- package/dist/recipes/subagent-quorum.json +0 -10
- package/dist/recipes/subagent-review-coordinator.json +0 -14
- package/dist/recipes/subagent-review.json +0 -11
- package/dist/recipes/subagent-task-card.json +0 -11
- package/dist/recipes/subagent-tools.json +0 -10
- package/dist/recipes/subagent-verify.json +0 -11
- package/dist/recipes/subagents-prompts.json +0 -10
- package/dist/recipes/tool-review.json +0 -10
- package/dist/scripts/async-runner.mjs +25 -25
- package/dist/scripts/conformance.mjs +4 -2
- package/dist/scripts/locker.mjs +200 -66
- package/dist/scripts/music-player.mjs +159 -150
- package/dist/scripts/recipe-utils.mjs +6 -96
- package/dist/scripts/release-gates.mjs +60 -0
- package/dist/scripts/validate-recipe.mjs +3 -53
- package/dist/skills/actors/SKILL.md +53 -266
- package/dist/skills/swarm/SKILL.md +11 -33
- package/docs/0.43-baseline.md +44 -0
- package/docs/README.md +3 -3
- package/docs/actor-inspector.md +26 -64
- package/docs/actors-deep-reference.md +92 -50
- package/docs/async-runs.md +81 -328
- package/docs/command-templates.md +2 -2
- package/docs/component-recipes.md +30 -133
- package/docs/recipe-library.md +57 -182
- package/docs/task-first-recipes.md +10 -12
- package/docs/template-recipes.md +76 -289
- package/docs/tool-registry.md +41 -161
- package/fixtures/protocol/control-endpoint.json +6 -0
- package/fixtures/protocol/control-record.json +9 -0
- package/fixtures/protocol/recipe-summary.json +4 -12
- package/fixtures/protocol/trace-event.json +9 -0
- package/lib/async-runs.ts +202 -201
- package/lib/control.ts +102 -0
- package/lib/execution-sessions.ts +111 -0
- package/lib/file-state.ts +17 -4
- package/lib/inspector-actions.ts +2 -2
- package/lib/inspector-command.ts +3 -3
- package/lib/inspector-overlay.ts +577 -1121
- package/lib/inspector.ts +46 -979
- package/lib/limits.ts +4 -2
- package/lib/observability.ts +60 -101
- package/lib/prompts.ts +2 -2
- package/lib/recipe-control.ts +45 -0
- package/lib/recipes-discovery.ts +2 -0
- package/lib/recipes-references.ts +9 -45
- package/lib/review-projection.ts +1 -5
- package/lib/run-ui-runtime.ts +2 -2
- package/lib/runs-control-delivery.ts +181 -0
- package/lib/runs-controls.ts +204 -0
- package/lib/runs-retention.ts +38 -3
- package/lib/runs-start.ts +4 -2
- package/lib/runs-status.ts +11 -6
- package/lib/runs-trace.ts +132 -0
- package/lib/runtime-notifier.ts +1 -1
- package/lib/tools-inspect.ts +240 -901
- package/lib/tools-local.ts +2 -12
- package/lib/tools-message.ts +112 -519
- package/lib/tools-response.ts +5 -52
- package/lib/tools-spawn.ts +16 -32
- package/lib/tools.ts +1 -2
- package/lib/trace-projection.ts +221 -0
- package/package.json +2 -1
- package/recipes/draft-review.json +0 -10
- package/recipes/lens-swarm.json +0 -14
- package/recipes/music-player.json +10 -19
- package/recipes/pipeline-architect-coordinator.json +0 -11
- package/recipes/pipeline-artifact-bundle.json +1 -22
- package/recipes/pipeline-artifact-report.json +1 -18
- package/recipes/pipeline-artifact-write.json +1 -18
- package/recipes/pipeline-async-run-ops.json +0 -12
- package/recipes/pipeline-checkpoint-continuation.json +0 -14
- package/recipes/pipeline-development-tasking.json +0 -12
- package/recipes/pipeline-docs-maintenance.json +0 -12
- package/recipes/pipeline-media-library.json +0 -12
- package/recipes/pipeline-quorum-review.json +0 -12
- package/recipes/pipeline-release-readiness.json +0 -12
- package/recipes/pipeline-release-summary.json +0 -12
- package/recipes/pipeline-repo-health.json +0 -12
- package/recipes/pipeline-research-synthesis.json +0 -11
- package/recipes/pipeline-review-readiness.json +0 -12
- package/recipes/resource-locker.json +27 -0
- package/recipes/subagent-artifact.json +0 -9
- package/recipes/subagent-checkpoint.json +0 -10
- package/recipes/subagent-conflict-report.json +0 -11
- package/recipes/subagent-contradiction-map.json +0 -11
- package/recipes/subagent-critic.json +0 -11
- package/recipes/subagent-evidence-map.json +0 -11
- package/recipes/subagent-followup.json +0 -10
- package/recipes/subagent-judge.json +0 -11
- package/recipes/subagent-merge.json +0 -11
- package/recipes/subagent-normalize.json +0 -11
- package/recipes/subagent-plan.json +0 -11
- package/recipes/subagent-preflight.json +0 -11
- package/recipes/subagent-prompt.json +0 -10
- package/recipes/subagent-quorum.json +0 -10
- package/recipes/subagent-review-coordinator.json +0 -14
- package/recipes/subagent-review.json +0 -11
- package/recipes/subagent-task-card.json +0 -11
- package/recipes/subagent-tools.json +0 -10
- package/recipes/subagent-verify.json +0 -11
- package/recipes/subagents-prompts.json +0 -10
- package/recipes/tool-review.json +0 -10
- package/scripts/async-runner.mjs +25 -25
- package/scripts/conformance.mjs +4 -2
- package/scripts/locker.mjs +200 -66
- package/scripts/music-player.mjs +159 -150
- package/scripts/recipe-utils.mjs +6 -96
- package/scripts/release-gates.mjs +60 -0
- package/scripts/validate-recipe.mjs +3 -53
- package/skills/actors/SKILL.md +53 -266
- package/skills/swarm/SKILL.md +11 -33
- package/dist/fixtures/protocol/actor-message-branch.json +0 -13
- package/dist/fixtures/protocol/mailbox-contract.json +0 -15
- package/dist/fixtures/protocol/room-message.json +0 -11
- package/dist/fixtures/protocol/room-roster.json +0 -11
- package/dist/fixtures/protocol/run-inbox-message.json +0 -9
- package/dist/fixtures/protocol/run-outbox-event.json +0 -9
- package/dist/lib/mailbox-loop.d.ts +0 -41
- package/dist/lib/mailbox-loop.js +0 -60
- package/dist/lib/messages.d.ts +0 -25
- package/dist/lib/messages.js +0 -122
- package/dist/lib/rooms.d.ts +0 -104
- package/dist/lib/rooms.js +0 -647
- package/dist/lib/runs-mailbox.d.ts +0 -25
- package/dist/lib/runs-mailbox.js +0 -146
- package/dist/lib/runs-messages.d.ts +0 -15
- package/dist/lib/runs-messages.js +0 -179
- package/dist/lib/runs-outbox.d.ts +0 -41
- package/dist/lib/runs-outbox.js +0 -87
- package/dist/lib/tools-mailbox.d.ts +0 -8
- package/dist/lib/tools-mailbox.js +0 -48
- package/dist/recipes/actor-worker.json +0 -39
- package/dist/recipes/coordinator-locker.json +0 -45
- package/dist/recipes/locker.json +0 -45
- package/dist/recipes/pipeline-room-swarm.json +0 -50
- package/dist/recipes/subagent-message.json +0 -32
- package/dist/recipes/utility-actor-message.json +0 -23
- package/dist/scripts/actor-worker.mjs +0 -214
- package/dist/scripts/coordinator.mjs +0 -799
- package/docs/actor-messages.md +0 -225
- package/fixtures/protocol/actor-message-branch.json +0 -13
- package/fixtures/protocol/mailbox-contract.json +0 -15
- package/fixtures/protocol/room-message.json +0 -11
- package/fixtures/protocol/room-roster.json +0 -11
- package/fixtures/protocol/run-inbox-message.json +0 -9
- package/fixtures/protocol/run-outbox-event.json +0 -9
- package/lib/mailbox-loop.ts +0 -144
- package/lib/messages.ts +0 -151
- package/lib/rooms.ts +0 -939
- package/lib/runs-mailbox.ts +0 -208
- package/lib/runs-messages.ts +0 -252
- package/lib/runs-outbox.ts +0 -144
- package/lib/tools-mailbox.ts +0 -56
- package/recipes/actor-worker.json +0 -39
- package/recipes/coordinator-locker.json +0 -45
- package/recipes/locker.json +0 -45
- package/recipes/pipeline-room-swarm.json +0 -50
- package/recipes/subagent-message.json +0 -32
- package/recipes/utility-actor-message.json +0 -23
- package/scripts/actor-worker.mjs +0 -214
- package/scripts/coordinator.mjs +0 -799
package/lib/inspector-overlay.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Keyboard-driven
|
|
3
|
-
* Zones:
|
|
4
|
-
* Owns
|
|
2
|
+
* Keyboard-driven Actor Inspector overlay for concrete Run instances.
|
|
3
|
+
* Zones: owned actor selection, Recipe/Trace/Control tabs, safe Run Kill confirmation
|
|
4
|
+
* Owns manual kernel navigation; evidence parsing and lifecycle mutation stay in ports.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
import { readFileSync } from "node:fs";
|
|
7
8
|
import * as path from "node:path";
|
|
8
|
-
import { stripVTControlCharacters } from "node:util";
|
|
9
9
|
|
|
10
10
|
import type { Theme } from "@earendil-works/pi-coding-agent";
|
|
11
11
|
import {
|
|
@@ -15,169 +15,150 @@ import {
|
|
|
15
15
|
type TUI,
|
|
16
16
|
} from "@earendil-works/pi-tui";
|
|
17
17
|
|
|
18
|
-
import * as
|
|
18
|
+
import * as ActorInspector from "./inspector.ts";
|
|
19
|
+
import * as RunsControls from "./runs-controls.ts";
|
|
20
|
+
import * as RunControlDelivery from "./runs-control-delivery.ts";
|
|
21
|
+
import * as TraceProjection from "./trace-projection.ts";
|
|
19
22
|
|
|
20
|
-
export type
|
|
23
|
+
export type ActorInspectorTab = "recipe" | "trace" | "control";
|
|
21
24
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
"communications",
|
|
25
|
-
"turns",
|
|
26
|
-
];
|
|
27
|
-
|
|
28
|
-
type DetailLogicalLine = string | { sectionBreak: true };
|
|
29
|
-
|
|
30
|
-
const DETAIL_SECTION_BREAK: DetailLogicalLine = { sectionBreak: true };
|
|
25
|
+
type InspectorFocus = "runs" | "tabs" | "document" | "list" | "detail" | "select";
|
|
26
|
+
type SelectorMode = "run" | "source";
|
|
31
27
|
|
|
32
|
-
export interface
|
|
28
|
+
export interface ActorInspectorActionResult {
|
|
33
29
|
ok: boolean;
|
|
34
30
|
message: string;
|
|
35
31
|
}
|
|
36
32
|
|
|
37
33
|
export interface ActorInspectorOverlayOptions {
|
|
38
34
|
done: () => void;
|
|
39
|
-
killRun?: (
|
|
40
|
-
run: string,
|
|
41
|
-
runInstanceId: string,
|
|
42
|
-
) => ActorInspectorOverlayActionResult;
|
|
35
|
+
killRun?: (run: string, runInstanceId: string) => ActorInspectorActionResult;
|
|
43
36
|
ownerId: string;
|
|
44
|
-
|
|
37
|
+
readRuns?: typeof ActorInspector.readActorInspectorRuns;
|
|
38
|
+
readTrace?: typeof TraceProjection.projectRunTrace;
|
|
45
39
|
stateRoot: string;
|
|
46
40
|
theme: Theme;
|
|
47
41
|
tui: TUI;
|
|
48
42
|
}
|
|
49
43
|
|
|
44
|
+
interface KillConfirmation {
|
|
45
|
+
run: string;
|
|
46
|
+
runInstanceId: string;
|
|
47
|
+
status: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const TABS: ActorInspectorTab[] = ["recipe", "trace", "control"];
|
|
51
|
+
const TRACE_SOURCES: TraceProjection.TraceSourceFilter[] = [
|
|
52
|
+
"all",
|
|
53
|
+
"lifecycle",
|
|
54
|
+
"control",
|
|
55
|
+
"process",
|
|
56
|
+
"agent",
|
|
57
|
+
"artifact",
|
|
58
|
+
"runtime",
|
|
59
|
+
];
|
|
60
|
+
|
|
50
61
|
export class ActorInspectorOverlay {
|
|
51
62
|
private readonly done: () => void;
|
|
52
|
-
private readonly killRun?: (
|
|
53
|
-
run: string,
|
|
54
|
-
runInstanceId: string,
|
|
55
|
-
) => ActorInspectorOverlayActionResult;
|
|
63
|
+
private readonly killRun?: (run: string, runInstanceId: string) => ActorInspectorActionResult;
|
|
56
64
|
private readonly ownerId: string;
|
|
57
|
-
private readonly
|
|
65
|
+
private readonly readRuns: typeof ActorInspector.readActorInspectorRuns;
|
|
66
|
+
private readonly readTrace: typeof TraceProjection.projectRunTrace;
|
|
58
67
|
private readonly stateRoot: string;
|
|
59
68
|
private readonly theme: Theme;
|
|
60
69
|
private readonly tui: TUI;
|
|
61
70
|
private readonly refreshTimer: NodeJS.Timeout;
|
|
62
|
-
private runIndex = 0;
|
|
63
|
-
private communicationChannel: "all" | "broadcast" | "direct" | "room" = "all";
|
|
64
|
-
private communicationFrom = "all";
|
|
65
|
-
private communicationUnread = false;
|
|
66
71
|
private contentStripeIndices: number[] = [];
|
|
67
|
-
private
|
|
68
|
-
private detailOpen = false;
|
|
72
|
+
private documentScroll = 0;
|
|
69
73
|
private detailScroll = 0;
|
|
70
|
-
private
|
|
71
|
-
private
|
|
72
|
-
private killConfirmation?:
|
|
74
|
+
private feedback?: ActorInspectorActionResult;
|
|
75
|
+
private focus: InspectorFocus = "runs";
|
|
76
|
+
private killConfirmation?: KillConfirmation;
|
|
73
77
|
private killDialogChoice: "cancel" | "kill" = "cancel";
|
|
74
|
-
private readonly readKeys = new Set<string>();
|
|
75
|
-
private focus: "runs" | "tabs" | "recipe" | "list" | "detail" | "select" = "tabs";
|
|
76
|
-
private filterControlIndex = 0;
|
|
77
|
-
private menuLevel: "run" | "filter" | "value" = "filter";
|
|
78
|
-
private selectorIndex = 0;
|
|
79
|
-
private recipeScroll = 0;
|
|
80
78
|
private rowIndex = 0;
|
|
81
|
-
private
|
|
82
|
-
private
|
|
83
|
-
private
|
|
84
|
-
private
|
|
79
|
+
private runCache?: ActorInspector.ActorInspectorRunItem[];
|
|
80
|
+
private runIndex = 0;
|
|
81
|
+
private selectorIndex = 0;
|
|
82
|
+
private selectorMode?: SelectorMode;
|
|
83
|
+
private tabIndex = 0;
|
|
84
|
+
private traceCache?: {
|
|
85
|
+
run: string;
|
|
86
|
+
runInstanceId?: string;
|
|
87
|
+
items: TraceProjection.TraceItem[];
|
|
88
|
+
};
|
|
89
|
+
private traceDetail?: TraceProjection.TraceItem;
|
|
90
|
+
private traceSourceIndex = 0;
|
|
85
91
|
|
|
86
92
|
constructor(options: ActorInspectorOverlayOptions) {
|
|
87
93
|
this.done = options.done;
|
|
88
94
|
this.killRun = options.killRun;
|
|
89
95
|
this.ownerId = options.ownerId;
|
|
90
|
-
this.
|
|
96
|
+
this.readRuns = options.readRuns ?? ActorInspector.readActorInspectorRuns;
|
|
97
|
+
this.readTrace = options.readTrace ?? TraceProjection.projectRunTrace;
|
|
91
98
|
this.stateRoot = options.stateRoot;
|
|
92
99
|
this.theme = options.theme;
|
|
93
100
|
this.tui = options.tui;
|
|
94
101
|
this.refreshTimer = setInterval(() => {
|
|
95
|
-
|
|
102
|
+
this.runCache = undefined;
|
|
103
|
+
if (this.focus !== "list" && this.focus !== "detail") this.traceCache = undefined;
|
|
96
104
|
this.tui.requestRender();
|
|
97
|
-
},
|
|
105
|
+
}, 1_000);
|
|
98
106
|
this.refreshTimer.unref?.();
|
|
99
107
|
}
|
|
100
108
|
|
|
109
|
+
dispose(): void {
|
|
110
|
+
clearInterval(this.refreshTimer);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
invalidate(): void {}
|
|
114
|
+
|
|
101
115
|
handleInput(data: string): void {
|
|
102
116
|
const runs = this.runs();
|
|
103
|
-
this.
|
|
117
|
+
this.runIndex = Math.min(this.runIndex, Math.max(0, runs.length - 1));
|
|
104
118
|
if (matchesKey(data, "ctrl+c")) {
|
|
105
119
|
this.done();
|
|
106
120
|
return;
|
|
107
121
|
}
|
|
108
122
|
if (this.killConfirmation) {
|
|
109
|
-
|
|
110
|
-
if (matchesKey(data, "escape") || key === "n") {
|
|
111
|
-
this.cancelKill();
|
|
112
|
-
} else if (
|
|
113
|
-
matchesKey(data, "left") ||
|
|
114
|
-
matchesKey(data, "right") ||
|
|
115
|
-
matchesKey(data, "tab")
|
|
116
|
-
) {
|
|
117
|
-
this.killDialogChoice =
|
|
118
|
-
this.killDialogChoice === "cancel" ? "kill" : "cancel";
|
|
119
|
-
} else if (key === "y") {
|
|
120
|
-
this.confirmKill();
|
|
121
|
-
} else if (matchesKey(data, "return")) {
|
|
122
|
-
if (this.killDialogChoice === "kill") this.confirmKill();
|
|
123
|
-
else this.cancelKill();
|
|
124
|
-
}
|
|
123
|
+
this.handleKillInput(data);
|
|
125
124
|
this.tui.requestRender();
|
|
126
125
|
return;
|
|
127
126
|
}
|
|
128
127
|
if (data.toLowerCase() === "k") {
|
|
129
|
-
this.requestKill(runs);
|
|
128
|
+
this.requestKill(runs[this.runIndex]);
|
|
130
129
|
this.tui.requestRender();
|
|
131
130
|
return;
|
|
132
131
|
}
|
|
133
132
|
if (matchesKey(data, "escape")) {
|
|
134
133
|
if (this.focus === "select") {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
else this.
|
|
134
|
+
this.selectorMode = undefined;
|
|
135
|
+
this.focus = this.tab === "trace" ? "tabs" : "runs";
|
|
136
|
+
} else if (this.focus === "detail") {
|
|
137
|
+
this.traceDetail = undefined;
|
|
138
|
+
this.detailScroll = 0;
|
|
139
|
+
this.focus = "list";
|
|
140
|
+
} else if (this.focus === "document" || this.focus === "list") {
|
|
141
|
+
this.focus = "tabs";
|
|
142
|
+
} else {
|
|
143
|
+
this.done();
|
|
144
|
+
}
|
|
142
145
|
this.tui.requestRender();
|
|
143
146
|
return;
|
|
144
147
|
}
|
|
145
148
|
if (this.focus === "select") {
|
|
146
|
-
|
|
147
|
-
if (matchesKey(data, "up"))
|
|
148
|
-
this.selectorIndex = Math.max(0, this.selectorIndex - 1);
|
|
149
|
-
else if (matchesKey(data, "down"))
|
|
150
|
-
this.selectorIndex = Math.min(options.length - 1, this.selectorIndex + 1);
|
|
151
|
-
else if (matchesKey(data, "left")) this.backMenu();
|
|
152
|
-
else if (
|
|
153
|
-
matchesKey(data, "return") ||
|
|
154
|
-
(matchesKey(data, "right") && this.menuLevel !== "value")
|
|
155
|
-
)
|
|
156
|
-
this.applyMenuOption(options[this.selectorIndex]);
|
|
149
|
+
this.handleSelectorInput(data, runs);
|
|
157
150
|
this.tui.requestRender();
|
|
158
151
|
return;
|
|
159
152
|
}
|
|
160
|
-
if (this.focus === "
|
|
161
|
-
|
|
162
|
-
if (this.recipeScroll === 0) this.focus = "tabs";
|
|
163
|
-
else this.recipeScroll -= 1;
|
|
164
|
-
} else if (matchesKey(data, "down")) this.recipeScroll += 1;
|
|
165
|
-
else if (matchesKey(data, "pageUp"))
|
|
166
|
-
this.recipeScroll = Math.max(0, this.recipeScroll - this.contentViewportRows());
|
|
167
|
-
else if (matchesKey(data, "pageDown"))
|
|
168
|
-
this.recipeScroll += this.contentViewportRows();
|
|
169
|
-
else if (matchesKey(data, "left")) this.focus = "tabs";
|
|
153
|
+
if (this.focus === "detail") {
|
|
154
|
+
this.handleScrollableInput(data, "detail");
|
|
170
155
|
this.tui.requestRender();
|
|
171
156
|
return;
|
|
172
157
|
}
|
|
173
|
-
if (this.focus === "
|
|
174
|
-
if (matchesKey(data, "
|
|
175
|
-
else if (matchesKey(data, "
|
|
176
|
-
else
|
|
177
|
-
this.detailScroll = Math.max(0, this.detailScroll - this.contentViewportRows());
|
|
178
|
-
else if (matchesKey(data, "pageDown"))
|
|
179
|
-
this.detailScroll += this.contentViewportRows();
|
|
180
|
-
else if (matchesKey(data, "left")) this.backDetail();
|
|
158
|
+
if (this.focus === "document") {
|
|
159
|
+
if (matchesKey(data, "left")) this.focus = "tabs";
|
|
160
|
+
else if (matchesKey(data, "up") && this.documentScroll === 0) this.focus = "tabs";
|
|
161
|
+
else this.handleScrollableInput(data, "document");
|
|
181
162
|
this.tui.requestRender();
|
|
182
163
|
return;
|
|
183
164
|
}
|
|
@@ -185,197 +166,183 @@ export class ActorInspectorOverlay {
|
|
|
185
166
|
if (matchesKey(data, "left")) this.cycleRun(runs, -1);
|
|
186
167
|
else if (matchesKey(data, "right")) this.cycleRun(runs, 1);
|
|
187
168
|
else if (matchesKey(data, "down")) this.focus = "tabs";
|
|
188
|
-
else if (matchesKey(data, "return") && runs.length > 0)
|
|
169
|
+
else if (matchesKey(data, "return") && runs.length > 0) {
|
|
170
|
+
this.selectorMode = "run";
|
|
171
|
+
this.selectorIndex = this.runIndex;
|
|
172
|
+
this.focus = "select";
|
|
173
|
+
}
|
|
189
174
|
} else if (this.focus === "tabs") {
|
|
190
|
-
if (matchesKey(data, "left") || matchesKey(data, "right")) {
|
|
191
|
-
const
|
|
192
|
-
|
|
193
|
-
this.
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
this.
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
else if (
|
|
201
|
-
(
|
|
202
|
-
this.
|
|
203
|
-
|
|
204
|
-
else if (matchesKey(data, "down") && this.listItemCount() > 0)
|
|
205
|
-
this.focus = "list";
|
|
206
|
-
else if (matchesKey(data, "return")) this.openFilterMenu();
|
|
175
|
+
if (matchesKey(data, "left") || matchesKey(data, "right") || matchesKey(data, "tab")) {
|
|
176
|
+
const direction = matchesKey(data, "left") ? -1 : 1;
|
|
177
|
+
this.tabIndex = (this.tabIndex + direction + TABS.length) % TABS.length;
|
|
178
|
+
this.resetContentPosition();
|
|
179
|
+
} else if (matchesKey(data, "up")) {
|
|
180
|
+
this.focus = "runs";
|
|
181
|
+
} else if (matchesKey(data, "down")) {
|
|
182
|
+
this.focus = this.tab === "trace" && this.traceItems(runs[this.runIndex]).length > 0
|
|
183
|
+
? "list"
|
|
184
|
+
: "document";
|
|
185
|
+
} else if (matchesKey(data, "return")) {
|
|
186
|
+
if (this.tab === "trace") this.openSourceSelector(runs[this.runIndex]);
|
|
187
|
+
else this.focus = "document";
|
|
188
|
+
}
|
|
207
189
|
} else if (this.focus === "list") {
|
|
208
|
-
const count = this.
|
|
190
|
+
const count = this.traceItems(runs[this.runIndex]).length;
|
|
209
191
|
if (matchesKey(data, "left")) this.focus = "tabs";
|
|
210
|
-
else if (matchesKey(data, "pageUp"))
|
|
211
|
-
this.rowIndex = Math.max(0, this.rowIndex - this.contentViewportRows());
|
|
212
|
-
else if (matchesKey(data, "pageDown"))
|
|
213
|
-
this.rowIndex = Math.min(Math.max(0, count - 1), this.rowIndex + this.contentViewportRows());
|
|
214
192
|
else if (matchesKey(data, "up")) {
|
|
215
193
|
if (this.rowIndex === 0) this.focus = "tabs";
|
|
216
194
|
else this.rowIndex -= 1;
|
|
217
|
-
} else if (matchesKey(data, "down"))
|
|
195
|
+
} else if (matchesKey(data, "down")) {
|
|
218
196
|
this.rowIndex = Math.min(Math.max(0, count - 1), this.rowIndex + 1);
|
|
219
|
-
else if (matchesKey(data, "
|
|
220
|
-
this.
|
|
197
|
+
} else if (matchesKey(data, "pageUp")) {
|
|
198
|
+
this.rowIndex = Math.max(0, this.rowIndex - this.contentViewportRows());
|
|
199
|
+
} else if (matchesKey(data, "pageDown")) {
|
|
200
|
+
this.rowIndex = Math.min(
|
|
201
|
+
Math.max(0, count - 1),
|
|
202
|
+
this.rowIndex + this.contentViewportRows(),
|
|
203
|
+
);
|
|
204
|
+
} else if (matchesKey(data, "return") || matchesKey(data, "right")) {
|
|
205
|
+
const item = this.traceItems(runs[this.runIndex])[this.rowIndex];
|
|
206
|
+
if (item) {
|
|
207
|
+
this.traceDetail = item;
|
|
208
|
+
this.detailScroll = 0;
|
|
209
|
+
this.focus = "detail";
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
if (data.toLowerCase() === "f" && this.tab === "trace") {
|
|
214
|
+
this.cycleTraceSource(runs[this.runIndex]);
|
|
221
215
|
}
|
|
222
216
|
this.tui.requestRender();
|
|
223
217
|
}
|
|
224
218
|
|
|
225
219
|
render(width: number): string[] {
|
|
226
|
-
this.ensureSelectedRun(this.runs());
|
|
227
220
|
const safeWidth = Math.max(24, width);
|
|
228
|
-
const innerWidth =
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
const
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
`#${runs.length - index} ${run.run} ${run.status}`,
|
|
240
|
-
),
|
|
241
|
-
this.selectorIndex,
|
|
242
|
-
undefined,
|
|
243
|
-
undefined,
|
|
244
|
-
false,
|
|
245
|
-
false,
|
|
246
|
-
this.contentViewportRows(),
|
|
247
|
-
)
|
|
248
|
-
: this.renderFilterMenus(innerWidth)
|
|
249
|
-
: [];
|
|
250
|
-
const selectorAnchor = this.selectorAnchor();
|
|
251
|
-
if (this.menuLevel === "run" && selector.length > 0) {
|
|
252
|
-
const popup = selector[0];
|
|
253
|
-
const popupWidth = visibleWidth(popup);
|
|
254
|
-
const leading = this.fit(
|
|
255
|
-
this.takeVisiblePrefix(this.renderTabs(), selectorAnchor),
|
|
256
|
-
selectorAnchor,
|
|
257
|
-
);
|
|
258
|
-
const trailingWidth = Math.max(0, innerWidth - selectorAnchor - popupWidth);
|
|
259
|
-
const trailing = this.fit(
|
|
260
|
-
this.dropVisiblePrefix(this.renderTabs(), selectorAnchor + popupWidth),
|
|
261
|
-
trailingWidth,
|
|
262
|
-
);
|
|
263
|
-
lines.push(this.row(`${leading}${popup}${trailing}`, innerWidth, true));
|
|
264
|
-
selector = selector.slice(1);
|
|
265
|
-
} else lines.push(this.row(this.renderTabs(), innerWidth));
|
|
266
|
-
const selectorTop = selector[0];
|
|
267
|
-
if (selectorTop) {
|
|
268
|
-
const leadingBorder = "─".repeat(selectorAnchor);
|
|
269
|
-
const remainingBorder = "─".repeat(
|
|
270
|
-
Math.max(0, innerWidth - visibleWidth(selectorTop) - selectorAnchor),
|
|
271
|
-
);
|
|
272
|
-
lines.push(
|
|
273
|
-
`${this.theme.fg("borderAccent", `├${leadingBorder}`)}${selectorTop}${this.theme.fg("borderAccent", `${remainingBorder}┤`)}`,
|
|
274
|
-
);
|
|
275
|
-
} else lines.push(this.border("├", "", "┤", innerWidth));
|
|
221
|
+
const innerWidth = safeWidth - 2;
|
|
222
|
+
const runs = this.runs();
|
|
223
|
+
this.runIndex = Math.min(this.runIndex, Math.max(0, runs.length - 1));
|
|
224
|
+
const run = runs[this.runIndex];
|
|
225
|
+
if (this.killConfirmation) return this.renderKillDialog(innerWidth);
|
|
226
|
+
|
|
227
|
+
const lines = [this.border("╭", " Actor Inspector ", "╮", innerWidth)];
|
|
228
|
+
lines.push(this.row(this.renderRunControl(run, runs.length - this.runIndex), innerWidth));
|
|
229
|
+
lines.push(this.row(this.renderTabs(run), innerWidth));
|
|
230
|
+
lines.push(this.border("├", "", "┤", innerWidth));
|
|
231
|
+
|
|
276
232
|
this.contentStripeIndices = [];
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
]
|
|
294
|
-
|
|
295
|
-
const contentViewportRows = this.contentViewportRows();
|
|
296
|
-
const selectorRows = selector.slice(1);
|
|
297
|
-
for (let index = 0; index < Math.max(content.length, contentViewportRows); index += 1) {
|
|
298
|
-
const base = content[index] ?? "";
|
|
299
|
-
const stripeIndex = index < content.length
|
|
300
|
-
? this.contentStripeIndices[index] ?? index
|
|
301
|
-
: 0;
|
|
302
|
-
const popup = selectorRows[index];
|
|
303
|
-
if (!popup) {
|
|
304
|
-
lines.push(this.stripedRow(base, innerWidth, stripeIndex));
|
|
305
|
-
continue;
|
|
306
|
-
}
|
|
307
|
-
const transparentPrefix = popup.match(/^ +/)?.[0].length ?? 0;
|
|
308
|
-
const visiblePopup = popup.slice(transparentPrefix);
|
|
309
|
-
const popupWidth = visibleWidth(visiblePopup);
|
|
310
|
-
const popupAnchor = selectorAnchor + transparentPrefix;
|
|
311
|
-
const baseWidth = Math.max(0, innerWidth - popupWidth - popupAnchor);
|
|
312
|
-
const leadingBase = this.stripeBackground(
|
|
313
|
-
this.fit(this.takeVisiblePrefix(base, popupAnchor), popupAnchor),
|
|
314
|
-
stripeIndex,
|
|
315
|
-
);
|
|
316
|
-
const preservedBase = this.stripeBackground(
|
|
317
|
-
this.fit(
|
|
318
|
-
this.dropVisiblePrefix(base, popupWidth + popupAnchor),
|
|
319
|
-
baseWidth,
|
|
320
|
-
),
|
|
321
|
-
stripeIndex,
|
|
322
|
-
);
|
|
323
|
-
lines.push(this.row(`${leadingBase}${visiblePopup}${preservedBase}`, innerWidth, true));
|
|
233
|
+
let content = this.renderContent(run, innerWidth);
|
|
234
|
+
if (this.feedback) {
|
|
235
|
+
const color = this.feedback.ok
|
|
236
|
+
? "success"
|
|
237
|
+
: /^Kill (failed|rejected)/u.test(this.feedback.message)
|
|
238
|
+
? "error"
|
|
239
|
+
: "warning";
|
|
240
|
+
content = [this.theme.fg(color, ` ${this.feedback.message}`), ...content];
|
|
241
|
+
this.contentStripeIndices = [0, ...this.contentStripeIndices];
|
|
242
|
+
}
|
|
243
|
+
const viewportRows = this.contentViewportRows();
|
|
244
|
+
content = content.slice(0, viewportRows);
|
|
245
|
+
for (let index = 0; index < viewportRows; index += 1) {
|
|
246
|
+
lines.push(this.stripedRow(
|
|
247
|
+
content[index] ?? "",
|
|
248
|
+
innerWidth,
|
|
249
|
+
this.contentStripeIndices[index] ?? index,
|
|
250
|
+
));
|
|
324
251
|
}
|
|
325
252
|
lines.push(this.footerBorder(this.renderKeyHints(), innerWidth));
|
|
326
253
|
return lines;
|
|
327
254
|
}
|
|
328
255
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
dispose(): void {
|
|
332
|
-
clearInterval(this.refreshTimer);
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
private runs(): Inspector.ActorInspectorRunItem[] {
|
|
336
|
-
return Inspector.readActorInspectorRuns(this.stateRoot, this.ownerId);
|
|
256
|
+
private get tab(): ActorInspectorTab {
|
|
257
|
+
return TABS[this.tabIndex]!;
|
|
337
258
|
}
|
|
338
259
|
|
|
339
|
-
private
|
|
340
|
-
if (
|
|
260
|
+
private runs(): ActorInspector.ActorInspectorRunItem[] {
|
|
261
|
+
if (this.runCache) return this.runCache;
|
|
262
|
+
this.runCache = this.readRuns(this.stateRoot, this.ownerId);
|
|
263
|
+
return this.runCache;
|
|
341
264
|
}
|
|
342
265
|
|
|
343
266
|
private contentViewportRows(): number {
|
|
344
|
-
const
|
|
267
|
+
const rows = (this.tui as TUI & { terminal?: { rows?: number } }).terminal?.rows ?? 30;
|
|
268
|
+
const overlayRows = Math.floor(rows * 0.94);
|
|
345
269
|
return Math.max(4, Math.min(24, overlayRows - 5));
|
|
346
270
|
}
|
|
347
271
|
|
|
348
|
-
private
|
|
349
|
-
this.
|
|
350
|
-
this.
|
|
351
|
-
this.runIndex = index;
|
|
352
|
-
this.recipeScroll = 0;
|
|
272
|
+
private resetContentPosition(): void {
|
|
273
|
+
this.documentScroll = 0;
|
|
274
|
+
this.detailScroll = 0;
|
|
353
275
|
this.rowIndex = 0;
|
|
354
|
-
this.
|
|
355
|
-
this.communicationFrom = "all";
|
|
276
|
+
this.traceDetail = undefined;
|
|
356
277
|
}
|
|
357
278
|
|
|
358
|
-
private cycleRun(
|
|
359
|
-
runs: Inspector.ActorInspectorRunItem[],
|
|
360
|
-
direction: -1 | 1,
|
|
361
|
-
): void {
|
|
279
|
+
private cycleRun(runs: ActorInspector.ActorInspectorRunItem[], direction: -1 | 1): void {
|
|
362
280
|
if (runs.length === 0) return;
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
const
|
|
369
|
-
if (
|
|
281
|
+
this.runIndex = (this.runIndex + direction + runs.length) % runs.length;
|
|
282
|
+
this.resetContentPosition();
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
private handleScrollableInput(data: string, target: "detail" | "document"): void {
|
|
286
|
+
const key = target === "detail" ? "detailScroll" : "documentScroll";
|
|
287
|
+
if (matchesKey(data, "up")) this[key] = Math.max(0, this[key] - 1);
|
|
288
|
+
else if (matchesKey(data, "down")) this[key] += 1;
|
|
289
|
+
else if (matchesKey(data, "pageUp")) {
|
|
290
|
+
this[key] = Math.max(0, this[key] - this.contentViewportRows());
|
|
291
|
+
} else if (matchesKey(data, "pageDown")) {
|
|
292
|
+
this[key] += this.contentViewportRows();
|
|
293
|
+
} else if (matchesKey(data, "left") && target === "detail") {
|
|
294
|
+
this.traceDetail = undefined;
|
|
295
|
+
this.detailScroll = 0;
|
|
296
|
+
this.focus = "list";
|
|
297
|
+
}
|
|
370
298
|
}
|
|
371
299
|
|
|
372
|
-
private
|
|
300
|
+
private handleSelectorInput(
|
|
301
|
+
data: string,
|
|
302
|
+
runs: ActorInspector.ActorInspectorRunItem[],
|
|
303
|
+
): void {
|
|
304
|
+
const run = runs[this.runIndex];
|
|
305
|
+
const options = this.selectorMode === "run"
|
|
306
|
+
? runs.map((item) => item.run)
|
|
307
|
+
: this.traceSources(run);
|
|
308
|
+
if (matchesKey(data, "up")) this.selectorIndex = Math.max(0, this.selectorIndex - 1);
|
|
309
|
+
else if (matchesKey(data, "down")) {
|
|
310
|
+
this.selectorIndex = Math.min(Math.max(0, options.length - 1), this.selectorIndex + 1);
|
|
311
|
+
} else if (matchesKey(data, "left")) {
|
|
312
|
+
this.focus = this.selectorMode === "run" ? "runs" : "tabs";
|
|
313
|
+
this.selectorMode = undefined;
|
|
314
|
+
} else if (matchesKey(data, "return") || matchesKey(data, "right")) {
|
|
315
|
+
if (this.selectorMode === "run") {
|
|
316
|
+
this.runIndex = this.selectorIndex;
|
|
317
|
+
this.focus = "runs";
|
|
318
|
+
} else {
|
|
319
|
+
this.traceSourceIndex = this.selectorIndex;
|
|
320
|
+
this.focus = "tabs";
|
|
321
|
+
}
|
|
322
|
+
this.selectorMode = undefined;
|
|
323
|
+
this.resetContentPosition();
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
private openSourceSelector(run?: ActorInspector.ActorInspectorRunItem): void {
|
|
328
|
+
const sources = this.traceSources(run);
|
|
329
|
+
this.selectorMode = "source";
|
|
330
|
+
this.selectorIndex = Math.min(this.traceSourceIndex, sources.length - 1);
|
|
331
|
+
this.focus = "select";
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
private cycleTraceSource(run?: ActorInspector.ActorInspectorRunItem): void {
|
|
335
|
+
const sources = this.traceSources(run);
|
|
336
|
+
this.traceSourceIndex = (this.traceSourceIndex + 1) % sources.length;
|
|
337
|
+
this.resetContentPosition();
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
private requestKill(run?: ActorInspector.ActorInspectorRunItem): void {
|
|
373
341
|
this.feedback = undefined;
|
|
374
342
|
if (this.focus !== "runs") {
|
|
375
343
|
this.feedback = { ok: false, message: "Focus Run before using Kill." };
|
|
376
344
|
return;
|
|
377
345
|
}
|
|
378
|
-
const run = runs.find((item) => item.run === this.selectedRun);
|
|
379
346
|
if (!run) {
|
|
380
347
|
this.feedback = { ok: false, message: "Kill unavailable: no owned run." };
|
|
381
348
|
return;
|
|
@@ -385,17 +352,11 @@ export class ActorInspectorOverlay {
|
|
|
385
352
|
return;
|
|
386
353
|
}
|
|
387
354
|
if (run.status !== "running") {
|
|
388
|
-
this.feedback = {
|
|
389
|
-
ok: false,
|
|
390
|
-
message: `Kill unavailable: run is ${run.status}.`,
|
|
391
|
-
};
|
|
355
|
+
this.feedback = { ok: false, message: `Kill unavailable: run is ${run.status}.` };
|
|
392
356
|
return;
|
|
393
357
|
}
|
|
394
358
|
if (!run.runInstanceId) {
|
|
395
|
-
this.feedback = {
|
|
396
|
-
ok: false,
|
|
397
|
-
message: "Kill unavailable: run generation unavailable.",
|
|
398
|
-
};
|
|
359
|
+
this.feedback = { ok: false, message: "Kill unavailable: run generation unavailable." };
|
|
399
360
|
return;
|
|
400
361
|
}
|
|
401
362
|
this.killConfirmation = {
|
|
@@ -406,25 +367,32 @@ export class ActorInspectorOverlay {
|
|
|
406
367
|
this.killDialogChoice = "cancel";
|
|
407
368
|
}
|
|
408
369
|
|
|
409
|
-
private
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
370
|
+
private handleKillInput(data: string): void {
|
|
371
|
+
const key = data.toLowerCase();
|
|
372
|
+
if (matchesKey(data, "escape") || key === "n") {
|
|
373
|
+
this.feedback = { ok: false, message: "Kill cancelled." };
|
|
374
|
+
this.killConfirmation = undefined;
|
|
375
|
+
this.killDialogChoice = "cancel";
|
|
376
|
+
} else if (matchesKey(data, "left") || matchesKey(data, "right") || matchesKey(data, "tab")) {
|
|
377
|
+
this.killDialogChoice = this.killDialogChoice === "cancel" ? "kill" : "cancel";
|
|
378
|
+
} else if (key === "y") {
|
|
379
|
+
this.confirmKill();
|
|
380
|
+
} else if (matchesKey(data, "return")) {
|
|
381
|
+
if (this.killDialogChoice === "kill") this.confirmKill();
|
|
382
|
+
else {
|
|
383
|
+
this.feedback = { ok: false, message: "Kill cancelled." };
|
|
384
|
+
this.killConfirmation = undefined;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
413
387
|
}
|
|
414
388
|
|
|
415
389
|
private confirmKill(): void {
|
|
416
390
|
const confirmation = this.killConfirmation;
|
|
417
391
|
this.killConfirmation = undefined;
|
|
418
392
|
this.killDialogChoice = "cancel";
|
|
419
|
-
if (!confirmation || !this.killRun)
|
|
420
|
-
this.feedback = { ok: false, message: "Kill unavailable in this Inspector." };
|
|
421
|
-
return;
|
|
422
|
-
}
|
|
393
|
+
if (!confirmation || !this.killRun) return;
|
|
423
394
|
try {
|
|
424
|
-
this.feedback = this.killRun(
|
|
425
|
-
confirmation.run,
|
|
426
|
-
confirmation.runInstanceId,
|
|
427
|
-
);
|
|
395
|
+
this.feedback = this.killRun(confirmation.run, confirmation.runInstanceId);
|
|
428
396
|
} catch (error) {
|
|
429
397
|
const message = error instanceof Error ? error.message : String(error);
|
|
430
398
|
this.feedback = {
|
|
@@ -434,14 +402,13 @@ export class ActorInspectorOverlay {
|
|
|
434
402
|
}
|
|
435
403
|
}
|
|
436
404
|
|
|
437
|
-
private renderKillDialog(
|
|
405
|
+
private renderKillDialog(innerWidth: number): string[] {
|
|
438
406
|
const confirmation = this.killConfirmation!;
|
|
439
|
-
const totalRows = this.contentViewportRows() + 5;
|
|
440
407
|
const cancel = this.killDialogChoice === "cancel"
|
|
441
|
-
? this.theme.bg("
|
|
408
|
+
? this.theme.bg("customMessageBg", this.theme.fg("accent", " Cancel "))
|
|
442
409
|
: this.theme.fg("muted", " Cancel ");
|
|
443
410
|
const kill = this.killDialogChoice === "kill"
|
|
444
|
-
? this.theme.bg("
|
|
411
|
+
? this.theme.bg("customMessageBg", this.theme.fg("error", " Kill actor "))
|
|
445
412
|
: this.theme.fg("error", " Kill actor ");
|
|
446
413
|
const body = [
|
|
447
414
|
"",
|
|
@@ -450,863 +417,394 @@ export class ActorInspectorOverlay {
|
|
|
450
417
|
`${this.theme.fg("muted", "Run:")} ${this.theme.fg("accent", `run:${confirmation.run}`)}`,
|
|
451
418
|
`${this.theme.fg("muted", "Current status:")} ${this.theme.fg("warning", confirmation.status)}`,
|
|
452
419
|
"",
|
|
453
|
-
this.theme.fg("text", "This sends canonical
|
|
420
|
+
this.theme.fg("text", "This sends canonical kill Control."),
|
|
454
421
|
this.theme.fg("error", "The action is destructive and cannot be undone."),
|
|
455
422
|
"",
|
|
456
423
|
`${cancel} ${kill}`,
|
|
457
424
|
"",
|
|
458
425
|
];
|
|
459
426
|
const lines = [this.border("╭", " Confirm Actor Kill ", "╮", innerWidth)];
|
|
460
|
-
const availableRows =
|
|
427
|
+
const availableRows = this.contentViewportRows() + 3;
|
|
461
428
|
const topPadding = Math.max(0, Math.floor((availableRows - body.length) / 2));
|
|
462
|
-
const
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
].slice(0, availableRows);
|
|
466
|
-
while (dialogRows.length < availableRows) dialogRows.push("");
|
|
467
|
-
for (const line of dialogRows)
|
|
468
|
-
lines.push(this.row(this.center(line, innerWidth), innerWidth));
|
|
429
|
+
const rows = [...Array.from({ length: topPadding }, () => ""), ...body].slice(0, availableRows);
|
|
430
|
+
while (rows.length < availableRows) rows.push("");
|
|
431
|
+
for (const line of rows) lines.push(this.row(this.center(line, innerWidth), innerWidth));
|
|
469
432
|
lines.push(this.footerBorder(this.renderKeyHints(), innerWidth));
|
|
470
433
|
return lines;
|
|
471
434
|
}
|
|
472
435
|
|
|
473
|
-
private
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
private selectorAnchor(): number {
|
|
481
|
-
if (this.menuLevel === "run") {
|
|
482
|
-
const runControl = stripVTControlCharacters(this.renderRunControl());
|
|
483
|
-
return Math.max(0, runControl.indexOf("Run:") - 2);
|
|
484
|
-
}
|
|
485
|
-
const label = this.tab === "communications"
|
|
486
|
-
? "Messages"
|
|
487
|
-
: this.tab === "turns"
|
|
488
|
-
? "Turns"
|
|
489
|
-
: "Recipe";
|
|
490
|
-
const tabs = stripVTControlCharacters(this.renderTabs());
|
|
491
|
-
return Math.max(0, tabs.indexOf(label) - 2);
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
private renderKeyHints(): string {
|
|
495
|
-
const hint = (keys: string, description: string) =>
|
|
496
|
-
`${this.theme.fg("accent", keys)}${this.theme.fg("borderAccent", ` ${description}`)}`;
|
|
497
|
-
const divider = this.theme.fg("borderAccent", " ─ ");
|
|
498
|
-
const hints = (...items: Array<[string, string]>) =>
|
|
499
|
-
items.map(([keys, description]) => hint(keys, description)).join(divider);
|
|
500
|
-
if (this.killConfirmation)
|
|
501
|
-
return hints(
|
|
502
|
-
["←→/tab", "choose"],
|
|
503
|
-
["enter/y", "confirm"],
|
|
504
|
-
["esc/n", "cancel"],
|
|
505
|
-
);
|
|
506
|
-
if (this.focus === "select")
|
|
507
|
-
return this.menuLevel === "value"
|
|
508
|
-
? hints(["↑↓", "option"], ["enter", "apply"], ["←/esc", "back"])
|
|
509
|
-
: hints(["↑↓", "option"], ["→/enter", "open"], ["←/esc", "back"]);
|
|
510
|
-
if (this.focus === "recipe")
|
|
511
|
-
return hints(
|
|
512
|
-
["↑↓/pgup/pgdn", "scroll"],
|
|
513
|
-
["↑ at top", "tabs"],
|
|
514
|
-
["←/esc", "tabs"],
|
|
515
|
-
);
|
|
516
|
-
if (this.focus === "detail")
|
|
517
|
-
return hints(["↑↓/pgup/pgdn", "scroll"], ["←/esc", "back"]);
|
|
518
|
-
if (this.focus === "list")
|
|
519
|
-
return hints(
|
|
520
|
-
["↑↓/pgup/pgdn", "row"],
|
|
521
|
-
["→/enter", "open"],
|
|
522
|
-
["←", "tabs"],
|
|
523
|
-
["esc", "close"],
|
|
524
|
-
);
|
|
525
|
-
if (this.focus === "runs") {
|
|
526
|
-
const items: Array<[string, string]> = [
|
|
527
|
-
["←→", "run"],
|
|
528
|
-
["↓", "tabs"],
|
|
529
|
-
["enter", "list"],
|
|
530
|
-
];
|
|
531
|
-
const run = this.runs().find((item) => item.run === this.selectedRun);
|
|
532
|
-
if (this.killRun && run?.status === "running" && run.runInstanceId)
|
|
533
|
-
items.push(["k", "kill"]);
|
|
534
|
-
items.push(["esc", "close"]);
|
|
535
|
-
return hints(...items);
|
|
536
|
-
}
|
|
537
|
-
return hints(
|
|
538
|
-
["←→", "navigate"],
|
|
539
|
-
["↑↓", "change row"],
|
|
540
|
-
["enter", "select"],
|
|
541
|
-
["esc", "close"],
|
|
542
|
-
);
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
private renderRunControl(): string {
|
|
546
|
-
const runs = this.runs();
|
|
547
|
-
const runIndex = runs.findIndex((item) => item.run === this.selectedRun);
|
|
548
|
-
const run = runs[runIndex];
|
|
549
|
-
const focused = this.focus === "runs";
|
|
550
|
-
const active = focused || (this.focus === "select" && this.menuLevel === "run");
|
|
436
|
+
private renderRunControl(
|
|
437
|
+
run: ActorInspector.ActorInspectorRunItem | undefined,
|
|
438
|
+
sequence: number,
|
|
439
|
+
): string {
|
|
440
|
+
const active = this.focus === "runs" || (this.focus === "select" && this.selectorMode === "run");
|
|
551
441
|
const prefix = active ? this.theme.fg("accent", " ← ") : " ";
|
|
552
442
|
const suffix = active ? this.theme.fg("accent", " → ") : " ";
|
|
553
443
|
if (!run) {
|
|
554
444
|
const empty = this.theme.fg("muted", `${prefix}Run: none${suffix}`);
|
|
555
|
-
return
|
|
445
|
+
return this.focus === "runs" ? this.theme.bg("customMessageBg", empty) : empty;
|
|
556
446
|
}
|
|
557
|
-
const
|
|
558
|
-
|
|
559
|
-
: run.status === "running"
|
|
560
|
-
? "warning"
|
|
561
|
-
: run.status === "done"
|
|
562
|
-
? "success"
|
|
563
|
-
: "muted";
|
|
564
|
-
const sequence = runs.length - runIndex;
|
|
565
|
-
const value = `${prefix}${this.theme.fg("muted", "Run: ")}${this.theme.fg("text", `#${sequence}`)} ${this.theme.fg("accent", run.run)} ${this.theme.fg(statusColor, run.status)}${suffix}`;
|
|
566
|
-
return focused ? this.theme.bg("selectedBg", value) : value;
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
private activeMessageFilters(): string[] {
|
|
570
|
-
return [
|
|
571
|
-
...(this.communicationChannel === "all" ? [] : [this.communicationChannel]),
|
|
572
|
-
...(this.communicationUnread ? ["unread"] : []),
|
|
573
|
-
...(this.communicationFrom === "all" ? [] : [this.communicationFrom]),
|
|
574
|
-
];
|
|
447
|
+
const value = `${prefix}${this.theme.fg("muted", "Run: ")}${this.theme.fg("text", `#${sequence}`)} ${this.theme.fg("accent", run.run)} ${this.theme.fg(this.statusColor(run.status), run.status)}${suffix}`;
|
|
448
|
+
return this.focus === "runs" ? this.theme.bg("customMessageBg", value) : value;
|
|
575
449
|
}
|
|
576
450
|
|
|
577
|
-
private renderTabs(): string {
|
|
578
|
-
const
|
|
579
|
-
|
|
580
|
-
const
|
|
581
|
-
const
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
const display = selected && !runActive ? `[ ${label} ]` : ` ${label} `;
|
|
451
|
+
private renderTabs(run?: ActorInspector.ActorInspectorRunItem): string {
|
|
452
|
+
const source = this.traceSource(run);
|
|
453
|
+
return TABS.map((tab, index) => {
|
|
454
|
+
const base = `${tab[0]!.toUpperCase()}${tab.slice(1)}`;
|
|
455
|
+
const label = tab === "trace" && source !== "all" ? `${base} (${source})` : base;
|
|
456
|
+
const selected = index === this.tabIndex;
|
|
457
|
+
const display = selected && this.focus !== "runs" ? `[ ${label} ]` : ` ${label} `;
|
|
585
458
|
const value = ` ${display} `;
|
|
586
459
|
if (!selected) return this.theme.fg("muted", value);
|
|
587
460
|
const colored = this.theme.fg("accent", value);
|
|
588
|
-
return
|
|
589
|
-
};
|
|
590
|
-
const messageFilters = this.activeMessageFilters();
|
|
591
|
-
const subagent = this.subagents()[this.subagentIndex] ?? "all";
|
|
592
|
-
const messagesLabel = `Messages${messageFilters.length > 0 ? ` (${messageFilters.join(", ")})` : ""}`;
|
|
593
|
-
const turnsLabel = `Turns${subagent === "all" ? "" : ` (${subagent})`}`;
|
|
594
|
-
return `${tab("recipe", "Recipe")} ${tab("communications", messagesLabel)} ${tab("turns", turnsLabel)}`;
|
|
461
|
+
return this.focus === "tabs" ? this.theme.bg("customMessageBg", colored) : colored;
|
|
462
|
+
}).join(" ");
|
|
595
463
|
}
|
|
596
464
|
|
|
597
|
-
private
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
if (!run) {
|
|
616
|
-
this.selectedRun = undefined;
|
|
617
|
-
return [this.theme.fg("warning", " Selected run is no longer owned by this session")];
|
|
618
|
-
}
|
|
619
|
-
if (this.tab === "recipe") return this.renderRecipe(width);
|
|
620
|
-
const rows =
|
|
621
|
-
this.tab === "communications"
|
|
622
|
-
? this.communicationRows()
|
|
623
|
-
: this.turnRows();
|
|
624
|
-
if (rows.length === 0) {
|
|
625
|
-
this.contentStripeIndices = [0];
|
|
626
|
-
const filtered = this.tab === "communications"
|
|
627
|
-
? this.activeMessageFilters().length > 0
|
|
628
|
-
: (this.subagents()[this.subagentIndex] ?? "all") !== "all";
|
|
629
|
-
return [
|
|
630
|
-
this.theme.fg(
|
|
631
|
-
"muted",
|
|
632
|
-
filtered
|
|
633
|
-
? " No results for the active filters. Enter on the tab to adjust."
|
|
634
|
-
: this.tab === "communications"
|
|
635
|
-
? " No messages in this run"
|
|
636
|
-
: " No persisted turns in this run",
|
|
637
|
-
),
|
|
638
|
-
];
|
|
639
|
-
}
|
|
640
|
-
this.rowIndex = Math.min(this.rowIndex, rows.length - 1);
|
|
641
|
-
const viewportRows = this.contentViewportRows();
|
|
642
|
-
const start = Math.max(
|
|
643
|
-
0,
|
|
644
|
-
Math.min(
|
|
645
|
-
this.rowIndex - Math.floor(viewportRows / 2),
|
|
646
|
-
Math.max(0, rows.length - viewportRows),
|
|
647
|
-
),
|
|
465
|
+
private renderContent(
|
|
466
|
+
run: ActorInspector.ActorInspectorRunItem | undefined,
|
|
467
|
+
width: number,
|
|
468
|
+
): string[] {
|
|
469
|
+
if (!run) return [this.theme.fg("muted", " No owned actor runs")];
|
|
470
|
+
if (this.focus === "select") return this.renderSelector(run);
|
|
471
|
+
if (this.focus === "detail") return this.renderTraceDetail(width);
|
|
472
|
+
if (this.tab === "trace") return this.renderTraceList(run);
|
|
473
|
+
const stateDir = path.join(this.stateRoot, run.run);
|
|
474
|
+
const value = this.tab === "recipe"
|
|
475
|
+
? ActorInspector.readActorInspectorRecipe(stateDir)
|
|
476
|
+
: this.controlDocument(run, stateDir);
|
|
477
|
+
const projection = this.documentProjection(value, width);
|
|
478
|
+
const maxStart = Math.max(0, projection.lines.length - this.contentViewportRows());
|
|
479
|
+
this.documentScroll = Math.min(this.documentScroll, maxStart);
|
|
480
|
+
this.contentStripeIndices = projection.stripes.slice(
|
|
481
|
+
this.documentScroll,
|
|
482
|
+
this.documentScroll + this.contentViewportRows(),
|
|
648
483
|
);
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
)
|
|
653
|
-
: this.turnItems().map((turn) => Math.max(0, turn.index - 1));
|
|
654
|
-
const visibleRows = rows.slice(start, start + viewportRows);
|
|
655
|
-
this.contentStripeIndices = visibleRows.map(
|
|
656
|
-
(_, offset) => stripeIndices[start + offset] ?? start + offset,
|
|
484
|
+
return projection.lines.slice(
|
|
485
|
+
this.documentScroll,
|
|
486
|
+
this.documentScroll + this.contentViewportRows(),
|
|
657
487
|
);
|
|
658
|
-
return visibleRows.map((row, offset) => {
|
|
659
|
-
const index = start + offset;
|
|
660
|
-
const selected = this.focus === "list" && index === this.rowIndex;
|
|
661
|
-
return selected
|
|
662
|
-
? this.theme.fg("accent", ` ▶ ${row}`)
|
|
663
|
-
: ` ${row}`;
|
|
664
|
-
});
|
|
665
488
|
}
|
|
666
489
|
|
|
667
|
-
private
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
);
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
),
|
|
687
|
-
...(
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
490
|
+
private renderSelector(run: ActorInspector.ActorInspectorRunItem): string[] {
|
|
491
|
+
const options = this.selectorMode === "run"
|
|
492
|
+
? this.runs().map((item) => `run:${item.run} ${item.status}`)
|
|
493
|
+
: this.traceSources(run).map((source) => `Trace source: ${source}`);
|
|
494
|
+
const lines = this.renderMenuBox(options, this.selectorIndex);
|
|
495
|
+
this.contentStripeIndices = lines.map(() => 0);
|
|
496
|
+
return lines;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
private renderMenuBox(options: string[], focusedIndex: number): string[] {
|
|
500
|
+
if (options.length === 0) return [this.theme.fg("muted", " No options")];
|
|
501
|
+
const visibleLimit = Math.max(1, Math.min(this.contentViewportRows(), options.length));
|
|
502
|
+
const maxStart = Math.max(0, options.length - visibleLimit);
|
|
503
|
+
const start = Math.max(0, Math.min(focusedIndex - Math.floor(visibleLimit / 2), maxStart));
|
|
504
|
+
const visible = options.slice(start, start + visibleLimit);
|
|
505
|
+
const width = Math.max(8, ...options.map((option) => visibleWidth(option) + 4));
|
|
506
|
+
const border = (left: string, right: string, marker = "") =>
|
|
507
|
+
this.theme.fg("borderAccent", `${left}${marker}${"─".repeat(Math.max(0, width - visibleWidth(marker)))}${right}`);
|
|
508
|
+
return [
|
|
509
|
+
border("╭", "╮", start > 0 ? "↑" : ""),
|
|
510
|
+
...visible.map((option, offset) => {
|
|
511
|
+
const index = start + offset;
|
|
512
|
+
const row = this.fit(`${index === focusedIndex ? " ▶ " : " "}${option}`, width);
|
|
513
|
+
const colored = index === focusedIndex ? this.theme.fg("accent", row) : row;
|
|
514
|
+
const styled = index === focusedIndex ? this.theme.bg("customMessageBg", colored) : colored;
|
|
515
|
+
return `${this.theme.fg("borderAccent", "│")}${styled}${this.theme.fg("borderAccent", "│")}`;
|
|
516
|
+
}),
|
|
517
|
+
border("╰", "╯", start + visible.length < options.length ? "↓" : ""),
|
|
694
518
|
];
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
);
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
private renderTraceList(run: ActorInspector.ActorInspectorRunItem): string[] {
|
|
522
|
+
const items = this.traceItems(run);
|
|
523
|
+
if (items.length === 0) return [this.theme.fg("muted", " No Trace evidence")];
|
|
524
|
+
this.rowIndex = Math.min(this.rowIndex, items.length - 1);
|
|
699
525
|
const viewportRows = this.contentViewportRows();
|
|
700
|
-
const maxStart = Math.max(0,
|
|
701
|
-
|
|
702
|
-
this.contentStripeIndices =
|
|
703
|
-
|
|
704
|
-
|
|
526
|
+
const maxStart = Math.max(0, items.length - viewportRows);
|
|
527
|
+
const start = Math.max(0, Math.min(this.rowIndex - Math.floor(viewportRows / 2), maxStart));
|
|
528
|
+
this.contentStripeIndices = items
|
|
529
|
+
.slice(start, start + viewportRows)
|
|
530
|
+
.map((_item, offset) => start + offset);
|
|
531
|
+
return items.slice(start, start + viewportRows).map((item, offset) => {
|
|
532
|
+
const index = start + offset;
|
|
533
|
+
const detail = item.detail && typeof item.detail === "object"
|
|
534
|
+
? item.detail as Record<string, unknown>
|
|
535
|
+
: {};
|
|
536
|
+
const marker = item.level === "error"
|
|
537
|
+
? this.theme.fg("error", "!")
|
|
538
|
+
: detail.attention
|
|
539
|
+
? this.theme.fg("warning", "•")
|
|
540
|
+
: this.theme.fg("muted", "·");
|
|
541
|
+
const prefix = this.focus === "list" && index === this.rowIndex
|
|
542
|
+
? this.theme.fg("accent", " ▶ ")
|
|
543
|
+
: " ";
|
|
544
|
+
const row = `${prefix}${this.theme.fg("text", `#${index + 1}`)} ${marker} ${this.theme.fg("muted", item.source)}/${this.theme.fg(item.level === "error" ? "error" : "accent", item.kind)} ${this.theme.fg("text", item.summary)}`;
|
|
545
|
+
return this.focus === "list" && index === this.rowIndex
|
|
546
|
+
? this.theme.fg("accent", row)
|
|
547
|
+
: row;
|
|
548
|
+
});
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
private renderTraceDetail(width: number): string[] {
|
|
552
|
+
if (!this.traceDetail) return [this.theme.fg("warning", " Trace row is no longer available")];
|
|
553
|
+
const projection = this.documentProjection(this.traceDetail, width);
|
|
554
|
+
const maxStart = Math.max(0, projection.lines.length - this.contentViewportRows());
|
|
555
|
+
this.detailScroll = Math.min(this.detailScroll, maxStart);
|
|
556
|
+
this.contentStripeIndices = projection.stripes.slice(
|
|
557
|
+
this.detailScroll,
|
|
558
|
+
this.detailScroll + this.contentViewportRows(),
|
|
705
559
|
);
|
|
706
|
-
return
|
|
707
|
-
this.
|
|
708
|
-
this.
|
|
560
|
+
return projection.lines.slice(
|
|
561
|
+
this.detailScroll,
|
|
562
|
+
this.detailScroll + this.contentViewportRows(),
|
|
709
563
|
);
|
|
710
564
|
}
|
|
711
565
|
|
|
712
|
-
private
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
.
|
|
727
|
-
|
|
728
|
-
this.communicationFrom === "all" ||
|
|
729
|
-
this.communicationActorLabel(preview.from_display ?? preview.from) ===
|
|
730
|
-
this.communicationFrom,
|
|
731
|
-
)
|
|
732
|
-
.reverse();
|
|
566
|
+
private controlDocument(
|
|
567
|
+
run: ActorInspector.ActorInspectorRunItem,
|
|
568
|
+
stateDir: string,
|
|
569
|
+
): Record<string, unknown> {
|
|
570
|
+
const endpoint = run.runInstanceId
|
|
571
|
+
? RunControlDelivery.readRunControlEndpoint(stateDir, run.runInstanceId)
|
|
572
|
+
: undefined;
|
|
573
|
+
const meta = this.readRunMeta(stateDir);
|
|
574
|
+
return {
|
|
575
|
+
status: run.status,
|
|
576
|
+
run_instance_id: run.runInstanceId,
|
|
577
|
+
actor_actions: Array.isArray(meta.control) ? meta.control : [],
|
|
578
|
+
runtime_actions: run.status === "running" ? ["kill"] : ["archive", "prune"],
|
|
579
|
+
endpoint,
|
|
580
|
+
recent_controls: RunsControls.readRunControlsFromStateDir(stateDir).slice(-20).reverse(),
|
|
581
|
+
};
|
|
733
582
|
}
|
|
734
583
|
|
|
735
|
-
private
|
|
736
|
-
|
|
737
|
-
if (
|
|
738
|
-
|
|
584
|
+
private allTraceItems(run?: ActorInspector.ActorInspectorRunItem): TraceProjection.TraceItem[] {
|
|
585
|
+
if (!run) return [];
|
|
586
|
+
if (
|
|
587
|
+
this.traceCache?.run === run.run &&
|
|
588
|
+
this.traceCache.runInstanceId === run.runInstanceId
|
|
589
|
+
) {
|
|
590
|
+
return this.traceCache.items;
|
|
591
|
+
}
|
|
592
|
+
const items = this.readTrace(path.join(this.stateRoot, run.run), {
|
|
593
|
+
limit: 100,
|
|
594
|
+
source: "all",
|
|
595
|
+
});
|
|
596
|
+
this.traceCache = { run: run.run, runInstanceId: run.runInstanceId, items };
|
|
597
|
+
return items;
|
|
739
598
|
}
|
|
740
599
|
|
|
741
|
-
private
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
this.stateRoot,
|
|
745
|
-
this.selectedRun,
|
|
746
|
-
).map((member) =>
|
|
747
|
-
this.communicationActorLabel(member.display ?? member.address),
|
|
748
|
-
);
|
|
749
|
-
return ["all", ...new Set(actors)];
|
|
600
|
+
private traceSources(run?: ActorInspector.ActorInspectorRunItem): TraceProjection.TraceSourceFilter[] {
|
|
601
|
+
const present = new Set(this.allTraceItems(run).map((item) => item.source));
|
|
602
|
+
return TRACE_SOURCES.filter((source) => source === "all" || present.has(source));
|
|
750
603
|
}
|
|
751
604
|
|
|
752
|
-
private
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
const text = preview.summary ?? preview.body_preview ?? "(no body)";
|
|
757
|
-
return `#${preview.sequence ?? "?"} ${preview.needs_response ? "! " : ""}${actor} → ${target} ${preview.type} ${text}`;
|
|
758
|
-
});
|
|
605
|
+
private traceSource(run?: ActorInspector.ActorInspectorRunItem): TraceProjection.TraceSourceFilter {
|
|
606
|
+
const sources = this.traceSources(run);
|
|
607
|
+
this.traceSourceIndex %= sources.length;
|
|
608
|
+
return sources[this.traceSourceIndex] ?? "all";
|
|
759
609
|
}
|
|
760
610
|
|
|
761
|
-
private
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
const fields: Array<[string, unknown]> = [
|
|
766
|
-
["channel", preview.channel],
|
|
767
|
-
["run", preview.run],
|
|
768
|
-
["from", preview.from_display ?? preview.from],
|
|
769
|
-
["to", preview.to],
|
|
770
|
-
["type", preview.type],
|
|
771
|
-
["summary", preview.summary],
|
|
772
|
-
["body", preview.body_preview],
|
|
773
|
-
["attention", preview.needs_response],
|
|
774
|
-
["inbox", preview.inbox_status],
|
|
775
|
-
["timestamp", preview.timestamp],
|
|
776
|
-
];
|
|
777
|
-
const lines = fields
|
|
778
|
-
.filter(([, value]) => value !== undefined)
|
|
779
|
-
.map(([label, value]) =>
|
|
780
|
-
truncateToWidth(
|
|
781
|
-
` ${label.padEnd(12, " ")} ${typeof value === "string" ? value : JSON.stringify(value)}`,
|
|
782
|
-
width,
|
|
783
|
-
"…",
|
|
784
|
-
),
|
|
785
|
-
);
|
|
786
|
-
const viewportRows = this.contentViewportRows();
|
|
787
|
-
const maxStart = Math.max(0, lines.length - viewportRows);
|
|
788
|
-
this.detailScroll = Math.min(this.detailScroll, maxStart);
|
|
789
|
-
return lines.slice(this.detailScroll, this.detailScroll + viewportRows);
|
|
790
|
-
}
|
|
791
|
-
const turn = this.detailTurn;
|
|
792
|
-
if (!turn) return [this.theme.fg("warning", " Turn row is no longer available")];
|
|
793
|
-
const logicalLines = this.turnEvidenceLines(turn);
|
|
794
|
-
const wrapped = this.wrapDetailLines(logicalLines, width);
|
|
795
|
-
const viewportRows = this.contentViewportRows();
|
|
796
|
-
const maxStart = Math.max(0, wrapped.lines.length - viewportRows);
|
|
797
|
-
this.detailScroll = Math.min(this.detailScroll, maxStart);
|
|
798
|
-
this.contentStripeIndices = wrapped.stripeIndices.slice(
|
|
799
|
-
this.detailScroll,
|
|
800
|
-
this.detailScroll + viewportRows,
|
|
801
|
-
);
|
|
802
|
-
return wrapped.lines.slice(
|
|
803
|
-
this.detailScroll,
|
|
804
|
-
this.detailScroll + viewportRows,
|
|
805
|
-
);
|
|
611
|
+
private traceItems(run?: ActorInspector.ActorInspectorRunItem): TraceProjection.TraceItem[] {
|
|
612
|
+
const source = this.traceSource(run);
|
|
613
|
+
const items = this.allTraceItems(run);
|
|
614
|
+
return source === "all" ? items : items.filter((item) => item.source === source);
|
|
806
615
|
}
|
|
807
616
|
|
|
808
|
-
private
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
const stripeIndices: number[] = [];
|
|
814
|
-
let sectionIndex = 0;
|
|
815
|
-
for (const line of lines) {
|
|
816
|
-
if (typeof line !== "string") {
|
|
817
|
-
sectionIndex += 1;
|
|
818
|
-
continue;
|
|
819
|
-
}
|
|
820
|
-
const physicalLines = line.replaceAll("\t", " ").split(/\r?\n/);
|
|
821
|
-
for (const physicalLine of physicalLines) {
|
|
822
|
-
if (!physicalLine.trim()) continue;
|
|
823
|
-
const leading = physicalLine.match(/^ */)?.[0] ?? "";
|
|
824
|
-
const content = physicalLine.slice(leading.length);
|
|
825
|
-
const contentWidth = Math.max(1, width - visibleWidth(leading));
|
|
826
|
-
const labeled = /^(\s*(?:-\s+)?[^:]+:\s+)(\S.*)$/u.exec(physicalLine);
|
|
827
|
-
let wrapped: string[];
|
|
828
|
-
if (
|
|
829
|
-
visibleWidth(physicalLine) > width &&
|
|
830
|
-
labeled &&
|
|
831
|
-
visibleWidth(labeled[1]) < width
|
|
832
|
-
) {
|
|
833
|
-
const prefix = labeled[1];
|
|
834
|
-
const value = labeled[2];
|
|
835
|
-
const firstWidth = Math.max(1, width - visibleWidth(prefix));
|
|
836
|
-
const first = this.wrapPreferred(value, firstWidth)[0] ??
|
|
837
|
-
this.takeVisiblePrefix(value, firstWidth);
|
|
838
|
-
const remainder = value.slice(first.length).trimStart();
|
|
839
|
-
const continuation = `${leading} `;
|
|
840
|
-
const continuationWidth = Math.max(
|
|
841
|
-
1,
|
|
842
|
-
width - visibleWidth(continuation),
|
|
843
|
-
);
|
|
844
|
-
wrapped = [
|
|
845
|
-
`${prefix}${first}`,
|
|
846
|
-
...(remainder
|
|
847
|
-
? this.wrapPreferred(remainder, continuationWidth).map(
|
|
848
|
-
(visualLine) => `${continuation}${visualLine}`,
|
|
849
|
-
)
|
|
850
|
-
: []),
|
|
851
|
-
];
|
|
852
|
-
} else {
|
|
853
|
-
wrapped = visibleWidth(physicalLine) <= width
|
|
854
|
-
? [physicalLine]
|
|
855
|
-
: this.wrapPreferred(content, contentWidth).map(
|
|
856
|
-
(visualLine) => `${leading}${visualLine}`,
|
|
857
|
-
);
|
|
858
|
-
}
|
|
859
|
-
for (const visualLine of wrapped) {
|
|
860
|
-
if (!visualLine.trim()) continue;
|
|
861
|
-
wrappedLines.push(visualLine);
|
|
862
|
-
stripeIndices.push(sectionIndex);
|
|
863
|
-
}
|
|
864
|
-
}
|
|
617
|
+
private readRunMeta(stateDir: string): Record<string, unknown> {
|
|
618
|
+
try {
|
|
619
|
+
return JSON.parse(readFileSync(path.join(stateDir, "run.json"), "utf8")) as Record<string, unknown>;
|
|
620
|
+
} catch {
|
|
621
|
+
return {};
|
|
865
622
|
}
|
|
866
|
-
return { lines: wrappedLines, stripeIndices };
|
|
867
623
|
}
|
|
868
624
|
|
|
869
|
-
private
|
|
625
|
+
private documentProjection(
|
|
626
|
+
value: unknown,
|
|
627
|
+
width: number,
|
|
628
|
+
): { lines: string[]; stripes: number[] } {
|
|
629
|
+
const sections = value && typeof value === "object" && !Array.isArray(value)
|
|
630
|
+
? Object.entries(value as Record<string, unknown>).map(([key, item]) => {
|
|
631
|
+
const inline = this.inlineDocumentValue(item);
|
|
632
|
+
return inline !== undefined
|
|
633
|
+
? [`${key}: ${inline}`]
|
|
634
|
+
: item && typeof item === "object"
|
|
635
|
+
? [`${key}:`, ...this.document(item, 1)]
|
|
636
|
+
: this.labeledScalarLines(key, item);
|
|
637
|
+
})
|
|
638
|
+
: [this.document(value)];
|
|
870
639
|
const lines: string[] = [];
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
const
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
640
|
+
const stripes: number[] = [];
|
|
641
|
+
sections.forEach((section, sectionIndex) => {
|
|
642
|
+
const wrapped = this.wrapDocument(section, width);
|
|
643
|
+
lines.push(...wrapped.map((line) => this.styleDocumentLine(line)));
|
|
644
|
+
stripes.push(...wrapped.map(() => sectionIndex));
|
|
645
|
+
});
|
|
646
|
+
return { lines, stripes };
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
private inlineDocumentValue(value: unknown): string | undefined {
|
|
650
|
+
if (Array.isArray(value)) {
|
|
651
|
+
if (value.length === 0) return "[]";
|
|
652
|
+
if (value.every((item) =>
|
|
653
|
+
(item === null || typeof item !== "object") &&
|
|
654
|
+
!(typeof item === "string" && /[\r\n]/u.test(item))
|
|
655
|
+
)) {
|
|
656
|
+
return `[${value.map((item) => String(item ?? "none")).join(", ")}]`;
|
|
884
657
|
}
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
658
|
+
return undefined;
|
|
659
|
+
}
|
|
660
|
+
if (value && typeof value === "object") {
|
|
661
|
+
const entries = Object.entries(value as Record<string, unknown>);
|
|
662
|
+
if (entries.length === 0) return "{}";
|
|
663
|
+
if (
|
|
664
|
+
entries.length <= 3 &&
|
|
665
|
+
entries.every(([, item]) =>
|
|
666
|
+
(item === null || typeof item !== "object") &&
|
|
667
|
+
!(typeof item === "string" && /[\r\n]/u.test(item))
|
|
668
|
+
)
|
|
669
|
+
) {
|
|
670
|
+
return `{ ${entries
|
|
671
|
+
.map(([key, item]) => `${key}: ${String(item ?? "none")}`)
|
|
672
|
+
.join(", ")} }`;
|
|
891
673
|
}
|
|
892
|
-
lines.push(head);
|
|
893
|
-
remaining = remaining.slice(splitAt);
|
|
894
|
-
if (trimBoundary) remaining = remaining.trimStart();
|
|
895
674
|
}
|
|
896
|
-
|
|
897
|
-
return lines;
|
|
675
|
+
return undefined;
|
|
898
676
|
}
|
|
899
677
|
|
|
900
|
-
private
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
): DetailLogicalLine[] {
|
|
904
|
-
if (content.length === 0) return [];
|
|
678
|
+
private labeledScalarLines(key: string, value: unknown, depth = 0): string[] {
|
|
679
|
+
const indent = " ".repeat(depth);
|
|
680
|
+
const [first = "", ...rest] = String(value ?? "none").split(/\r?\n/u);
|
|
905
681
|
return [
|
|
906
|
-
|
|
907
|
-
...
|
|
908
|
-
line
|
|
909
|
-
.split(/\r?\n/)
|
|
910
|
-
.filter((physicalLine) => physicalLine.trim().length > 0)
|
|
911
|
-
.map((physicalLine) => ` ${physicalLine}`),
|
|
912
|
-
),
|
|
913
|
-
DETAIL_SECTION_BREAK,
|
|
682
|
+
`${indent}${key}: ${first}`,
|
|
683
|
+
...rest.map((line) => `${indent} ${line}`),
|
|
914
684
|
];
|
|
915
685
|
}
|
|
916
686
|
|
|
917
|
-
private
|
|
918
|
-
value
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
): string[] {
|
|
922
|
-
if (depth > 8) return [`${indent}[nested value]`];
|
|
923
|
-
if (typeof value === "string") {
|
|
924
|
-
const trimmed = value.trim();
|
|
925
|
-
if (/^[{[]/.test(trimmed)) {
|
|
926
|
-
try {
|
|
927
|
-
return this.readableValueLines(JSON.parse(trimmed), indent, depth + 1);
|
|
928
|
-
} catch {
|
|
929
|
-
// Keep non-JSON text as written.
|
|
930
|
-
}
|
|
931
|
-
}
|
|
932
|
-
return value.split(/\r?\n/).map((line) => `${indent}${line}`);
|
|
933
|
-
}
|
|
934
|
-
if (value === null || value === undefined)
|
|
935
|
-
return [`${indent}${String(value)}`];
|
|
936
|
-
if (typeof value !== "object") return [`${indent}${String(value)}`];
|
|
687
|
+
private document(value: unknown, depth = 0): string[] {
|
|
688
|
+
if (value === null || value === undefined) return [`${" ".repeat(depth)}none`];
|
|
689
|
+
const inline = this.inlineDocumentValue(value);
|
|
690
|
+
if (inline !== undefined) return [`${" ".repeat(depth)}${inline}`];
|
|
937
691
|
if (Array.isArray(value)) {
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
(item
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
);
|
|
692
|
+
return value.flatMap((item, index) => {
|
|
693
|
+
const marker = `${" ".repeat(depth)}- #${index + 1}`;
|
|
694
|
+
const itemInline = this.inlineDocumentValue(item);
|
|
695
|
+
return itemInline !== undefined
|
|
696
|
+
? [`${marker}: ${itemInline}`]
|
|
697
|
+
: [marker, ...this.document(item, depth + 1)];
|
|
698
|
+
});
|
|
699
|
+
}
|
|
700
|
+
if (typeof value === "object") {
|
|
701
|
+
return Object.entries(value as Record<string, unknown>).flatMap(([key, item]) => {
|
|
702
|
+
const itemInline = this.inlineDocumentValue(item);
|
|
703
|
+
if (itemInline !== undefined) {
|
|
704
|
+
return [`${" ".repeat(depth)}${key}: ${itemInline}`];
|
|
951
705
|
}
|
|
952
|
-
if (
|
|
953
|
-
return [`${
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
`${indent} `,
|
|
957
|
-
depth + 1,
|
|
958
|
-
);
|
|
959
|
-
return [
|
|
960
|
-
`${indent}- ${first.trimStart()}`,
|
|
961
|
-
...rest,
|
|
962
|
-
];
|
|
706
|
+
if (item && typeof item === "object") {
|
|
707
|
+
return [`${" ".repeat(depth)}${key}:`, ...this.document(item, depth + 1)];
|
|
708
|
+
}
|
|
709
|
+
return this.labeledScalarLines(key, item, depth);
|
|
963
710
|
});
|
|
964
711
|
}
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
712
|
+
return String(value)
|
|
713
|
+
.split(/\r?\n/u)
|
|
714
|
+
.map((line) => `${" ".repeat(depth)}${line}`);
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
private wrapDocument(lines: string[], width: number): string[] {
|
|
718
|
+
return lines.flatMap((line) => {
|
|
719
|
+
const normalized = line.replaceAll("\t", " ");
|
|
720
|
+
if (visibleWidth(normalized) <= width) return [normalized];
|
|
721
|
+
const leading = normalized.match(/^ */u)?.[0] ?? "";
|
|
722
|
+
const continuation = `${leading} `;
|
|
723
|
+
const wrapped: string[] = [];
|
|
724
|
+
let rest = normalized.slice(leading.length);
|
|
725
|
+
let prefix = leading;
|
|
726
|
+
while (rest && visibleWidth(`${prefix}${rest}`) > width) {
|
|
727
|
+
const available = Math.max(1, width - visibleWidth(prefix));
|
|
728
|
+
const candidate = this.visiblePrefix(rest, available);
|
|
729
|
+
const minimumBreak = Math.floor(candidate.length * 0.6);
|
|
730
|
+
const preferred = Math.max(
|
|
731
|
+
candidate.lastIndexOf(" "),
|
|
732
|
+
candidate.lastIndexOf("/"),
|
|
733
|
+
candidate.lastIndexOf(","),
|
|
734
|
+
);
|
|
735
|
+
const cut = preferred >= minimumBreak ? preferred + 1 : candidate.length;
|
|
736
|
+
wrapped.push(`${prefix}${rest.slice(0, cut).trimEnd()}`);
|
|
737
|
+
rest = rest.slice(cut).trimStart();
|
|
738
|
+
prefix = continuation;
|
|
975
739
|
}
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
...this.readableValueLines(item, `${indent} `, depth + 1),
|
|
979
|
-
];
|
|
740
|
+
if (rest) wrapped.push(`${prefix}${rest}`);
|
|
741
|
+
return wrapped;
|
|
980
742
|
});
|
|
981
743
|
}
|
|
982
744
|
|
|
983
|
-
private
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
const subagent = commandNumber
|
|
989
|
-
? String(Number.parseInt(commandNumber, 10))
|
|
990
|
-
: turn.commandId;
|
|
991
|
-
const stage =
|
|
992
|
-
turn.stage && turn.stage !== "subagent" && turn.stage !== "command"
|
|
993
|
-
? ` (${turn.stage})`
|
|
994
|
-
: "";
|
|
995
|
-
lines.push(` Subagent ${subagent}${stage}`, DETAIL_SECTION_BREAK);
|
|
996
|
-
if (turn.userText)
|
|
997
|
-
lines.push(
|
|
998
|
-
...this.detailSection("User", [this.readablePromptText(turn.userText)]),
|
|
999
|
-
);
|
|
1000
|
-
if (turn.thinking)
|
|
1001
|
-
lines.push(...this.detailSection("Thinking", [turn.thinking]));
|
|
1002
|
-
if (turn.assistantText)
|
|
1003
|
-
lines.push(...this.detailSection("Assistant", [turn.assistantText]));
|
|
1004
|
-
for (const [index, tool] of turn.toolCalls.entries()) {
|
|
1005
|
-
const content = [
|
|
1006
|
-
...(tool.arguments === undefined
|
|
1007
|
-
? []
|
|
1008
|
-
: ["Input", ...this.readableValueLines(tool.arguments, " ")]),
|
|
1009
|
-
...(tool.result === undefined
|
|
1010
|
-
? []
|
|
1011
|
-
: ["Result", ...this.readableValueLines(tool.result, " ")]),
|
|
1012
|
-
`call id: ${tool.id}`,
|
|
1013
|
-
];
|
|
1014
|
-
lines.push(
|
|
1015
|
-
...this.detailSection(
|
|
1016
|
-
`Tool ${index + 1} (${tool.name}${tool.resultError ? ", Error" : ""})`,
|
|
1017
|
-
content,
|
|
1018
|
-
),
|
|
1019
|
-
);
|
|
745
|
+
private styleDocumentLine(line: string): string {
|
|
746
|
+
const indent = line.match(/^ */u)?.[0] ?? "";
|
|
747
|
+
const content = line.slice(indent.length);
|
|
748
|
+
if (/^- #\d+$/u.test(content) || content.endsWith(":")) {
|
|
749
|
+
return `${indent}${this.theme.fg("accent", content)}`;
|
|
1020
750
|
}
|
|
1021
|
-
const
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
...(turn.stopReason ? [`stop reason: ${turn.stopReason}`] : []),
|
|
1025
|
-
...(turn.error ? [`error: ${turn.error}`] : []),
|
|
1026
|
-
...(turn.usage === undefined
|
|
1027
|
-
? []
|
|
1028
|
-
: ["Usage", ...this.readableValueLines(turn.usage, " ")]),
|
|
1029
|
-
...(turn.unmatchedToolResults > 0
|
|
1030
|
-
? [`unmatched tool results: ${turn.unmatchedToolResults}`]
|
|
1031
|
-
: []),
|
|
1032
|
-
];
|
|
1033
|
-
if (execution.length > 0)
|
|
1034
|
-
lines.push(...this.detailSection("Execution", execution));
|
|
1035
|
-
if (turn.diagnostics.length > 0)
|
|
1036
|
-
lines.push(...this.detailSection("Diagnostics", turn.diagnostics));
|
|
1037
|
-
const provenance = [
|
|
1038
|
-
"Session",
|
|
1039
|
-
` ${turn.sessionFile}`,
|
|
1040
|
-
...(turn.sessionTruncated ? [" turn list truncated"] : []),
|
|
1041
|
-
...(turn.promptFile
|
|
1042
|
-
? [
|
|
1043
|
-
"Prompt",
|
|
1044
|
-
` ${turn.promptFile}${turn.promptBytes ? ` (${turn.promptBytes} bytes)` : ""}`,
|
|
1045
|
-
]
|
|
1046
|
-
: []),
|
|
1047
|
-
];
|
|
1048
|
-
lines.push(...this.detailSection("Provenance", provenance));
|
|
1049
|
-
return lines.length > 0 ? lines : [" No persisted turn evidence"];
|
|
1050
|
-
}
|
|
1051
|
-
|
|
1052
|
-
private readablePromptText(text: string): string {
|
|
1053
|
-
const wrapped = /^\s*<file\b[^>]*>\s*([\s\S]*?)\s*<\/file>\s*$/i.exec(text);
|
|
1054
|
-
return wrapped?.[1]?.trim() || text;
|
|
1055
|
-
}
|
|
1056
|
-
|
|
1057
|
-
private allTurnItems(): Inspector.ActorInspectorTurnItem[] {
|
|
1058
|
-
if (!this.selectedRun) return [];
|
|
1059
|
-
if (this.turnCache?.run === this.selectedRun) return this.turnCache.turns;
|
|
1060
|
-
const turns = this.readTurns(path.join(this.stateRoot, this.selectedRun));
|
|
1061
|
-
this.turnCache = { run: this.selectedRun, turns };
|
|
1062
|
-
return turns;
|
|
1063
|
-
}
|
|
1064
|
-
|
|
1065
|
-
private turnItems(): Inspector.ActorInspectorTurnItem[] {
|
|
1066
|
-
const selectedSubagent = this.subagents()[this.subagentIndex];
|
|
1067
|
-
return this.allTurnItems()
|
|
1068
|
-
.filter(
|
|
1069
|
-
(turn) =>
|
|
1070
|
-
!selectedSubagent ||
|
|
1071
|
-
selectedSubagent === "all" ||
|
|
1072
|
-
turn.commandId === selectedSubagent,
|
|
1073
|
-
)
|
|
1074
|
-
.reverse();
|
|
1075
|
-
}
|
|
1076
|
-
|
|
1077
|
-
private turnRows(): string[] {
|
|
1078
|
-
return this.turnItems().map((turn) => {
|
|
1079
|
-
const commandNumber = /^command-(\d+)$/.exec(turn.commandId)?.[1];
|
|
1080
|
-
const subagent = commandNumber
|
|
1081
|
-
? `Subagent ${Number.parseInt(commandNumber, 10)}`
|
|
1082
|
-
: `Subagent ${turn.commandId}`;
|
|
1083
|
-
const stage =
|
|
1084
|
-
turn.stage && turn.stage !== "subagent" && turn.stage !== "command"
|
|
1085
|
-
? ` (${turn.stage})`
|
|
1086
|
-
: "";
|
|
1087
|
-
const toolNames = [...new Set(turn.toolCalls.map((tool) => tool.name))];
|
|
1088
|
-
const action = turn.toolCalls.length === 0
|
|
1089
|
-
? ""
|
|
1090
|
-
: turn.toolCalls.length === 1
|
|
1091
|
-
? ` (${toolNames[0]})`
|
|
1092
|
-
: toolNames.length === 1
|
|
1093
|
-
? ` (${toolNames[0]}, ${turn.toolCalls.length} calls)`
|
|
1094
|
-
: turn.toolCalls.length <= 2
|
|
1095
|
-
? ` (${toolNames.join(", ")})`
|
|
1096
|
-
: ` (${turn.toolCalls.length} tools)`;
|
|
1097
|
-
const failed = turn.error || turn.toolCalls.some((tool) => tool.resultError)
|
|
1098
|
-
? " (error)"
|
|
1099
|
-
: "";
|
|
1100
|
-
const preview = this.readablePromptText(
|
|
1101
|
-
turn.assistantText ?? turn.userText ?? "(no text)",
|
|
1102
|
-
).replaceAll(/\s+/g, " ").trim();
|
|
1103
|
-
return `#${turn.index} ${subagent}${stage} ${turn.model ?? "unknown model"}${action}${failed} ${preview}`;
|
|
1104
|
-
});
|
|
1105
|
-
}
|
|
1106
|
-
|
|
1107
|
-
private subagents(): string[] {
|
|
1108
|
-
if (!this.selectedRun || this.tab !== "turns") return ["all"];
|
|
1109
|
-
const ids = this.allTurnItems().map((turn) => turn.commandId);
|
|
1110
|
-
return ["all", ...new Set(ids)];
|
|
1111
|
-
}
|
|
1112
|
-
|
|
1113
|
-
private valueOptions(): string[] {
|
|
1114
|
-
if (this.tab === "turns") return this.subagents();
|
|
1115
|
-
if (this.filterControlIndex === 0)
|
|
1116
|
-
return ["all", "room", "direct", "broadcast"];
|
|
1117
|
-
if (this.filterControlIndex === 1) return ["all", "unread"];
|
|
1118
|
-
return this.communicationFromOptions();
|
|
1119
|
-
}
|
|
1120
|
-
|
|
1121
|
-
private filterOptions(): string[] {
|
|
1122
|
-
return this.tab === "communications"
|
|
1123
|
-
? ["Channel", "State", "From"]
|
|
1124
|
-
: ["Subagent"];
|
|
1125
|
-
}
|
|
1126
|
-
|
|
1127
|
-
private menuOptions(): string[] {
|
|
1128
|
-
if (this.menuLevel === "run") return this.runs().map((run) => run.run);
|
|
1129
|
-
if (this.menuLevel === "filter") return this.filterOptions();
|
|
1130
|
-
return this.valueOptions();
|
|
1131
|
-
}
|
|
1132
|
-
|
|
1133
|
-
private backMenu(): void {
|
|
1134
|
-
if (this.menuLevel === "value") {
|
|
1135
|
-
this.menuLevel = "filter";
|
|
1136
|
-
this.selectorIndex = this.filterControlIndex;
|
|
1137
|
-
return;
|
|
751
|
+
const labeled = /^([^:]+:)(.*)$/u.exec(content);
|
|
752
|
+
if (labeled) {
|
|
753
|
+
return `${indent}${this.theme.fg("muted", labeled[1])}${this.theme.fg("text", labeled[2])}`;
|
|
1138
754
|
}
|
|
1139
|
-
this.
|
|
755
|
+
return `${indent}${this.theme.fg(content === "none" || content === "[]" ? "muted" : "text", content)}`;
|
|
1140
756
|
}
|
|
1141
757
|
|
|
1142
|
-
private
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
this.focus = "select";
|
|
758
|
+
private visiblePrefix(value: string, width: number): string {
|
|
759
|
+
let consumed = 0;
|
|
760
|
+
let result = "";
|
|
761
|
+
for (const character of value) {
|
|
762
|
+
const characterWidth = visibleWidth(character);
|
|
763
|
+
if (consumed + characterWidth > width) break;
|
|
764
|
+
result += character;
|
|
765
|
+
consumed += characterWidth;
|
|
766
|
+
}
|
|
767
|
+
return result;
|
|
1153
768
|
}
|
|
1154
769
|
|
|
1155
|
-
private
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
return;
|
|
770
|
+
private renderKeyHints(): string {
|
|
771
|
+
const hint = (keys: string, description: string) =>
|
|
772
|
+
`${this.theme.fg("accent", keys)}${this.theme.fg("borderAccent", ` ${description}`)}`;
|
|
773
|
+
const divider = this.theme.fg("borderAccent", " ─ ");
|
|
774
|
+
const hints = (...items: Array<[string, string]>) =>
|
|
775
|
+
items.map(([keys, description]) => hint(keys, description)).join(divider);
|
|
776
|
+
if (this.killConfirmation) {
|
|
777
|
+
return hints(["←→/tab", "choose"], ["enter/y", "confirm"], ["esc/n", "cancel"]);
|
|
1164
778
|
}
|
|
1165
|
-
if (this.
|
|
1166
|
-
|
|
1167
|
-
const options = this.valueOptions();
|
|
1168
|
-
const current = this.currentFilterValue();
|
|
1169
|
-
this.selectorIndex = Math.max(0, options.indexOf(current));
|
|
1170
|
-
this.menuLevel = "value";
|
|
1171
|
-
return;
|
|
779
|
+
if (this.focus === "select") {
|
|
780
|
+
return hints(["↑↓", "option"], ["enter/→", "apply"], ["←/esc", "back"]);
|
|
1172
781
|
}
|
|
1173
|
-
if (this.
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
this.
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
return this.
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
private currentFilterValue(): string {
|
|
1193
|
-
return this.filterValue(this.filterControlIndex);
|
|
1194
|
-
}
|
|
1195
|
-
|
|
1196
|
-
private renderMenuBox(
|
|
1197
|
-
options: string[],
|
|
1198
|
-
focusedIndex: number,
|
|
1199
|
-
current?: string,
|
|
1200
|
-
parentIndex?: number,
|
|
1201
|
-
omitLeftBorder = false,
|
|
1202
|
-
omitRightBorder = false,
|
|
1203
|
-
maxVisibleOptions = options.length,
|
|
1204
|
-
): string[] {
|
|
1205
|
-
const visibleLimit = Math.max(1, Math.min(maxVisibleOptions, options.length));
|
|
1206
|
-
const anchorIndex = Math.max(
|
|
1207
|
-
0,
|
|
1208
|
-
Math.min(
|
|
1209
|
-
options.length - 1,
|
|
1210
|
-
focusedIndex >= 0 ? focusedIndex : (parentIndex ?? 0),
|
|
1211
|
-
),
|
|
1212
|
-
);
|
|
1213
|
-
const maxStart = Math.max(0, options.length - visibleLimit);
|
|
1214
|
-
const start = Math.max(
|
|
1215
|
-
0,
|
|
1216
|
-
Math.min(anchorIndex - Math.floor(visibleLimit / 2), maxStart),
|
|
1217
|
-
);
|
|
1218
|
-
const visibleOptions = options.slice(start, start + visibleLimit);
|
|
1219
|
-
const hiddenAbove = start > 0;
|
|
1220
|
-
const hiddenBelow = start + visibleOptions.length < options.length;
|
|
1221
|
-
const contentWidth = Math.max(
|
|
1222
|
-
4,
|
|
1223
|
-
...options.map((option) => visibleWidth(option) + 4),
|
|
1224
|
-
);
|
|
1225
|
-
const border = (left: string, right: string, marker = "") =>
|
|
1226
|
-
this.theme.fg(
|
|
1227
|
-
"borderAccent",
|
|
1228
|
-
`${omitLeftBorder ? "" : left}${marker}${"─".repeat(Math.max(0, contentWidth - visibleWidth(marker)))}${omitRightBorder ? "" : right}`,
|
|
1229
|
-
);
|
|
1230
|
-
return [
|
|
1231
|
-
border("╭", "╮", hiddenAbove ? "↑" : ""),
|
|
1232
|
-
...visibleOptions.map((option, localIndex) => {
|
|
1233
|
-
const index = start + localIndex;
|
|
1234
|
-
const focused = index === focusedIndex;
|
|
1235
|
-
const row = this.fit(`${focused ? " ▶ " : " "}${option}`, contentWidth);
|
|
1236
|
-
const colored = focused || option === current || index === parentIndex
|
|
1237
|
-
? this.theme.fg("accent", row)
|
|
1238
|
-
: row;
|
|
1239
|
-
const styled = focused || index === parentIndex
|
|
1240
|
-
? this.theme.bg("selectedBg", colored)
|
|
1241
|
-
: colored;
|
|
1242
|
-
return `${omitLeftBorder ? "" : this.theme.fg("borderAccent", "│")}${styled}${omitRightBorder ? "" : this.theme.fg("borderAccent", "│")}`;
|
|
1243
|
-
}),
|
|
1244
|
-
border("╰", "╯", hiddenBelow ? "↓" : ""),
|
|
1245
|
-
];
|
|
1246
|
-
}
|
|
1247
|
-
|
|
1248
|
-
private renderFilterMenus(_width: number): string[] {
|
|
1249
|
-
const filters = this.filterOptions().map(
|
|
1250
|
-
(filter, index) => `${filter}: ${this.filterValue(index)}`,
|
|
1251
|
-
);
|
|
1252
|
-
const maxVisibleOptions = Math.max(1, this.contentViewportRows() - 1);
|
|
1253
|
-
const parentFocus = this.menuLevel === "filter" ? this.selectorIndex : -1;
|
|
1254
|
-
const parent = this.renderMenuBox(
|
|
1255
|
-
filters,
|
|
1256
|
-
parentFocus,
|
|
1257
|
-
undefined,
|
|
1258
|
-
this.menuLevel === "value" ? this.filterControlIndex : undefined,
|
|
1259
|
-
false,
|
|
1260
|
-
false,
|
|
1261
|
-
maxVisibleOptions,
|
|
1262
|
-
);
|
|
1263
|
-
if (this.menuLevel !== "value") return parent;
|
|
1264
|
-
const parentShared = this.renderMenuBox(
|
|
1265
|
-
filters,
|
|
1266
|
-
parentFocus,
|
|
1267
|
-
undefined,
|
|
1268
|
-
this.filterControlIndex,
|
|
1269
|
-
false,
|
|
1270
|
-
true,
|
|
1271
|
-
maxVisibleOptions,
|
|
1272
|
-
);
|
|
1273
|
-
const child = this.renderMenuBox(
|
|
1274
|
-
this.valueOptions(),
|
|
1275
|
-
this.selectorIndex,
|
|
1276
|
-
this.currentFilterValue(),
|
|
1277
|
-
undefined,
|
|
1278
|
-
false,
|
|
1279
|
-
false,
|
|
1280
|
-
maxVisibleOptions,
|
|
1281
|
-
);
|
|
1282
|
-
return Array.from({ length: Math.max(parent.length, child.length) }, (_, index) =>
|
|
1283
|
-
child[index]
|
|
1284
|
-
? `${parentShared[index] ?? " ".repeat(visibleWidth(parentShared[0]))}${child[index]}`
|
|
1285
|
-
: (parent[index] ?? ""),
|
|
1286
|
-
);
|
|
1287
|
-
}
|
|
1288
|
-
|
|
1289
|
-
private openDetail(): void {
|
|
1290
|
-
if (this.tab === "communications") {
|
|
1291
|
-
const preview = this.communicationPreviews()[this.rowIndex];
|
|
1292
|
-
this.detailCommunication = preview;
|
|
1293
|
-
if (preview) this.readKeys.add(Inspector.inspectorPreviewReadKey(preview));
|
|
1294
|
-
} else this.detailTurn = this.turnItems()[this.rowIndex];
|
|
1295
|
-
this.detailScroll = 0;
|
|
1296
|
-
this.detailOpen = Boolean(this.detailCommunication || this.detailTurn);
|
|
1297
|
-
if (this.detailOpen) this.focus = "detail";
|
|
1298
|
-
}
|
|
1299
|
-
|
|
1300
|
-
private backDetail(): void {
|
|
1301
|
-
this.closeDetail();
|
|
782
|
+
if (this.focus === "detail") {
|
|
783
|
+
return hints(["↑↓/pgup/pgdn", "scroll"], ["←/esc", "back"]);
|
|
784
|
+
}
|
|
785
|
+
if (this.focus === "document") {
|
|
786
|
+
return hints(["↑↓/pgup/pgdn", "scroll"], ["↑ at top", "tabs"], ["←/esc", "tabs"]);
|
|
787
|
+
}
|
|
788
|
+
if (this.focus === "list") {
|
|
789
|
+
return hints(["↑↓/pgup/pgdn", "row"], ["→/enter", "open"], ["←", "tabs"], ["esc", "close"]);
|
|
790
|
+
}
|
|
791
|
+
if (this.focus === "runs") {
|
|
792
|
+
const items: Array<[string, string]> = [["←→", "run"], ["↓", "tabs"], ["enter", "list"]];
|
|
793
|
+
const run = this.runs()[this.runIndex];
|
|
794
|
+
if (this.killRun && run?.status === "running" && run.runInstanceId) items.push(["k", "kill"]);
|
|
795
|
+
items.push(["esc", "close"]);
|
|
796
|
+
return hints(...items);
|
|
797
|
+
}
|
|
798
|
+
return this.tab === "trace"
|
|
799
|
+
? hints(["←→", "tabs"], ["↑↓", "focus"], ["enter/f", "source"], ["esc", "close"])
|
|
800
|
+
: hints(["←→", "tabs"], ["↑↓", "focus"], ["enter", "open"], ["esc", "close"]);
|
|
1302
801
|
}
|
|
1303
802
|
|
|
1304
|
-
private
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
this.focus = "list";
|
|
803
|
+
private statusColor(status: string): "error" | "warning" | "success" | "muted" {
|
|
804
|
+
if (status === "failed") return "error";
|
|
805
|
+
if (status === "running") return "warning";
|
|
806
|
+
if (status === "done" || status === "terminal") return "success";
|
|
807
|
+
return "muted";
|
|
1310
808
|
}
|
|
1311
809
|
|
|
1312
810
|
private footerBorder(hints: string, width: number): string {
|
|
@@ -1318,81 +816,39 @@ export class ActorInspectorOverlay {
|
|
|
1318
816
|
"",
|
|
1319
817
|
);
|
|
1320
818
|
const fill = "─".repeat(
|
|
1321
|
-
Math.max(
|
|
1322
|
-
0,
|
|
1323
|
-
width - visibleWidth(prefix) - visibleWidth(suffix) - visibleWidth(content),
|
|
1324
|
-
),
|
|
819
|
+
Math.max(0, width - visibleWidth(prefix) - visibleWidth(suffix) - visibleWidth(content)),
|
|
1325
820
|
);
|
|
1326
821
|
return `${this.theme.fg("borderAccent", `╰${prefix}`)}${content}${this.theme.fg("borderAccent", `${suffix}${fill}╯`)}`;
|
|
1327
822
|
}
|
|
1328
823
|
|
|
1329
|
-
private border(left: string, title: string, right: string, width: number): string {
|
|
1330
|
-
const titleText = truncateToWidth(title, width, "");
|
|
1331
|
-
const fill = "─".repeat(Math.max(0, width - visibleWidth(titleText)));
|
|
1332
|
-
return this.theme.fg("borderAccent", `${left}${titleText}${fill}${right}`);
|
|
1333
|
-
}
|
|
1334
|
-
|
|
1335
824
|
private stripeBackground(content: string, index: number): string {
|
|
1336
|
-
|
|
1337
|
-
const segments = content.split("…");
|
|
1338
|
-
return segments
|
|
1339
|
-
.map((segment, segmentIndex) =>
|
|
1340
|
-
`${this.theme.bg("customMessageBg", segment)}${segmentIndex < segments.length - 1 ? this.theme.bg("customMessageBg", "…") : ""}`,
|
|
1341
|
-
)
|
|
1342
|
-
.join("");
|
|
825
|
+
return index % 2 === 0 ? content : this.theme.bg("customMessageBg", content);
|
|
1343
826
|
}
|
|
1344
827
|
|
|
1345
828
|
private stripedRow(content: string, width: number, index: number): string {
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
width,
|
|
1349
|
-
true,
|
|
1350
|
-
);
|
|
829
|
+
const fitted = this.fit(content, width);
|
|
830
|
+
return `${this.theme.fg("borderAccent", "│")}${this.stripeBackground(fitted, index)}${this.theme.fg("borderAccent", "│")}`;
|
|
1351
831
|
}
|
|
1352
832
|
|
|
1353
|
-
private row(content: string, width: number
|
|
1354
|
-
|
|
1355
|
-
return `${this.theme.fg("borderAccent", "│")}${body}${this.theme.fg("borderAccent", "│")}`;
|
|
833
|
+
private row(content: string, width: number): string {
|
|
834
|
+
return `${this.theme.fg("borderAccent", "│")}${this.fit(content, width)}${this.theme.fg("borderAccent", "│")}`;
|
|
1356
835
|
}
|
|
1357
836
|
|
|
1358
|
-
private
|
|
1359
|
-
const
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
if (consumed + characterWidth > width) break;
|
|
1365
|
-
result += character;
|
|
1366
|
-
consumed += characterWidth;
|
|
1367
|
-
}
|
|
1368
|
-
return result;
|
|
837
|
+
private border(left: string, label: string, right: string, width: number): string {
|
|
838
|
+
const bounded = truncateToWidth(label, width, "");
|
|
839
|
+
return this.theme.fg(
|
|
840
|
+
"borderAccent",
|
|
841
|
+
`${left}${bounded}${"─".repeat(Math.max(0, width - visibleWidth(bounded)))}${right}`,
|
|
842
|
+
);
|
|
1369
843
|
}
|
|
1370
844
|
|
|
1371
|
-
private
|
|
1372
|
-
const
|
|
1373
|
-
|
|
1374
|
-
let index = 0;
|
|
1375
|
-
for (const character of plain) {
|
|
1376
|
-
if (consumed >= width) break;
|
|
1377
|
-
consumed += visibleWidth(character);
|
|
1378
|
-
index += character.length;
|
|
1379
|
-
}
|
|
1380
|
-
return plain.slice(index);
|
|
845
|
+
private fit(content: string, width: number): string {
|
|
846
|
+
const bounded = truncateToWidth(content, width, "");
|
|
847
|
+
return `${bounded}${" ".repeat(Math.max(0, width - visibleWidth(bounded)))}`;
|
|
1381
848
|
}
|
|
1382
849
|
|
|
1383
850
|
private center(content: string, width: number): string {
|
|
1384
|
-
const
|
|
1385
|
-
|
|
1386
|
-
return `${" ".repeat(leading)}${content}`;
|
|
1387
|
-
}
|
|
1388
|
-
|
|
1389
|
-
private fit(content: string, width: number): string {
|
|
1390
|
-
// A Component render entry must remain exactly one terminal row. Persisted
|
|
1391
|
-
// prompts, messages, and tool results may contain line breaks or tabs;
|
|
1392
|
-
// passing those through lets them escape the overlay compositor and corrupt
|
|
1393
|
-
// the underlying TUI during tab changes.
|
|
1394
|
-
const singleLine = content.replace(/[\r\n\t]/g, " ");
|
|
1395
|
-
const bounded = truncateToWidth(singleLine, width, "…");
|
|
1396
|
-
return `${bounded}${" ".repeat(Math.max(0, width - visibleWidth(bounded)))}`;
|
|
851
|
+
const padding = Math.max(0, Math.floor((width - visibleWidth(content)) / 2));
|
|
852
|
+
return `${" ".repeat(padding)}${content}`;
|
|
1397
853
|
}
|
|
1398
854
|
}
|