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