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