@llblab/pi-actors 0.42.3 → 0.43.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +127 -175
- package/BACKLOG.md +189 -1
- package/CHANGELOG.md +183 -292
- package/README.md +115 -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/index.js +1 -1
- package/dist/lib/async-runs.d.ts +15 -38
- package/dist/lib/async-runs.js +173 -111
- package/dist/lib/automatic-review-runtime.d.ts +1 -1
- package/dist/lib/automatic-review-runtime.js +5 -5
- package/dist/lib/command-templates.d.ts +2 -0
- package/dist/lib/command-templates.js +38 -4
- package/dist/lib/control-projection.d.ts +20 -0
- package/dist/lib/control-projection.js +66 -0
- package/dist/lib/control.d.ts +15 -0
- package/dist/lib/control.js +97 -0
- package/dist/lib/draft-sleep.js +3 -3
- package/dist/lib/execution-sessions.d.ts +17 -0
- package/dist/lib/execution-sessions.js +85 -0
- package/dist/lib/file-state.d.ts +2 -0
- package/dist/lib/file-state.js +114 -46
- 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 +54 -70
- package/dist/lib/inspector-overlay.js +576 -910
- package/dist/lib/inspector.d.ts +3 -71
- package/dist/lib/inspector.js +19 -665
- package/dist/lib/limits.d.ts +7 -3
- package/dist/lib/limits.js +7 -3
- 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 +3 -3
- package/dist/lib/recipe-control.d.ts +7 -0
- package/dist/lib/recipe-control.js +43 -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-control.d.ts +1 -1
- package/dist/lib/review-control.js +4 -5
- 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 +28 -0
- package/dist/lib/runs-control-delivery.js +150 -0
- package/dist/lib/runs-controls.d.ts +37 -0
- package/dist/lib/runs-controls.js +146 -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 +102 -0
- package/dist/lib/runtime-identity.d.ts +7 -0
- package/dist/lib/runtime-identity.js +35 -0
- package/dist/lib/runtime-notifier.d.ts +1 -1
- package/dist/lib/runtime-notifier.js +1 -1
- package/dist/lib/runtime-triage.d.ts +29 -0
- package/dist/lib/runtime-triage.js +76 -0
- package/dist/lib/tool-review-scheduler.js +7 -7
- package/dist/lib/tools-inspect.d.ts +3 -3
- package/dist/lib/tools-inspect.js +241 -707
- package/dist/lib/tools-local.js +2 -10
- package/dist/lib/tools-message.d.ts +6 -7
- package/dist/lib/tools-message.js +95 -396
- package/dist/lib/tools-response.d.ts +1 -5
- package/dist/lib/tools-response.js +5 -48
- package/dist/lib/tools-spawn.js +16 -28
- package/dist/lib/tools.d.ts +1 -1
- package/dist/lib/tools.js +2 -3
- package/dist/lib/trace-projection.d.ts +22 -0
- package/dist/lib/trace-projection.js +185 -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 +196 -69
- package/dist/scripts/music-player.mjs +162 -159
- package/dist/scripts/recipe-utils.mjs +6 -96
- package/dist/scripts/release-gates.mjs +91 -0
- package/dist/scripts/validate-recipe.mjs +8 -57
- package/dist/skills/actors/SKILL.md +57 -265
- package/dist/skills/swarm/SKILL.md +10 -34
- package/docs/0.43-baseline.md +39 -0
- package/docs/README.md +4 -6
- package/docs/actor-inspector.md +26 -64
- package/docs/async-runs.md +81 -328
- package/docs/command-templates.md +8 -118
- package/docs/recipe-library.md +55 -182
- package/docs/releasing.md +28 -0
- 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/index.ts +1 -1
- package/lib/async-runs.ts +218 -204
- package/lib/automatic-review-runtime.ts +7 -7
- package/lib/command-templates.ts +44 -4
- package/lib/control-projection.ts +105 -0
- package/lib/control.ts +117 -0
- package/lib/draft-sleep.ts +3 -3
- package/lib/execution-sessions.ts +111 -0
- package/lib/file-state.ts +84 -64
- package/lib/inspector-actions.ts +2 -2
- package/lib/inspector-command.ts +3 -3
- package/lib/inspector-overlay.ts +617 -1126
- package/lib/inspector.ts +46 -979
- package/lib/limits.ts +7 -3
- package/lib/observability.ts +60 -101
- package/lib/prompts.ts +3 -3
- package/lib/recipe-control.ts +52 -0
- package/lib/recipes-discovery.ts +2 -0
- package/lib/recipes-references.ts +9 -45
- package/lib/review-control.ts +4 -5
- package/lib/review-projection.ts +1 -5
- package/lib/run-ui-runtime.ts +2 -2
- package/lib/runs-control-delivery.ts +209 -0
- package/lib/runs-controls.ts +213 -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 +136 -0
- package/lib/runtime-identity.ts +39 -0
- package/lib/runtime-notifier.ts +1 -1
- package/lib/runtime-triage.ts +120 -0
- package/lib/tool-review-scheduler.ts +7 -7
- package/lib/tools-inspect.ts +283 -900
- package/lib/tools-local.ts +2 -12
- package/lib/tools-message.ts +112 -520
- package/lib/tools-response.ts +5 -64
- package/lib/tools-spawn.ts +16 -32
- package/lib/tools.ts +5 -6
- package/lib/trace-projection.ts +244 -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 +196 -69
- package/scripts/music-player.mjs +162 -159
- package/scripts/recipe-utils.mjs +6 -96
- package/scripts/release-gates.mjs +91 -0
- package/scripts/validate-recipe.mjs +8 -57
- package/skills/actors/SKILL.md +57 -265
- package/skills/swarm/SKILL.md +10 -34
- 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/docs/actors-deep-reference.md +0 -66
- package/docs/component-recipes.md +0 -148
- package/docs/task-first-recipes.md +0 -263
- 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,134 @@
|
|
|
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 ControlProjection from "./control-projection.js";
|
|
11
|
+
import * as RunsControls from "./runs-controls.js";
|
|
12
|
+
import * as RunControlDelivery from "./runs-control-delivery.js";
|
|
13
|
+
import * as TraceProjection from "./trace-projection.js";
|
|
14
|
+
const TABS = ["recipe", "trace", "control"];
|
|
15
|
+
const TRACE_SOURCES = [
|
|
16
|
+
"all",
|
|
17
|
+
"lifecycle",
|
|
18
|
+
"control",
|
|
19
|
+
"process",
|
|
20
|
+
"agent",
|
|
21
|
+
"artifact",
|
|
22
|
+
"runtime",
|
|
14
23
|
];
|
|
15
|
-
|
|
24
|
+
function newestTraceItems(items) {
|
|
25
|
+
return [...items].sort((left, right) => right.ts.localeCompare(left.ts) || right.id.localeCompare(left.id));
|
|
26
|
+
}
|
|
16
27
|
export class ActorInspectorOverlay {
|
|
17
28
|
done;
|
|
18
29
|
killRun;
|
|
19
30
|
ownerId;
|
|
20
|
-
|
|
31
|
+
readRuns;
|
|
32
|
+
readTrace;
|
|
21
33
|
stateRoot;
|
|
22
34
|
theme;
|
|
23
35
|
tui;
|
|
24
36
|
refreshTimer;
|
|
25
|
-
runIndex = 0;
|
|
26
|
-
communicationChannel = "all";
|
|
27
|
-
communicationFrom = "all";
|
|
28
|
-
communicationUnread = false;
|
|
29
37
|
contentStripeIndices = [];
|
|
30
|
-
|
|
31
|
-
detailOpen = false;
|
|
38
|
+
documentScroll = 0;
|
|
32
39
|
detailScroll = 0;
|
|
33
|
-
detailTurn;
|
|
34
40
|
feedback;
|
|
41
|
+
focus = "runs";
|
|
35
42
|
killConfirmation;
|
|
36
43
|
killDialogChoice = "cancel";
|
|
37
|
-
readKeys = new Set();
|
|
38
|
-
focus = "tabs";
|
|
39
|
-
filterControlIndex = 0;
|
|
40
|
-
menuLevel = "filter";
|
|
41
|
-
selectorIndex = 0;
|
|
42
|
-
recipeScroll = 0;
|
|
43
44
|
rowIndex = 0;
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
45
|
+
runCache;
|
|
46
|
+
selectedTraceId;
|
|
47
|
+
runIndex = 0;
|
|
48
|
+
selectorIndex = 0;
|
|
49
|
+
selectorMode;
|
|
50
|
+
tabIndex = 0;
|
|
51
|
+
traceCache;
|
|
52
|
+
traceDetail;
|
|
53
|
+
traceSourceIndex = 0;
|
|
48
54
|
constructor(options) {
|
|
49
55
|
this.done = options.done;
|
|
50
56
|
this.killRun = options.killRun;
|
|
51
57
|
this.ownerId = options.ownerId;
|
|
52
|
-
this.
|
|
58
|
+
this.readRuns = options.readRuns ?? ActorInspector.readActorInspectorRuns;
|
|
59
|
+
this.readTrace = options.readTrace ?? TraceProjection.projectRunTrace;
|
|
53
60
|
this.stateRoot = options.stateRoot;
|
|
54
61
|
this.theme = options.theme;
|
|
55
62
|
this.tui = options.tui;
|
|
56
63
|
this.refreshTimer = setInterval(() => {
|
|
57
|
-
|
|
58
|
-
this.turnCache = undefined;
|
|
64
|
+
this.invalidate();
|
|
59
65
|
this.tui.requestRender();
|
|
60
|
-
},
|
|
66
|
+
}, 1_000);
|
|
61
67
|
this.refreshTimer.unref?.();
|
|
62
68
|
}
|
|
69
|
+
dispose() {
|
|
70
|
+
clearInterval(this.refreshTimer);
|
|
71
|
+
}
|
|
72
|
+
invalidate() {
|
|
73
|
+
this.runCache = undefined;
|
|
74
|
+
this.traceCache = undefined;
|
|
75
|
+
}
|
|
63
76
|
handleInput(data) {
|
|
64
77
|
const runs = this.runs();
|
|
65
|
-
this.
|
|
78
|
+
this.runIndex = Math.min(this.runIndex, Math.max(0, runs.length - 1));
|
|
66
79
|
if (matchesKey(data, "ctrl+c")) {
|
|
67
80
|
this.done();
|
|
68
81
|
return;
|
|
69
82
|
}
|
|
70
83
|
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
|
-
}
|
|
84
|
+
this.handleKillInput(data);
|
|
90
85
|
this.tui.requestRender();
|
|
91
86
|
return;
|
|
92
87
|
}
|
|
93
88
|
if (data.toLowerCase() === "k") {
|
|
94
|
-
this.requestKill(runs);
|
|
89
|
+
this.requestKill(runs[this.runIndex]);
|
|
95
90
|
this.tui.requestRender();
|
|
96
91
|
return;
|
|
97
92
|
}
|
|
98
93
|
if (matchesKey(data, "escape")) {
|
|
99
94
|
if (this.focus === "select") {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
95
|
+
this.selectorMode = undefined;
|
|
96
|
+
this.focus = this.tab === "trace" ? "tabs" : "runs";
|
|
97
|
+
}
|
|
98
|
+
else if (this.focus === "detail") {
|
|
99
|
+
this.selectedTraceId = this.traceDetail?.id;
|
|
100
|
+
this.traceDetail = undefined;
|
|
101
|
+
this.detailScroll = 0;
|
|
102
|
+
this.focus = "list";
|
|
106
103
|
}
|
|
107
|
-
else if (this.focus === "
|
|
108
|
-
this.
|
|
109
|
-
|
|
104
|
+
else if (this.focus === "document" || this.focus === "list") {
|
|
105
|
+
if (this.focus === "list")
|
|
106
|
+
this.selectedTraceId = undefined;
|
|
110
107
|
this.focus = "tabs";
|
|
111
|
-
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
112
110
|
this.done();
|
|
111
|
+
}
|
|
113
112
|
this.tui.requestRender();
|
|
114
113
|
return;
|
|
115
114
|
}
|
|
116
115
|
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]);
|
|
116
|
+
this.handleSelectorInput(data, runs);
|
|
127
117
|
this.tui.requestRender();
|
|
128
118
|
return;
|
|
129
119
|
}
|
|
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";
|
|
120
|
+
if (this.focus === "detail") {
|
|
121
|
+
this.handleScrollableInput(data, "detail");
|
|
145
122
|
this.tui.requestRender();
|
|
146
123
|
return;
|
|
147
124
|
}
|
|
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();
|
|
125
|
+
if (this.focus === "document") {
|
|
126
|
+
if (matchesKey(data, "left"))
|
|
127
|
+
this.focus = "tabs";
|
|
128
|
+
else if (matchesKey(data, "up") && this.documentScroll === 0)
|
|
129
|
+
this.focus = "tabs";
|
|
130
|
+
else
|
|
131
|
+
this.handleScrollableInput(data, "document");
|
|
159
132
|
this.tui.requestRender();
|
|
160
133
|
return;
|
|
161
134
|
}
|
|
@@ -166,163 +139,197 @@ export class ActorInspectorOverlay {
|
|
|
166
139
|
this.cycleRun(runs, 1);
|
|
167
140
|
else if (matchesKey(data, "down"))
|
|
168
141
|
this.focus = "tabs";
|
|
169
|
-
else if (matchesKey(data, "return") && runs.length > 0)
|
|
170
|
-
this.
|
|
142
|
+
else if (matchesKey(data, "return") && runs.length > 0) {
|
|
143
|
+
this.selectorMode = "run";
|
|
144
|
+
this.selectorIndex = this.runIndex;
|
|
145
|
+
this.focus = "select";
|
|
146
|
+
}
|
|
171
147
|
}
|
|
172
148
|
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;
|
|
149
|
+
if (matchesKey(data, "left") || matchesKey(data, "right") || matchesKey(data, "tab")) {
|
|
150
|
+
const direction = matchesKey(data, "left") ? -1 : 1;
|
|
151
|
+
this.tabIndex = (this.tabIndex + direction + TABS.length) % TABS.length;
|
|
152
|
+
this.resetContentPosition();
|
|
180
153
|
}
|
|
181
|
-
else if (matchesKey(data, "up"))
|
|
154
|
+
else if (matchesKey(data, "up")) {
|
|
182
155
|
this.focus = "runs";
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
this.
|
|
186
|
-
|
|
187
|
-
this.focus
|
|
188
|
-
|
|
189
|
-
|
|
156
|
+
}
|
|
157
|
+
else if (matchesKey(data, "down")) {
|
|
158
|
+
const items = this.tab === "trace" ? this.traceItems(runs[this.runIndex]) : [];
|
|
159
|
+
this.focus = items.length > 0 ? "list" : "document";
|
|
160
|
+
if (this.focus === "list") {
|
|
161
|
+
this.rowIndex = 0;
|
|
162
|
+
this.selectedTraceId = items[0]?.id;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
else if (matchesKey(data, "return")) {
|
|
166
|
+
if (this.tab === "trace")
|
|
167
|
+
this.openSourceSelector(runs[this.runIndex]);
|
|
168
|
+
else
|
|
169
|
+
this.focus = "document";
|
|
170
|
+
}
|
|
190
171
|
}
|
|
191
172
|
else if (this.focus === "list") {
|
|
192
|
-
const
|
|
193
|
-
|
|
173
|
+
const items = this.traceItems(runs[this.runIndex]);
|
|
174
|
+
const count = items.length;
|
|
175
|
+
if (matchesKey(data, "left")) {
|
|
176
|
+
this.selectedTraceId = undefined;
|
|
194
177
|
this.focus = "tabs";
|
|
195
|
-
|
|
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());
|
|
178
|
+
}
|
|
199
179
|
else if (matchesKey(data, "up")) {
|
|
200
|
-
if (this.rowIndex === 0)
|
|
180
|
+
if (this.rowIndex === 0) {
|
|
181
|
+
this.selectedTraceId = undefined;
|
|
201
182
|
this.focus = "tabs";
|
|
183
|
+
}
|
|
202
184
|
else
|
|
203
185
|
this.rowIndex -= 1;
|
|
204
186
|
}
|
|
205
|
-
else if (matchesKey(data, "down"))
|
|
187
|
+
else if (matchesKey(data, "down")) {
|
|
206
188
|
this.rowIndex = Math.min(Math.max(0, count - 1), this.rowIndex + 1);
|
|
207
|
-
|
|
208
|
-
|
|
189
|
+
}
|
|
190
|
+
else if (matchesKey(data, "pageUp")) {
|
|
191
|
+
this.rowIndex = Math.max(0, this.rowIndex - this.contentViewportRows());
|
|
192
|
+
}
|
|
193
|
+
else if (matchesKey(data, "pageDown")) {
|
|
194
|
+
this.rowIndex = Math.min(Math.max(0, count - 1), this.rowIndex + this.contentViewportRows());
|
|
195
|
+
}
|
|
196
|
+
else if (matchesKey(data, "return") || matchesKey(data, "right")) {
|
|
197
|
+
const item = items[this.rowIndex];
|
|
198
|
+
if (item) {
|
|
199
|
+
this.traceDetail = item;
|
|
200
|
+
this.detailScroll = 0;
|
|
201
|
+
this.focus = "detail";
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
if (this.focus === "list")
|
|
205
|
+
this.selectedTraceId = items[this.rowIndex]?.id;
|
|
206
|
+
}
|
|
207
|
+
if (data.toLowerCase() === "f" && this.tab === "trace") {
|
|
208
|
+
this.cycleTraceSource(runs[this.runIndex]);
|
|
209
209
|
}
|
|
210
210
|
this.tui.requestRender();
|
|
211
211
|
}
|
|
212
212
|
render(width) {
|
|
213
|
-
this.ensureSelectedRun(this.runs());
|
|
214
213
|
const safeWidth = Math.max(24, width);
|
|
215
|
-
const innerWidth =
|
|
214
|
+
const innerWidth = safeWidth - 2;
|
|
215
|
+
const runs = this.runs();
|
|
216
|
+
this.runIndex = Math.min(this.runIndex, Math.max(0, runs.length - 1));
|
|
217
|
+
const run = runs[this.runIndex];
|
|
216
218
|
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));
|
|
219
|
+
return this.renderKillDialog(innerWidth);
|
|
220
|
+
const lines = [this.border("╭", " Actor Inspector ", "╮", innerWidth)];
|
|
221
|
+
lines.push(this.row(this.renderRunControl(run, runs.length - this.runIndex), innerWidth));
|
|
222
|
+
lines.push(this.row(this.renderTabs(run), innerWidth));
|
|
223
|
+
lines.push(this.border("├", "", "┤", innerWidth));
|
|
246
224
|
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));
|
|
225
|
+
let content = this.renderContent(run, innerWidth);
|
|
226
|
+
if (this.feedback) {
|
|
227
|
+
const color = this.feedback.ok
|
|
228
|
+
? "success"
|
|
229
|
+
: /^Kill (failed|rejected)/u.test(this.feedback.message)
|
|
230
|
+
? "error"
|
|
231
|
+
: "warning";
|
|
232
|
+
content = [this.theme.fg(color, ` ${this.feedback.message}`), ...content];
|
|
233
|
+
this.contentStripeIndices = [0, ...this.contentStripeIndices];
|
|
234
|
+
}
|
|
235
|
+
const viewportRows = this.contentViewportRows();
|
|
236
|
+
content = content.slice(0, viewportRows);
|
|
237
|
+
for (let index = 0; index < viewportRows; index += 1) {
|
|
238
|
+
lines.push(this.stripedRow(content[index] ?? "", innerWidth, this.contentStripeIndices[index] ?? index));
|
|
282
239
|
}
|
|
283
240
|
lines.push(this.footerBorder(this.renderKeyHints(), innerWidth));
|
|
284
241
|
return lines;
|
|
285
242
|
}
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
clearInterval(this.refreshTimer);
|
|
243
|
+
get tab() {
|
|
244
|
+
return TABS[this.tabIndex];
|
|
289
245
|
}
|
|
290
246
|
runs() {
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
this.selectedRun = runs[0]?.run;
|
|
247
|
+
if (this.runCache)
|
|
248
|
+
return this.runCache;
|
|
249
|
+
this.runCache = this.readRuns(this.stateRoot, this.ownerId);
|
|
250
|
+
return this.runCache;
|
|
296
251
|
}
|
|
297
252
|
contentViewportRows() {
|
|
298
|
-
const
|
|
253
|
+
const rows = this.tui.terminal?.rows ?? 30;
|
|
254
|
+
const overlayRows = Math.floor(rows * 0.94);
|
|
299
255
|
return Math.max(4, Math.min(24, overlayRows - 5));
|
|
300
256
|
}
|
|
301
|
-
|
|
302
|
-
this.
|
|
303
|
-
this.
|
|
304
|
-
this.runIndex = index;
|
|
305
|
-
this.recipeScroll = 0;
|
|
257
|
+
resetContentPosition() {
|
|
258
|
+
this.documentScroll = 0;
|
|
259
|
+
this.detailScroll = 0;
|
|
306
260
|
this.rowIndex = 0;
|
|
307
|
-
this.
|
|
308
|
-
this.
|
|
261
|
+
this.selectedTraceId = undefined;
|
|
262
|
+
this.traceDetail = undefined;
|
|
309
263
|
}
|
|
310
264
|
cycleRun(runs, direction) {
|
|
311
265
|
if (runs.length === 0)
|
|
312
266
|
return;
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
267
|
+
this.runIndex = (this.runIndex + direction + runs.length) % runs.length;
|
|
268
|
+
this.resetContentPosition();
|
|
269
|
+
}
|
|
270
|
+
handleScrollableInput(data, target) {
|
|
271
|
+
const key = target === "detail" ? "detailScroll" : "documentScroll";
|
|
272
|
+
if (matchesKey(data, "up"))
|
|
273
|
+
this[key] = Math.max(0, this[key] - 1);
|
|
274
|
+
else if (matchesKey(data, "down"))
|
|
275
|
+
this[key] += 1;
|
|
276
|
+
else if (matchesKey(data, "pageUp")) {
|
|
277
|
+
this[key] = Math.max(0, this[key] - this.contentViewportRows());
|
|
278
|
+
}
|
|
279
|
+
else if (matchesKey(data, "pageDown")) {
|
|
280
|
+
this[key] += this.contentViewportRows();
|
|
281
|
+
}
|
|
282
|
+
else if (matchesKey(data, "left") && target === "detail") {
|
|
283
|
+
this.selectedTraceId = this.traceDetail?.id;
|
|
284
|
+
this.traceDetail = undefined;
|
|
285
|
+
this.detailScroll = 0;
|
|
286
|
+
this.focus = "list";
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
handleSelectorInput(data, runs) {
|
|
290
|
+
const run = runs[this.runIndex];
|
|
291
|
+
const options = this.selectorMode === "run"
|
|
292
|
+
? runs.map((item) => item.run)
|
|
293
|
+
: this.traceSources(run);
|
|
294
|
+
if (matchesKey(data, "up"))
|
|
295
|
+
this.selectorIndex = Math.max(0, this.selectorIndex - 1);
|
|
296
|
+
else if (matchesKey(data, "down")) {
|
|
297
|
+
this.selectorIndex = Math.min(Math.max(0, options.length - 1), this.selectorIndex + 1);
|
|
298
|
+
}
|
|
299
|
+
else if (matchesKey(data, "left")) {
|
|
300
|
+
this.focus = this.selectorMode === "run" ? "runs" : "tabs";
|
|
301
|
+
this.selectorMode = undefined;
|
|
302
|
+
}
|
|
303
|
+
else if (matchesKey(data, "return") || matchesKey(data, "right")) {
|
|
304
|
+
if (this.selectorMode === "run") {
|
|
305
|
+
this.runIndex = this.selectorIndex;
|
|
306
|
+
this.focus = "runs";
|
|
307
|
+
}
|
|
308
|
+
else {
|
|
309
|
+
this.traceSourceIndex = this.selectorIndex;
|
|
310
|
+
this.focus = "tabs";
|
|
311
|
+
}
|
|
312
|
+
this.selectorMode = undefined;
|
|
313
|
+
this.resetContentPosition();
|
|
314
|
+
}
|
|
318
315
|
}
|
|
319
|
-
|
|
316
|
+
openSourceSelector(run) {
|
|
317
|
+
const sources = this.traceSources(run);
|
|
318
|
+
this.selectorMode = "source";
|
|
319
|
+
this.selectorIndex = Math.min(this.traceSourceIndex, sources.length - 1);
|
|
320
|
+
this.focus = "select";
|
|
321
|
+
}
|
|
322
|
+
cycleTraceSource(run) {
|
|
323
|
+
const sources = this.traceSources(run);
|
|
324
|
+
this.traceSourceIndex = (this.traceSourceIndex + 1) % sources.length;
|
|
325
|
+
this.resetContentPosition();
|
|
326
|
+
}
|
|
327
|
+
requestKill(run) {
|
|
320
328
|
this.feedback = undefined;
|
|
321
329
|
if (this.focus !== "runs") {
|
|
322
330
|
this.feedback = { ok: false, message: "Focus Run before using Kill." };
|
|
323
331
|
return;
|
|
324
332
|
}
|
|
325
|
-
const run = runs.find((item) => item.run === this.selectedRun);
|
|
326
333
|
if (!run) {
|
|
327
334
|
this.feedback = { ok: false, message: "Kill unavailable: no owned run." };
|
|
328
335
|
return;
|
|
@@ -332,17 +339,11 @@ export class ActorInspectorOverlay {
|
|
|
332
339
|
return;
|
|
333
340
|
}
|
|
334
341
|
if (run.status !== "running") {
|
|
335
|
-
this.feedback = {
|
|
336
|
-
ok: false,
|
|
337
|
-
message: `Kill unavailable: run is ${run.status}.`,
|
|
338
|
-
};
|
|
342
|
+
this.feedback = { ok: false, message: `Kill unavailable: run is ${run.status}.` };
|
|
339
343
|
return;
|
|
340
344
|
}
|
|
341
345
|
if (!run.runInstanceId) {
|
|
342
|
-
this.feedback = {
|
|
343
|
-
ok: false,
|
|
344
|
-
message: "Kill unavailable: run generation unavailable.",
|
|
345
|
-
};
|
|
346
|
+
this.feedback = { ok: false, message: "Kill unavailable: run generation unavailable." };
|
|
346
347
|
return;
|
|
347
348
|
}
|
|
348
349
|
this.killConfirmation = {
|
|
@@ -352,21 +353,39 @@ export class ActorInspectorOverlay {
|
|
|
352
353
|
};
|
|
353
354
|
this.killDialogChoice = "cancel";
|
|
354
355
|
}
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
356
|
+
handleKillInput(data) {
|
|
357
|
+
const key = data.toLowerCase();
|
|
358
|
+
if (matchesKey(data, "escape") || key === "n") {
|
|
359
|
+
this.feedback = { ok: false, message: "Kill cancelled." };
|
|
360
|
+
this.killConfirmation = undefined;
|
|
361
|
+
this.killDialogChoice = "cancel";
|
|
362
|
+
}
|
|
363
|
+
else if (matchesKey(data, "left") || matchesKey(data, "right") || matchesKey(data, "tab")) {
|
|
364
|
+
this.killDialogChoice = this.killDialogChoice === "cancel" ? "kill" : "cancel";
|
|
365
|
+
}
|
|
366
|
+
else if (key === "y") {
|
|
367
|
+
this.confirmKill();
|
|
368
|
+
}
|
|
369
|
+
else if (matchesKey(data, "return")) {
|
|
370
|
+
if (this.killDialogChoice === "kill")
|
|
371
|
+
this.confirmKill();
|
|
372
|
+
else {
|
|
373
|
+
this.feedback = { ok: false, message: "Kill cancelled." };
|
|
374
|
+
this.killConfirmation = undefined;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
359
377
|
}
|
|
360
378
|
confirmKill() {
|
|
361
379
|
const confirmation = this.killConfirmation;
|
|
362
380
|
this.killConfirmation = undefined;
|
|
363
381
|
this.killDialogChoice = "cancel";
|
|
364
|
-
if (!confirmation || !this.killRun)
|
|
365
|
-
this.feedback = { ok: false, message: "Kill unavailable in this Inspector." };
|
|
382
|
+
if (!confirmation || !this.killRun)
|
|
366
383
|
return;
|
|
367
|
-
}
|
|
368
384
|
try {
|
|
369
|
-
|
|
385
|
+
const result = this.killRun(confirmation.run, confirmation.runInstanceId);
|
|
386
|
+
this.feedback = result.ok ? undefined : result;
|
|
387
|
+
if (result.ok)
|
|
388
|
+
this.invalidate();
|
|
370
389
|
}
|
|
371
390
|
catch (error) {
|
|
372
391
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -376,14 +395,13 @@ export class ActorInspectorOverlay {
|
|
|
376
395
|
};
|
|
377
396
|
}
|
|
378
397
|
}
|
|
379
|
-
renderKillDialog(
|
|
398
|
+
renderKillDialog(innerWidth) {
|
|
380
399
|
const confirmation = this.killConfirmation;
|
|
381
|
-
const totalRows = this.contentViewportRows() + 5;
|
|
382
400
|
const cancel = this.killDialogChoice === "cancel"
|
|
383
|
-
? this.theme.bg("
|
|
401
|
+
? this.theme.bg("customMessageBg", this.theme.fg("accent", " Cancel "))
|
|
384
402
|
: this.theme.fg("muted", " Cancel ");
|
|
385
403
|
const kill = this.killDialogChoice === "kill"
|
|
386
|
-
? this.theme.bg("
|
|
404
|
+
? this.theme.bg("customMessageBg", this.theme.fg("error", " Kill actor "))
|
|
387
405
|
: this.theme.fg("error", " Kill actor ");
|
|
388
406
|
const body = [
|
|
389
407
|
"",
|
|
@@ -392,686 +410,371 @@ export class ActorInspectorOverlay {
|
|
|
392
410
|
`${this.theme.fg("muted", "Run:")} ${this.theme.fg("accent", `run:${confirmation.run}`)}`,
|
|
393
411
|
`${this.theme.fg("muted", "Current status:")} ${this.theme.fg("warning", confirmation.status)}`,
|
|
394
412
|
"",
|
|
395
|
-
this.theme.fg("text", "This sends canonical
|
|
396
|
-
this.theme.fg("error", "The action is destructive and cannot be undone."),
|
|
413
|
+
this.theme.fg("text", "This sends canonical kill Control."),
|
|
397
414
|
"",
|
|
398
415
|
`${cancel} ${kill}`,
|
|
399
416
|
"",
|
|
400
417
|
];
|
|
401
418
|
const lines = [this.border("╭", " Confirm Actor Kill ", "╮", innerWidth)];
|
|
402
|
-
const availableRows =
|
|
419
|
+
const availableRows = this.contentViewportRows() + 3;
|
|
403
420
|
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)
|
|
421
|
+
const rows = [...Array.from({ length: topPadding }, () => ""), ...body].slice(0, availableRows);
|
|
422
|
+
while (rows.length < availableRows)
|
|
423
|
+
rows.push("");
|
|
424
|
+
for (const line of rows)
|
|
411
425
|
lines.push(this.row(this.center(line, innerWidth), innerWidth));
|
|
412
426
|
lines.push(this.footerBorder(this.renderKeyHints(), innerWidth));
|
|
413
427
|
return lines;
|
|
414
428
|
}
|
|
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");
|
|
429
|
+
renderRunControl(run, sequence) {
|
|
430
|
+
const active = this.focus === "runs" || (this.focus === "select" && this.selectorMode === "run");
|
|
471
431
|
const prefix = active ? this.theme.fg("accent", " ← ") : " ";
|
|
472
432
|
const suffix = active ? this.theme.fg("accent", " → ") : " ";
|
|
473
433
|
if (!run) {
|
|
474
434
|
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} `;
|
|
435
|
+
return this.focus === "runs" ? this.theme.bg("customMessageBg", empty) : empty;
|
|
436
|
+
}
|
|
437
|
+
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}`;
|
|
438
|
+
return this.focus === "runs" ? this.theme.bg("customMessageBg", value) : value;
|
|
439
|
+
}
|
|
440
|
+
renderTabs(run) {
|
|
441
|
+
const source = this.traceSource(run);
|
|
442
|
+
return TABS.map((tab, index) => {
|
|
443
|
+
const base = `${tab[0].toUpperCase()}${tab.slice(1)}`;
|
|
444
|
+
const label = tab === "trace" && source !== "all" ? `${base} (${source})` : base;
|
|
445
|
+
const selected = index === this.tabIndex;
|
|
446
|
+
const display = selected && this.focus !== "runs" ? `[ ${label} ]` : ` ${label} `;
|
|
502
447
|
const value = ` ${display} `;
|
|
503
448
|
if (!selected)
|
|
504
449
|
return this.theme.fg("muted", value);
|
|
505
450
|
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)}`;
|
|
451
|
+
return this.focus === "tabs" ? this.theme.bg("customMessageBg", colored) : colored;
|
|
452
|
+
}).join(" ");
|
|
513
453
|
}
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
if (runs.length === 0)
|
|
454
|
+
renderContent(run, width) {
|
|
455
|
+
if (!run)
|
|
517
456
|
return [this.theme.fg("muted", " No owned actor runs")];
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
457
|
+
if (this.focus === "select")
|
|
458
|
+
return this.renderSelector(run);
|
|
459
|
+
if (this.focus === "detail")
|
|
460
|
+
return this.renderTraceDetail(width);
|
|
461
|
+
if (this.tab === "trace")
|
|
462
|
+
return this.renderTraceList(run);
|
|
463
|
+
const stateDir = path.join(this.stateRoot, run.run);
|
|
464
|
+
const value = this.tab === "recipe"
|
|
465
|
+
? ActorInspector.readActorInspectorRecipe(stateDir)
|
|
466
|
+
: this.controlDocument(run, stateDir);
|
|
467
|
+
const projection = this.documentProjection(value, width);
|
|
468
|
+
const maxStart = Math.max(0, projection.lines.length - this.contentViewportRows());
|
|
469
|
+
this.documentScroll = Math.min(this.documentScroll, maxStart);
|
|
470
|
+
this.contentStripeIndices = projection.stripes.slice(this.documentScroll, this.documentScroll + this.contentViewportRows());
|
|
471
|
+
return projection.lines.slice(this.documentScroll, this.documentScroll + this.contentViewportRows());
|
|
472
|
+
}
|
|
473
|
+
renderSelector(run) {
|
|
474
|
+
const options = this.selectorMode === "run"
|
|
475
|
+
? this.runs().map((item) => `run:${item.run} ${item.status}`)
|
|
476
|
+
: this.traceSources(run).map((source) => `Trace source: ${source}`);
|
|
477
|
+
const lines = this.renderMenuBox(options, this.selectorIndex);
|
|
478
|
+
this.contentStripeIndices = lines.map(() => 0);
|
|
479
|
+
return lines;
|
|
528
480
|
}
|
|
529
|
-
|
|
530
|
-
if (
|
|
531
|
-
return [];
|
|
532
|
-
const
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
481
|
+
renderMenuBox(options, focusedIndex) {
|
|
482
|
+
if (options.length === 0)
|
|
483
|
+
return [this.theme.fg("muted", " No options")];
|
|
484
|
+
const visibleLimit = Math.max(1, Math.min(this.contentViewportRows(), options.length));
|
|
485
|
+
const maxStart = Math.max(0, options.length - visibleLimit);
|
|
486
|
+
const start = Math.max(0, Math.min(focusedIndex - Math.floor(visibleLimit / 2), maxStart));
|
|
487
|
+
const visible = options.slice(start, start + visibleLimit);
|
|
488
|
+
const width = Math.max(8, ...options.map((option) => visibleWidth(option) + 4));
|
|
489
|
+
const border = (left, right, marker = "") => this.theme.fg("borderAccent", `${left}${marker}${"─".repeat(Math.max(0, width - visibleWidth(marker)))}${right}`);
|
|
490
|
+
return [
|
|
491
|
+
border("╭", "╮", start > 0 ? "↑" : ""),
|
|
492
|
+
...visible.map((option, offset) => {
|
|
493
|
+
const index = start + offset;
|
|
494
|
+
const row = this.fit(`${index === focusedIndex ? " ▶ " : " "}${option}`, width);
|
|
495
|
+
const colored = index === focusedIndex ? this.theme.fg("accent", row) : row;
|
|
496
|
+
const styled = index === focusedIndex ? this.theme.bg("customMessageBg", colored) : colored;
|
|
497
|
+
return `${this.theme.fg("borderAccent", "│")}${styled}${this.theme.fg("borderAccent", "│")}`;
|
|
498
|
+
}),
|
|
499
|
+
border("╰", "╯", start + visible.length < options.length ? "↓" : ""),
|
|
500
|
+
];
|
|
501
|
+
}
|
|
502
|
+
renderTraceList(run) {
|
|
503
|
+
const items = this.traceItems(run);
|
|
504
|
+
if (items.length === 0)
|
|
505
|
+
return [this.theme.fg("muted", " No Trace evidence")];
|
|
506
|
+
if (this.selectedTraceId) {
|
|
507
|
+
const selectedIndex = items.findIndex((item) => item.id === this.selectedTraceId);
|
|
508
|
+
if (selectedIndex >= 0)
|
|
509
|
+
this.rowIndex = selectedIndex;
|
|
510
|
+
else
|
|
511
|
+
this.selectedTraceId = undefined;
|
|
536
512
|
}
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
? this.communicationRows()
|
|
541
|
-
: this.turnRows();
|
|
542
|
-
if (rows.length === 0) {
|
|
543
|
-
this.contentStripeIndices = [0];
|
|
544
|
-
const filtered = this.tab === "communications"
|
|
545
|
-
? this.activeMessageFilters().length > 0
|
|
546
|
-
: (this.subagents()[this.subagentIndex] ?? "all") !== "all";
|
|
547
|
-
return [
|
|
548
|
-
this.theme.fg("muted", filtered
|
|
549
|
-
? " No results for the active filters. Enter on the tab to adjust."
|
|
550
|
-
: this.tab === "communications"
|
|
551
|
-
? " No messages in this run"
|
|
552
|
-
: " No persisted turns in this run"),
|
|
553
|
-
];
|
|
513
|
+
this.rowIndex = Math.min(this.rowIndex, items.length - 1);
|
|
514
|
+
if (this.focus === "list" && !this.selectedTraceId) {
|
|
515
|
+
this.selectedTraceId = items[this.rowIndex]?.id;
|
|
554
516
|
}
|
|
555
|
-
this.rowIndex = Math.min(this.rowIndex, rows.length - 1);
|
|
556
517
|
const viewportRows = this.contentViewportRows();
|
|
557
|
-
const
|
|
558
|
-
const
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
return visibleRows.map((row, offset) => {
|
|
518
|
+
const maxStart = Math.max(0, items.length - viewportRows);
|
|
519
|
+
const start = Math.max(0, Math.min(this.rowIndex - Math.floor(viewportRows / 2), maxStart));
|
|
520
|
+
this.contentStripeIndices = items
|
|
521
|
+
.slice(start, start + viewportRows)
|
|
522
|
+
.map((_item, offset) => start + offset);
|
|
523
|
+
return items.slice(start, start + viewportRows).map((item, offset) => {
|
|
564
524
|
const index = start + offset;
|
|
565
|
-
const
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
525
|
+
const detail = item.detail && typeof item.detail === "object"
|
|
526
|
+
? item.detail
|
|
527
|
+
: {};
|
|
528
|
+
const marker = item.level === "error"
|
|
529
|
+
? this.theme.fg("error", "!")
|
|
530
|
+
: detail.attention
|
|
531
|
+
? this.theme.fg("warning", "A")
|
|
532
|
+
: " ";
|
|
533
|
+
const prefix = this.focus === "list" && index === this.rowIndex
|
|
534
|
+
? this.theme.fg("accent", " ▶ ")
|
|
535
|
+
: " ";
|
|
536
|
+
const row = `${prefix}${this.theme.fg("text", `#${items.length - index}`)} ${marker} ${this.theme.fg("muted", item.source)}/${this.theme.fg(item.level === "error" ? "error" : "accent", item.kind)} ${this.theme.fg("text", item.summary)}`;
|
|
537
|
+
return this.focus === "list" && index === this.rowIndex
|
|
538
|
+
? this.theme.fg("accent", row)
|
|
539
|
+
: row;
|
|
569
540
|
});
|
|
570
541
|
}
|
|
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
|
-
|
|
542
|
+
renderTraceDetail(width) {
|
|
543
|
+
if (!this.traceDetail)
|
|
544
|
+
return [this.theme.fg("warning", " Trace row is no longer available")];
|
|
545
|
+
const projection = this.documentProjection(this.traceDetail, width);
|
|
546
|
+
const maxStart = Math.max(0, projection.lines.length - this.contentViewportRows());
|
|
547
|
+
this.detailScroll = Math.min(this.detailScroll, maxStart);
|
|
548
|
+
this.contentStripeIndices = projection.stripes.slice(this.detailScroll, this.detailScroll + this.contentViewportRows());
|
|
549
|
+
return projection.lines.slice(this.detailScroll, this.detailScroll + this.contentViewportRows());
|
|
550
|
+
}
|
|
551
|
+
controlDocument(run, stateDir) {
|
|
552
|
+
const endpoint = run.runInstanceId
|
|
553
|
+
? RunControlDelivery.readRunControlEndpoint(stateDir, run.runInstanceId)
|
|
554
|
+
: undefined;
|
|
555
|
+
const meta = this.readRunMeta(stateDir);
|
|
556
|
+
return {
|
|
557
|
+
status: run.status,
|
|
558
|
+
run_instance_id: run.runInstanceId,
|
|
559
|
+
actor_actions: Array.isArray(meta.control) ? meta.control : [],
|
|
560
|
+
runtime_actions: run.status === "running" ? ["kill"] : ["archive", "prune"],
|
|
561
|
+
endpoint,
|
|
562
|
+
recent_controls: RunsControls.readRunControlsFromStateDir(stateDir)
|
|
563
|
+
.slice(-20)
|
|
564
|
+
.reverse()
|
|
565
|
+
.map(ControlProjection.projectRunControl),
|
|
566
|
+
};
|
|
592
567
|
}
|
|
593
|
-
|
|
594
|
-
if (!
|
|
568
|
+
allTraceItems(run) {
|
|
569
|
+
if (!run)
|
|
595
570
|
return [];
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
571
|
+
if (this.traceCache?.run === run.run &&
|
|
572
|
+
this.traceCache.runInstanceId === run.runInstanceId) {
|
|
573
|
+
return this.traceCache.items;
|
|
574
|
+
}
|
|
575
|
+
const items = newestTraceItems(this.readTrace(path.join(this.stateRoot, run.run), {
|
|
576
|
+
limit: 100,
|
|
577
|
+
source: "all",
|
|
578
|
+
}));
|
|
579
|
+
this.traceCache = { run: run.run, runInstanceId: run.runInstanceId, items };
|
|
580
|
+
return items;
|
|
581
|
+
}
|
|
582
|
+
traceSources(run) {
|
|
583
|
+
const present = new Set(this.allTraceItems(run).map((item) => item.source));
|
|
584
|
+
return TRACE_SOURCES.filter((source) => source === "all" || present.has(source));
|
|
585
|
+
}
|
|
586
|
+
traceSource(run) {
|
|
587
|
+
const sources = this.traceSources(run);
|
|
588
|
+
this.traceSourceIndex %= sources.length;
|
|
589
|
+
return sources[this.traceSourceIndex] ?? "all";
|
|
590
|
+
}
|
|
591
|
+
traceItems(run) {
|
|
592
|
+
const source = this.traceSource(run);
|
|
593
|
+
const items = this.allTraceItems(run);
|
|
594
|
+
return source === "all" ? items : items.filter((item) => item.source === source);
|
|
595
|
+
}
|
|
596
|
+
readRunMeta(stateDir) {
|
|
597
|
+
try {
|
|
598
|
+
return JSON.parse(readFileSync(path.join(stateDir, "run.json"), "utf8"));
|
|
599
|
+
}
|
|
600
|
+
catch {
|
|
601
|
+
return {};
|
|
602
|
+
}
|
|
623
603
|
}
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
604
|
+
documentProjection(value, width) {
|
|
605
|
+
const sections = value && typeof value === "object" && !Array.isArray(value)
|
|
606
|
+
? Object.entries(value).map(([key, item]) => this.labeledDocumentLines(key, item))
|
|
607
|
+
: [this.document(value)];
|
|
608
|
+
const lines = [];
|
|
609
|
+
const stripes = [];
|
|
610
|
+
sections.forEach((section, sectionIndex) => {
|
|
611
|
+
const wrapped = this.wrapDocument(section, width);
|
|
612
|
+
lines.push(...wrapped.map((line) => this.styleDocumentLine(line)));
|
|
613
|
+
stripes.push(...wrapped.map(() => sectionIndex));
|
|
630
614
|
});
|
|
615
|
+
return { lines, stripes };
|
|
631
616
|
}
|
|
632
|
-
|
|
633
|
-
if (
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
["
|
|
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);
|
|
667
|
-
}
|
|
668
|
-
wrapDetailLines(lines, width) {
|
|
669
|
-
const wrappedLines = [];
|
|
670
|
-
const stripeIndices = [];
|
|
671
|
-
let sectionIndex = 0;
|
|
672
|
-
for (const line of lines) {
|
|
673
|
-
if (typeof line !== "string") {
|
|
674
|
-
sectionIndex += 1;
|
|
675
|
-
continue;
|
|
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
|
-
}
|
|
617
|
+
inlineDocumentValue(value) {
|
|
618
|
+
if (Array.isArray(value)) {
|
|
619
|
+
if (value.length === 0)
|
|
620
|
+
return "[]";
|
|
621
|
+
if (value.every((item) => (item === null || typeof item !== "object") &&
|
|
622
|
+
!(typeof item === "string" && /[\r\n]/u.test(item)))) {
|
|
623
|
+
return `[${value.map((item) => String(item ?? "none")).join(", ")}]`;
|
|
715
624
|
}
|
|
625
|
+
return undefined;
|
|
716
626
|
}
|
|
717
|
-
|
|
627
|
+
if (value && typeof value === "object") {
|
|
628
|
+
if (Object.keys(value).length === 0)
|
|
629
|
+
return "{}";
|
|
630
|
+
}
|
|
631
|
+
return undefined;
|
|
718
632
|
}
|
|
719
|
-
|
|
720
|
-
const
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
if (/\s/u.test(candidate[index])) {
|
|
728
|
-
splitAt = index + 1;
|
|
729
|
-
trimBoundary = true;
|
|
730
|
-
}
|
|
731
|
-
else if (/[-+=/_,.:]/u.test(candidate[index])) {
|
|
732
|
-
splitAt = index;
|
|
733
|
-
trimBoundary = false;
|
|
734
|
-
}
|
|
735
|
-
}
|
|
736
|
-
if (splitAt <= 0)
|
|
737
|
-
splitAt = candidate.length;
|
|
738
|
-
const head = remaining.slice(0, splitAt).trimEnd();
|
|
739
|
-
if (!head) {
|
|
740
|
-
lines.push(candidate);
|
|
741
|
-
remaining = remaining.slice(candidate.length);
|
|
742
|
-
continue;
|
|
743
|
-
}
|
|
744
|
-
lines.push(head);
|
|
745
|
-
remaining = remaining.slice(splitAt);
|
|
746
|
-
if (trimBoundary)
|
|
747
|
-
remaining = remaining.trimStart();
|
|
633
|
+
labeledDocumentLines(key, value, depth = 0) {
|
|
634
|
+
const inline = this.inlineDocumentValue(value);
|
|
635
|
+
if (inline !== undefined)
|
|
636
|
+
return [`${" ".repeat(depth)}${key}: ${inline}`];
|
|
637
|
+
if (value && typeof value === "object") {
|
|
638
|
+
const indent = " ".repeat(depth);
|
|
639
|
+
const nested = this.document(value, depth);
|
|
640
|
+
return [`${indent}${key}: ${nested[0].slice(indent.length)}`, ...nested.slice(1)];
|
|
748
641
|
}
|
|
749
|
-
|
|
750
|
-
lines.push(remaining);
|
|
751
|
-
return lines;
|
|
642
|
+
return this.labeledScalarLines(key, value, depth);
|
|
752
643
|
}
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
644
|
+
labeledScalarLines(key, value, depth = 0) {
|
|
645
|
+
const indent = " ".repeat(depth);
|
|
646
|
+
const [first = "", ...rest] = String(value ?? "none").split(/\r?\n/u);
|
|
756
647
|
return [
|
|
757
|
-
|
|
758
|
-
...
|
|
759
|
-
.split(/\r?\n/)
|
|
760
|
-
.filter((physicalLine) => physicalLine.trim().length > 0)
|
|
761
|
-
.map((physicalLine) => ` ${physicalLine}`)),
|
|
762
|
-
DETAIL_SECTION_BREAK,
|
|
648
|
+
`${indent}${key}: ${first}`,
|
|
649
|
+
...rest.map((line) => `${indent} ${line}`),
|
|
763
650
|
];
|
|
764
651
|
}
|
|
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
|
-
}
|
|
652
|
+
document(value, depth = 0) {
|
|
780
653
|
if (value === null || value === undefined)
|
|
781
|
-
return [`${
|
|
782
|
-
|
|
783
|
-
|
|
654
|
+
return [`${" ".repeat(depth)}none`];
|
|
655
|
+
const inline = this.inlineDocumentValue(value);
|
|
656
|
+
if (inline !== undefined)
|
|
657
|
+
return [`${" ".repeat(depth)}${inline}`];
|
|
784
658
|
if (Array.isArray(value)) {
|
|
785
|
-
|
|
786
|
-
return [`${indent}(empty)`];
|
|
787
|
-
return value.flatMap((item) => {
|
|
788
|
-
if (item &&
|
|
789
|
-
typeof item === "object" &&
|
|
790
|
-
item.type === "text" &&
|
|
791
|
-
typeof item.text === "string") {
|
|
792
|
-
return this.readableValueLines(item.text, indent, depth + 1);
|
|
793
|
-
}
|
|
794
|
-
if (!item || typeof item !== "object")
|
|
795
|
-
return [`${indent}- ${String(item)}`];
|
|
796
|
-
const [first = "", ...rest] = this.readableValueLines(item, `${indent} `, depth + 1);
|
|
797
|
-
return [
|
|
798
|
-
`${indent}- ${first.trimStart()}`,
|
|
799
|
-
...rest,
|
|
800
|
-
];
|
|
801
|
-
});
|
|
802
|
-
}
|
|
803
|
-
const entries = Object.entries(value);
|
|
804
|
-
if (entries.length === 0)
|
|
805
|
-
return [`${indent}(empty)`];
|
|
806
|
-
return entries.flatMap(([key, item]) => {
|
|
807
|
-
if (!item || typeof item !== "object") {
|
|
808
|
-
const scalar = typeof item === "string" ? item : String(item);
|
|
809
|
-
const [first = "", ...rest] = scalar.split(/\r?\n/);
|
|
810
|
-
return [
|
|
811
|
-
`${indent}${key}: ${first}`,
|
|
812
|
-
...rest.map((line) => `${indent} ${line}`),
|
|
813
|
-
];
|
|
814
|
-
}
|
|
659
|
+
const indent = " ".repeat(depth);
|
|
815
660
|
return [
|
|
816
|
-
`${indent}
|
|
817
|
-
...
|
|
661
|
+
`${indent}[`,
|
|
662
|
+
...value.flatMap((item, index) => {
|
|
663
|
+
const marker = `${indent} - #${index + 1}`;
|
|
664
|
+
const itemInline = this.inlineDocumentValue(item);
|
|
665
|
+
return itemInline !== undefined
|
|
666
|
+
? [`${marker}: ${itemInline}`]
|
|
667
|
+
: [marker, ...this.document(item, depth + 2)];
|
|
668
|
+
}),
|
|
669
|
+
`${indent}]`,
|
|
818
670
|
];
|
|
819
|
-
}
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
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}`,
|
|
671
|
+
}
|
|
672
|
+
if (typeof value === "object") {
|
|
673
|
+
const indent = " ".repeat(depth);
|
|
674
|
+
const entries = Object.entries(value);
|
|
675
|
+
return [
|
|
676
|
+
`${indent}{`,
|
|
677
|
+
...entries.flatMap(([key, item], index) => {
|
|
678
|
+
const lines = this.labeledDocumentLines(key, item, depth + 1);
|
|
679
|
+
if (index < entries.length - 1)
|
|
680
|
+
lines[lines.length - 1] += ",";
|
|
681
|
+
return lines;
|
|
682
|
+
}),
|
|
683
|
+
`${indent}}`,
|
|
846
684
|
];
|
|
847
|
-
lines.push(...this.detailSection(`Tool ${index + 1} (${tool.name}${tool.resultError ? ", Error" : ""})`, content));
|
|
848
685
|
}
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
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}`;
|
|
686
|
+
return String(value)
|
|
687
|
+
.split(/\r?\n/u)
|
|
688
|
+
.map((line) => `${" ".repeat(depth)}${line}`);
|
|
689
|
+
}
|
|
690
|
+
wrapDocument(lines, width) {
|
|
691
|
+
return lines.flatMap((line) => {
|
|
692
|
+
const normalized = line.replaceAll("\t", " ");
|
|
693
|
+
if (visibleWidth(normalized) <= width)
|
|
694
|
+
return [normalized];
|
|
695
|
+
const leading = normalized.match(/^ */u)?.[0] ?? "";
|
|
696
|
+
const continuation = `${leading} `;
|
|
697
|
+
const wrapped = [];
|
|
698
|
+
let rest = normalized.slice(leading.length);
|
|
699
|
+
let prefix = leading;
|
|
700
|
+
while (rest && visibleWidth(`${prefix}${rest}`) > width) {
|
|
701
|
+
const available = Math.max(1, width - visibleWidth(prefix));
|
|
702
|
+
const candidate = this.visiblePrefix(rest, available);
|
|
703
|
+
const minimumBreak = Math.floor(candidate.length * 0.6);
|
|
704
|
+
const preferred = Math.max(candidate.lastIndexOf(" "), candidate.lastIndexOf("/"), candidate.lastIndexOf(","));
|
|
705
|
+
const cut = preferred >= minimumBreak ? preferred + 1 : candidate.length;
|
|
706
|
+
wrapped.push(`${prefix}${rest.slice(0, cut).trimEnd()}`);
|
|
707
|
+
rest = rest.slice(cut).trimStart();
|
|
708
|
+
prefix = continuation;
|
|
709
|
+
}
|
|
710
|
+
if (rest)
|
|
711
|
+
wrapped.push(`${prefix}${rest}`);
|
|
712
|
+
return wrapped;
|
|
924
713
|
});
|
|
925
714
|
}
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
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;
|
|
715
|
+
styleDocumentLine(line) {
|
|
716
|
+
const indent = line.match(/^ */u)?.[0] ?? "";
|
|
717
|
+
const content = line.slice(indent.length);
|
|
718
|
+
if (/^- #\d+$/u.test(content) || content.endsWith(":")) {
|
|
719
|
+
return `${indent}${this.theme.fg("accent", content)}`;
|
|
958
720
|
}
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
this.
|
|
964
|
-
this.menuLevel = "run";
|
|
965
|
-
this.focus = "select";
|
|
721
|
+
const labeled = /^([^:]+:)(.*)$/u.exec(content);
|
|
722
|
+
if (labeled) {
|
|
723
|
+
return `${indent}${this.theme.fg("muted", labeled[1])}${this.theme.fg("text", labeled[2])}`;
|
|
724
|
+
}
|
|
725
|
+
return `${indent}${this.theme.fg(content === "none" || content === "[]" ? "muted" : "text", content)}`;
|
|
966
726
|
}
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
727
|
+
visiblePrefix(value, width) {
|
|
728
|
+
let consumed = 0;
|
|
729
|
+
let result = "";
|
|
730
|
+
for (const character of value) {
|
|
731
|
+
const characterWidth = visibleWidth(character);
|
|
732
|
+
if (consumed + characterWidth > width)
|
|
733
|
+
break;
|
|
734
|
+
result += character;
|
|
735
|
+
consumed += characterWidth;
|
|
736
|
+
}
|
|
737
|
+
return result;
|
|
971
738
|
}
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
return;
|
|
739
|
+
renderKeyHints() {
|
|
740
|
+
const hint = (keys, description) => `${this.theme.fg("accent", keys)}${this.theme.fg("borderAccent", ` ${description}`)}`;
|
|
741
|
+
const divider = this.theme.fg("borderAccent", " ─ ");
|
|
742
|
+
const hints = (...items) => items.map(([keys, description]) => hint(keys, description)).join(divider);
|
|
743
|
+
if (this.killConfirmation) {
|
|
744
|
+
return hints(["←→/tab", "choose"], ["enter/y", "confirm"], ["esc/n", "cancel"]);
|
|
979
745
|
}
|
|
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;
|
|
746
|
+
if (this.focus === "select") {
|
|
747
|
+
return hints(["↑↓", "option"], ["enter/→", "apply"], ["←/esc", "back"]);
|
|
987
748
|
}
|
|
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));
|
|
749
|
+
if (this.focus === "detail") {
|
|
750
|
+
return hints(["↑↓/pgup/pgdn", "scroll"], ["←/esc", "back"]);
|
|
1058
751
|
}
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
752
|
+
if (this.focus === "document") {
|
|
753
|
+
return hints(["↑↓/pgup/pgdn", "scroll"], ["↑ at top", "tabs"], ["←/esc", "tabs"]);
|
|
754
|
+
}
|
|
755
|
+
if (this.focus === "list") {
|
|
756
|
+
return hints(["↑↓/pgup/pgdn", "row"], ["→/enter", "open"], ["←", "tabs"], ["esc", "close"]);
|
|
757
|
+
}
|
|
758
|
+
if (this.focus === "runs") {
|
|
759
|
+
const items = [["←→", "run"], ["↓", "tabs"], ["enter", "list"]];
|
|
760
|
+
const run = this.runs()[this.runIndex];
|
|
761
|
+
if (this.killRun && run?.status === "running" && run.runInstanceId)
|
|
762
|
+
items.push(["k", "kill"]);
|
|
763
|
+
items.push(["esc", "close"]);
|
|
764
|
+
return hints(...items);
|
|
765
|
+
}
|
|
766
|
+
return this.tab === "trace"
|
|
767
|
+
? hints(["←→", "tabs"], ["↑↓", "focus"], ["enter/f", "source"], ["esc", "close"])
|
|
768
|
+
: hints(["←→", "tabs"], ["↑↓", "focus"], ["enter", "open"], ["esc", "close"]);
|
|
1068
769
|
}
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
770
|
+
statusColor(status) {
|
|
771
|
+
if (status === "failed")
|
|
772
|
+
return "error";
|
|
773
|
+
if (status === "running")
|
|
774
|
+
return "warning";
|
|
775
|
+
if (status === "done" || status === "terminal")
|
|
776
|
+
return "success";
|
|
777
|
+
return "muted";
|
|
1075
778
|
}
|
|
1076
779
|
footerBorder(hints, width) {
|
|
1077
780
|
const prefix = " ";
|
|
@@ -1080,63 +783,26 @@ export class ActorInspectorOverlay {
|
|
|
1080
783
|
const fill = "─".repeat(Math.max(0, width - visibleWidth(prefix) - visibleWidth(suffix) - visibleWidth(content)));
|
|
1081
784
|
return `${this.theme.fg("borderAccent", `╰${prefix}`)}${content}${this.theme.fg("borderAccent", `${suffix}${fill}╯`)}`;
|
|
1082
785
|
}
|
|
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
786
|
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("");
|
|
787
|
+
return index % 2 === 0 ? content : this.theme.bg("customMessageBg", content);
|
|
1095
788
|
}
|
|
1096
789
|
stripedRow(content, width, index) {
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
row(content, width, fitted = false) {
|
|
1100
|
-
const body = fitted ? content : this.fit(content, width);
|
|
1101
|
-
return `${this.theme.fg("borderAccent", "│")}${body}${this.theme.fg("borderAccent", "│")}`;
|
|
790
|
+
const fitted = this.fit(content, width);
|
|
791
|
+
return `${this.theme.fg("borderAccent", "│")}${this.stripeBackground(fitted, index)}${this.theme.fg("borderAccent", "│")}`;
|
|
1102
792
|
}
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
let consumed = 0;
|
|
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);
|
|
793
|
+
row(content, width) {
|
|
794
|
+
return `${this.theme.fg("borderAccent", "│")}${this.fit(content, width)}${this.theme.fg("borderAccent", "│")}`;
|
|
1127
795
|
}
|
|
1128
|
-
|
|
1129
|
-
const
|
|
1130
|
-
|
|
1131
|
-
return `${" ".repeat(leading)}${content}`;
|
|
796
|
+
border(left, label, right, width) {
|
|
797
|
+
const bounded = truncateToWidth(label, width, "");
|
|
798
|
+
return this.theme.fg("borderAccent", `${left}${bounded}${"─".repeat(Math.max(0, width - visibleWidth(bounded)))}${right}`);
|
|
1132
799
|
}
|
|
1133
800
|
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, "…");
|
|
801
|
+
const bounded = truncateToWidth(content, width, "");
|
|
1140
802
|
return `${bounded}${" ".repeat(Math.max(0, width - visibleWidth(bounded)))}`;
|
|
1141
803
|
}
|
|
804
|
+
center(content, width) {
|
|
805
|
+
const padding = Math.max(0, Math.floor((width - visibleWidth(content)) / 2));
|
|
806
|
+
return `${" ".repeat(padding)}${content}`;
|
|
807
|
+
}
|
|
1142
808
|
}
|