@llblab/pi-actors 0.41.0 → 0.42.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +13 -8
- package/BACKLOG.md +2 -81
- package/CHANGELOG.md +22 -0
- package/README.md +22 -4
- package/dist/index.js +29 -82
- package/dist/lib/async-runs.d.ts +25 -5
- package/dist/lib/async-runs.js +136 -47
- package/dist/lib/automatic-review-runtime.d.ts +18 -0
- package/dist/lib/automatic-review-runtime.js +96 -0
- package/dist/lib/draft-consolidation-transaction.d.ts +65 -0
- package/dist/lib/draft-consolidation-transaction.js +610 -0
- package/dist/lib/draft-consolidation.d.ts +35 -0
- package/dist/lib/draft-consolidation.js +126 -0
- package/dist/lib/draft-review.d.ts +56 -0
- package/dist/lib/draft-review.js +254 -0
- package/dist/lib/draft-sleep.d.ts +65 -0
- package/dist/lib/draft-sleep.js +468 -0
- package/dist/lib/file-state.d.ts +8 -1
- package/dist/lib/file-state.js +115 -18
- package/dist/lib/inspector-actions.d.ts +16 -0
- package/dist/lib/inspector-actions.js +57 -0
- package/dist/lib/inspector-command.d.ts +7 -0
- package/dist/lib/inspector-command.js +37 -0
- package/dist/lib/inspector-overlay.d.ts +29 -1
- package/dist/lib/inspector-overlay.js +537 -90
- package/dist/lib/inspector.d.ts +9 -0
- package/dist/lib/inspector.js +52 -1
- package/dist/lib/observability.d.ts +39 -3
- package/dist/lib/observability.js +162 -34
- package/dist/lib/paths.d.ts +7 -0
- package/dist/lib/paths.js +28 -0
- package/dist/lib/recipes-discovery.js +32 -24
- package/dist/lib/recipes-usage.d.ts +18 -6
- package/dist/lib/recipes-usage.js +445 -34
- package/dist/lib/review-control.d.ts +14 -0
- package/dist/lib/review-control.js +111 -0
- package/dist/lib/review-diagnostics.d.ts +11 -0
- package/dist/lib/review-diagnostics.js +148 -0
- package/dist/lib/review-projection.d.ts +14 -0
- package/dist/lib/review-projection.js +170 -0
- package/dist/lib/run-ui-runtime.d.ts +18 -0
- package/dist/lib/run-ui-runtime.js +123 -0
- package/dist/lib/runs-artifacts.d.ts +1 -1
- package/dist/lib/runs-artifacts.js +1 -1
- package/dist/lib/runs-control.d.ts +8 -2
- package/dist/lib/runs-control.js +23 -6
- package/dist/lib/runs-identity.d.ts +1 -1
- package/dist/lib/runs-identity.js +1 -1
- package/dist/lib/runs-index.d.ts +11 -2
- package/dist/lib/runs-index.js +46 -23
- package/dist/lib/runs-mailbox.d.ts +1 -1
- package/dist/lib/runs-mailbox.js +1 -1
- package/dist/lib/runs-messages.d.ts +1 -1
- package/dist/lib/runs-messages.js +1 -1
- package/dist/lib/runs-outbox.d.ts +1 -1
- package/dist/lib/runs-outbox.js +1 -1
- package/dist/lib/runs-ownership.d.ts +1 -1
- package/dist/lib/runs-ownership.js +1 -1
- package/dist/lib/runs-parent-teardown.d.ts +51 -0
- package/dist/lib/runs-parent-teardown.js +172 -0
- package/dist/lib/runs-process.d.ts +1 -1
- package/dist/lib/runs-process.js +1 -1
- package/dist/lib/runs-retention.d.ts +1 -1
- package/dist/lib/runs-retention.js +1 -1
- package/dist/lib/runs-start.d.ts +5 -3
- package/dist/lib/runs-start.js +6 -48
- package/dist/lib/runs-status.d.ts +5 -3
- package/dist/lib/runs-status.js +4 -6
- package/dist/lib/runtime.d.ts +7 -1
- package/dist/lib/runtime.js +32 -18
- package/dist/lib/tool-review-lineage-transaction.d.ts +27 -0
- package/dist/lib/tool-review-lineage-transaction.js +597 -0
- package/dist/lib/tool-review-lineage.d.ts +24 -0
- package/dist/lib/tool-review-lineage.js +98 -0
- package/dist/lib/tool-review-scheduler.d.ts +80 -0
- package/dist/lib/tool-review-scheduler.js +494 -0
- package/dist/lib/tool-review-transaction.d.ts +50 -0
- package/dist/lib/tool-review-transaction.js +362 -0
- package/dist/lib/tool-review.d.ts +56 -0
- package/dist/lib/tool-review.js +197 -0
- package/dist/lib/tools-inspect.js +26 -3
- package/dist/lib/tools-local.js +4 -2
- package/dist/lib/tools-message.d.ts +1 -0
- package/dist/lib/tools-message.js +29 -17
- package/dist/lib/tools-response.d.ts +1 -1
- package/dist/lib/tools-response.js +5 -7
- package/dist/lib/tools-spawn.js +4 -1
- package/dist/lib/tools.d.ts +1 -0
- package/dist/lib/tools.js +1 -0
- package/dist/recipes/draft-review.json +24 -0
- package/dist/recipes/tool-review.json +24 -0
- package/dist/scripts/release-gates.mjs +165 -0
- package/dist/skills/actors/SKILL.md +11 -14
- package/dist/skills/swarm/SKILL.md +1 -1
- package/docs/actor-inspector.md +32 -18
- package/docs/async-runs.md +10 -2
- package/docs/recipe-library.md +7 -3
- package/docs/template-recipes.md +4 -11
- package/docs/tool-registry.md +11 -4
- package/index.ts +29 -103
- package/lib/async-runs.ts +218 -62
- package/lib/automatic-review-runtime.ts +135 -0
- package/lib/draft-consolidation-transaction.ts +821 -0
- package/lib/draft-consolidation.ts +181 -0
- package/lib/draft-review.ts +325 -0
- package/lib/draft-sleep.ts +576 -0
- package/lib/file-state.ts +143 -19
- package/lib/inspector-actions.ts +79 -0
- package/lib/inspector-command.ts +54 -0
- package/lib/inspector-overlay.ts +675 -105
- package/lib/inspector.ts +78 -3
- package/lib/observability.ts +219 -40
- package/lib/paths.ts +43 -0
- package/lib/recipes-discovery.ts +34 -26
- package/lib/recipes-usage.ts +569 -40
- package/lib/review-control.ts +137 -0
- package/lib/review-diagnostics.ts +164 -0
- package/lib/review-projection.ts +200 -0
- package/lib/run-ui-runtime.ts +153 -0
- package/lib/runs-artifacts.ts +1 -1
- package/lib/runs-control.ts +49 -5
- package/lib/runs-identity.ts +1 -1
- package/lib/runs-index.ts +57 -21
- package/lib/runs-mailbox.ts +1 -1
- package/lib/runs-messages.ts +1 -1
- package/lib/runs-outbox.ts +1 -1
- package/lib/runs-ownership.ts +1 -1
- package/lib/runs-parent-teardown.ts +257 -0
- package/lib/runs-process.ts +1 -1
- package/lib/runs-retention.ts +1 -1
- package/lib/runs-start.ts +12 -68
- package/lib/runs-status.ts +12 -8
- package/lib/runtime.ts +34 -17
- package/lib/tool-review-lineage-transaction.ts +881 -0
- package/lib/tool-review-lineage.ts +145 -0
- package/lib/tool-review-scheduler.ts +635 -0
- package/lib/tool-review-transaction.ts +563 -0
- package/lib/tool-review.ts +270 -0
- package/lib/tools-inspect.ts +33 -3
- package/lib/tools-local.ts +8 -2
- package/lib/tools-message.ts +45 -30
- package/lib/tools-response.ts +5 -6
- package/lib/tools-spawn.ts +8 -1
- package/lib/tools.ts +5 -0
- package/package.json +3 -2
- package/recipes/draft-review.json +24 -0
- package/recipes/tool-review.json +24 -0
- package/scripts/release-gates.mjs +165 -0
- package/skills/actors/SKILL.md +11 -14
- package/skills/swarm/SKILL.md +1 -1
package/lib/inspector-overlay.ts
CHANGED
|
@@ -17,11 +17,31 @@ import {
|
|
|
17
17
|
|
|
18
18
|
import * as Inspector from "./inspector.ts";
|
|
19
19
|
|
|
20
|
-
export type ActorInspectorOverlayTab = "communications" | "turns";
|
|
20
|
+
export type ActorInspectorOverlayTab = "recipe" | "communications" | "turns";
|
|
21
|
+
|
|
22
|
+
const INSPECTOR_TABS: ActorInspectorOverlayTab[] = [
|
|
23
|
+
"recipe",
|
|
24
|
+
"communications",
|
|
25
|
+
"turns",
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
type DetailLogicalLine = string | { sectionBreak: true };
|
|
29
|
+
|
|
30
|
+
const DETAIL_SECTION_BREAK: DetailLogicalLine = { sectionBreak: true };
|
|
31
|
+
|
|
32
|
+
export interface ActorInspectorOverlayActionResult {
|
|
33
|
+
ok: boolean;
|
|
34
|
+
message: string;
|
|
35
|
+
}
|
|
21
36
|
|
|
22
37
|
export interface ActorInspectorOverlayOptions {
|
|
23
38
|
done: () => void;
|
|
39
|
+
killRun?: (
|
|
40
|
+
run: string,
|
|
41
|
+
runInstanceId: string,
|
|
42
|
+
) => ActorInspectorOverlayActionResult;
|
|
24
43
|
ownerId: string;
|
|
44
|
+
readTurns?: (stateDir: string) => Inspector.ActorInspectorTurnItem[];
|
|
25
45
|
stateRoot: string;
|
|
26
46
|
theme: Theme;
|
|
27
47
|
tui: TUI;
|
|
@@ -29,7 +49,12 @@ export interface ActorInspectorOverlayOptions {
|
|
|
29
49
|
|
|
30
50
|
export class ActorInspectorOverlay {
|
|
31
51
|
private readonly done: () => void;
|
|
52
|
+
private readonly killRun?: (
|
|
53
|
+
run: string,
|
|
54
|
+
runInstanceId: string,
|
|
55
|
+
) => ActorInspectorOverlayActionResult;
|
|
32
56
|
private readonly ownerId: string;
|
|
57
|
+
private readonly readTurns: (stateDir: string) => Inspector.ActorInspectorTurnItem[];
|
|
33
58
|
private readonly stateRoot: string;
|
|
34
59
|
private readonly theme: Theme;
|
|
35
60
|
private readonly tui: TUI;
|
|
@@ -43,23 +68,32 @@ export class ActorInspectorOverlay {
|
|
|
43
68
|
private detailOpen = false;
|
|
44
69
|
private detailScroll = 0;
|
|
45
70
|
private detailTurn?: Inspector.ActorInspectorTurnItem;
|
|
71
|
+
private feedback?: ActorInspectorOverlayActionResult;
|
|
72
|
+
private killConfirmation?: { run: string; runInstanceId: string };
|
|
46
73
|
private readonly readKeys = new Set<string>();
|
|
47
|
-
private focus: "runs" | "tabs" | "list" | "detail" | "select" = "tabs";
|
|
74
|
+
private focus: "runs" | "tabs" | "recipe" | "list" | "detail" | "select" = "tabs";
|
|
48
75
|
private filterControlIndex = 0;
|
|
49
76
|
private menuLevel: "run" | "filter" | "value" = "filter";
|
|
50
77
|
private selectorIndex = 0;
|
|
78
|
+
private recipeScroll = 0;
|
|
51
79
|
private rowIndex = 0;
|
|
52
80
|
private selectedRun?: string;
|
|
53
81
|
private subagentIndex = 0;
|
|
54
|
-
private tab: ActorInspectorOverlayTab = "
|
|
82
|
+
private tab: ActorInspectorOverlayTab = "recipe";
|
|
83
|
+
private turnCache?: { run: string; turns: Inspector.ActorInspectorTurnItem[] };
|
|
55
84
|
|
|
56
85
|
constructor(options: ActorInspectorOverlayOptions) {
|
|
57
86
|
this.done = options.done;
|
|
87
|
+
this.killRun = options.killRun;
|
|
58
88
|
this.ownerId = options.ownerId;
|
|
89
|
+
this.readTurns = options.readTurns ?? Inspector.readActorInspectorTurns;
|
|
59
90
|
this.stateRoot = options.stateRoot;
|
|
60
91
|
this.theme = options.theme;
|
|
61
92
|
this.tui = options.tui;
|
|
62
|
-
this.refreshTimer = setInterval(() =>
|
|
93
|
+
this.refreshTimer = setInterval(() => {
|
|
94
|
+
if (this.focus !== "list" && this.focus !== "detail") this.turnCache = undefined;
|
|
95
|
+
this.tui.requestRender();
|
|
96
|
+
}, 1000);
|
|
63
97
|
this.refreshTimer.unref?.();
|
|
64
98
|
}
|
|
65
99
|
|
|
@@ -70,13 +104,29 @@ export class ActorInspectorOverlay {
|
|
|
70
104
|
this.done();
|
|
71
105
|
return;
|
|
72
106
|
}
|
|
107
|
+
if (this.killConfirmation) {
|
|
108
|
+
if (matchesKey(data, "escape") || data.toLowerCase() === "n") {
|
|
109
|
+
this.feedback = { ok: false, message: "Kill cancelled." };
|
|
110
|
+
this.killConfirmation = undefined;
|
|
111
|
+
} else if (matchesKey(data, "return") || data.toLowerCase() === "y") {
|
|
112
|
+
this.confirmKill();
|
|
113
|
+
}
|
|
114
|
+
this.tui.requestRender();
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
if (data.toLowerCase() === "k") {
|
|
118
|
+
this.requestKill(runs);
|
|
119
|
+
this.tui.requestRender();
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
73
122
|
if (matchesKey(data, "escape")) {
|
|
74
123
|
if (this.focus === "select") {
|
|
75
124
|
if (this.menuLevel === "value") {
|
|
76
125
|
this.menuLevel = "filter";
|
|
77
126
|
this.selectorIndex = this.filterControlIndex;
|
|
78
127
|
} else this.focus = this.menuLevel === "run" ? "runs" : "tabs";
|
|
79
|
-
} else if (this.focus === "detail") this.
|
|
128
|
+
} else if (this.focus === "detail") this.backDetail();
|
|
129
|
+
else if (this.focus === "recipe") this.focus = "tabs";
|
|
80
130
|
else this.done();
|
|
81
131
|
this.tui.requestRender();
|
|
82
132
|
return;
|
|
@@ -96,28 +146,61 @@ export class ActorInspectorOverlay {
|
|
|
96
146
|
this.tui.requestRender();
|
|
97
147
|
return;
|
|
98
148
|
}
|
|
149
|
+
if (this.focus === "recipe") {
|
|
150
|
+
if (matchesKey(data, "up")) {
|
|
151
|
+
if (this.recipeScroll === 0) this.focus = "tabs";
|
|
152
|
+
else this.recipeScroll -= 1;
|
|
153
|
+
} else if (matchesKey(data, "down")) this.recipeScroll += 1;
|
|
154
|
+
else if (matchesKey(data, "pageUp"))
|
|
155
|
+
this.recipeScroll = Math.max(0, this.recipeScroll - this.contentViewportRows());
|
|
156
|
+
else if (matchesKey(data, "pageDown"))
|
|
157
|
+
this.recipeScroll += this.contentViewportRows();
|
|
158
|
+
else if (matchesKey(data, "left")) this.focus = "tabs";
|
|
159
|
+
this.tui.requestRender();
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
99
162
|
if (this.focus === "detail") {
|
|
100
163
|
if (matchesKey(data, "up")) this.detailScroll = Math.max(0, this.detailScroll - 1);
|
|
101
164
|
else if (matchesKey(data, "down")) this.detailScroll += 1;
|
|
102
|
-
else if (matchesKey(data, "
|
|
165
|
+
else if (matchesKey(data, "pageUp"))
|
|
166
|
+
this.detailScroll = Math.max(0, this.detailScroll - this.contentViewportRows());
|
|
167
|
+
else if (matchesKey(data, "pageDown"))
|
|
168
|
+
this.detailScroll += this.contentViewportRows();
|
|
169
|
+
else if (matchesKey(data, "left")) this.backDetail();
|
|
103
170
|
this.tui.requestRender();
|
|
104
171
|
return;
|
|
105
172
|
}
|
|
106
173
|
if (this.focus === "runs") {
|
|
107
|
-
if (matchesKey(data, "
|
|
174
|
+
if (matchesKey(data, "left")) this.cycleRun(runs, -1);
|
|
175
|
+
else if (matchesKey(data, "right")) this.cycleRun(runs, 1);
|
|
176
|
+
else if (matchesKey(data, "down")) this.focus = "tabs";
|
|
108
177
|
else if (matchesKey(data, "return") && runs.length > 0) this.openRunMenu();
|
|
109
178
|
} else if (this.focus === "tabs") {
|
|
110
179
|
if (matchesKey(data, "left") || matchesKey(data, "right")) {
|
|
111
|
-
|
|
180
|
+
const current = INSPECTOR_TABS.indexOf(this.tab);
|
|
181
|
+
const direction = matchesKey(data, "right") ? 1 : -1;
|
|
182
|
+
this.tab = INSPECTOR_TABS[
|
|
183
|
+
(current + direction + INSPECTOR_TABS.length) % INSPECTOR_TABS.length
|
|
184
|
+
]!;
|
|
112
185
|
this.filterControlIndex = 0;
|
|
186
|
+
this.recipeScroll = 0;
|
|
113
187
|
this.rowIndex = 0;
|
|
114
188
|
} else if (matchesKey(data, "up")) this.focus = "runs";
|
|
189
|
+
else if (
|
|
190
|
+
(matchesKey(data, "down") || matchesKey(data, "return")) &&
|
|
191
|
+
this.tab === "recipe"
|
|
192
|
+
) this.focus = "recipe";
|
|
115
193
|
else if (matchesKey(data, "down") && this.listItemCount() > 0)
|
|
116
194
|
this.focus = "list";
|
|
117
195
|
else if (matchesKey(data, "return")) this.openFilterMenu();
|
|
118
196
|
} else if (this.focus === "list") {
|
|
119
197
|
const count = this.tab === "communications" ? this.communicationPreviews().length : this.turnItems().length;
|
|
120
|
-
if (matchesKey(data, "
|
|
198
|
+
if (matchesKey(data, "left")) this.focus = "tabs";
|
|
199
|
+
else if (matchesKey(data, "pageUp"))
|
|
200
|
+
this.rowIndex = Math.max(0, this.rowIndex - this.contentViewportRows());
|
|
201
|
+
else if (matchesKey(data, "pageDown"))
|
|
202
|
+
this.rowIndex = Math.min(Math.max(0, count - 1), this.rowIndex + this.contentViewportRows());
|
|
203
|
+
else if (matchesKey(data, "up")) {
|
|
121
204
|
if (this.rowIndex === 0) this.focus = "tabs";
|
|
122
205
|
else this.rowIndex -= 1;
|
|
123
206
|
} else if (matchesKey(data, "down"))
|
|
@@ -138,8 +221,16 @@ export class ActorInspectorOverlay {
|
|
|
138
221
|
let selector = this.focus === "select"
|
|
139
222
|
? this.menuLevel === "run"
|
|
140
223
|
? this.renderMenuBox(
|
|
141
|
-
this.runs().map(
|
|
224
|
+
this.runs().map(
|
|
225
|
+
(run, index, runs) =>
|
|
226
|
+
`#${runs.length - index} ${run.run} ${run.status}`,
|
|
227
|
+
),
|
|
142
228
|
this.selectorIndex,
|
|
229
|
+
undefined,
|
|
230
|
+
undefined,
|
|
231
|
+
false,
|
|
232
|
+
false,
|
|
233
|
+
this.contentViewportRows(),
|
|
143
234
|
)
|
|
144
235
|
: this.renderFilterMenus(innerWidth)
|
|
145
236
|
: [];
|
|
@@ -170,16 +261,33 @@ export class ActorInspectorOverlay {
|
|
|
170
261
|
);
|
|
171
262
|
} else lines.push(this.border("├", "", "┤", innerWidth));
|
|
172
263
|
this.contentStripeIndices = [];
|
|
173
|
-
const
|
|
174
|
-
? this.
|
|
175
|
-
: this.
|
|
176
|
-
? this.
|
|
177
|
-
: this.
|
|
178
|
-
|
|
264
|
+
const baseContent = this.killConfirmation
|
|
265
|
+
? this.renderKillConfirmation()
|
|
266
|
+
: this.detailOpen
|
|
267
|
+
? this.renderDetail(innerWidth)
|
|
268
|
+
: this.selectedRun
|
|
269
|
+
? this.renderTimeline(innerWidth)
|
|
270
|
+
: this.renderRunSelector(innerWidth);
|
|
271
|
+
const content = this.feedback && !this.killConfirmation
|
|
272
|
+
? [
|
|
273
|
+
this.theme.fg(
|
|
274
|
+
this.feedback.ok
|
|
275
|
+
? "success"
|
|
276
|
+
: /^Kill (failed|rejected):/.test(this.feedback.message)
|
|
277
|
+
? "error"
|
|
278
|
+
: "warning",
|
|
279
|
+
` ${this.feedback.message}`,
|
|
280
|
+
),
|
|
281
|
+
...baseContent.slice(0, Math.max(0, this.contentViewportRows() - 1)),
|
|
282
|
+
]
|
|
283
|
+
: baseContent;
|
|
284
|
+
const contentViewportRows = this.contentViewportRows();
|
|
179
285
|
const selectorRows = selector.slice(1);
|
|
180
|
-
for (let index = 0; index < Math.max(content.length,
|
|
286
|
+
for (let index = 0; index < Math.max(content.length, contentViewportRows); index += 1) {
|
|
181
287
|
const base = content[index] ?? "";
|
|
182
|
-
const stripeIndex =
|
|
288
|
+
const stripeIndex = index < content.length
|
|
289
|
+
? this.contentStripeIndices[index] ?? index
|
|
290
|
+
: 0;
|
|
183
291
|
const popup = selectorRows[index];
|
|
184
292
|
if (!popup) {
|
|
185
293
|
lines.push(this.stripedRow(base, innerWidth, stripeIndex));
|
|
@@ -223,7 +331,103 @@ export class ActorInspectorOverlay {
|
|
|
223
331
|
if (!this.selectedRun) this.selectedRun = runs[0]?.run;
|
|
224
332
|
}
|
|
225
333
|
|
|
334
|
+
private contentViewportRows(): number {
|
|
335
|
+
const overlayRows = Math.floor(this.tui.terminal.rows * 0.94);
|
|
336
|
+
return Math.max(4, Math.min(16, overlayRows - 7));
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
private selectRun(run: string, index: number): void {
|
|
340
|
+
this.selectedRun = run;
|
|
341
|
+
this.turnCache = undefined;
|
|
342
|
+
this.runIndex = index;
|
|
343
|
+
this.recipeScroll = 0;
|
|
344
|
+
this.rowIndex = 0;
|
|
345
|
+
this.subagentIndex = 0;
|
|
346
|
+
this.communicationFrom = "all";
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
private cycleRun(
|
|
350
|
+
runs: Inspector.ActorInspectorRunItem[],
|
|
351
|
+
direction: -1 | 1,
|
|
352
|
+
): void {
|
|
353
|
+
if (runs.length === 0) return;
|
|
354
|
+
const currentIndex = Math.max(
|
|
355
|
+
0,
|
|
356
|
+
runs.findIndex((run) => run.run === this.selectedRun),
|
|
357
|
+
);
|
|
358
|
+
const nextIndex = (currentIndex + direction + runs.length) % runs.length;
|
|
359
|
+
const next = runs[nextIndex];
|
|
360
|
+
if (next) this.selectRun(next.run, nextIndex);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
private requestKill(runs: Inspector.ActorInspectorRunItem[]): void {
|
|
364
|
+
this.feedback = undefined;
|
|
365
|
+
if (this.focus !== "runs") {
|
|
366
|
+
this.feedback = { ok: false, message: "Focus Run before using Kill." };
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
const run = runs.find((item) => item.run === this.selectedRun);
|
|
370
|
+
if (!run) {
|
|
371
|
+
this.feedback = { ok: false, message: "Kill unavailable: no owned run." };
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
if (!this.killRun) {
|
|
375
|
+
this.feedback = { ok: false, message: "Kill unavailable in this Inspector." };
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
if (run.status !== "running") {
|
|
379
|
+
this.feedback = {
|
|
380
|
+
ok: false,
|
|
381
|
+
message: `Kill unavailable: run is ${run.status}.`,
|
|
382
|
+
};
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
if (!run.runInstanceId) {
|
|
386
|
+
this.feedback = {
|
|
387
|
+
ok: false,
|
|
388
|
+
message: "Kill unavailable: run generation unavailable.",
|
|
389
|
+
};
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
this.killConfirmation = {
|
|
393
|
+
run: run.run,
|
|
394
|
+
runInstanceId: run.runInstanceId,
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
private confirmKill(): void {
|
|
399
|
+
const confirmation = this.killConfirmation;
|
|
400
|
+
this.killConfirmation = undefined;
|
|
401
|
+
if (!confirmation || !this.killRun) {
|
|
402
|
+
this.feedback = { ok: false, message: "Kill unavailable in this Inspector." };
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
try {
|
|
406
|
+
this.feedback = this.killRun(
|
|
407
|
+
confirmation.run,
|
|
408
|
+
confirmation.runInstanceId,
|
|
409
|
+
);
|
|
410
|
+
} catch (error) {
|
|
411
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
412
|
+
this.feedback = {
|
|
413
|
+
ok: false,
|
|
414
|
+
message: `Kill failed: ${message.replaceAll(/\s+/g, " ").slice(0, 160)}.`,
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
private renderKillConfirmation(): string[] {
|
|
420
|
+
return [
|
|
421
|
+
this.theme.fg(
|
|
422
|
+
"error",
|
|
423
|
+
` Kill running actor run:${this.killConfirmation?.run}?`,
|
|
424
|
+
),
|
|
425
|
+
this.theme.fg("muted", " This sends canonical control.kill and cannot be undone."),
|
|
426
|
+
];
|
|
427
|
+
}
|
|
428
|
+
|
|
226
429
|
private listItemCount(): number {
|
|
430
|
+
if (this.tab === "recipe") return 0;
|
|
227
431
|
return this.tab === "communications"
|
|
228
432
|
? this.communicationPreviews().length
|
|
229
433
|
: this.turnItems().length;
|
|
@@ -232,9 +436,13 @@ export class ActorInspectorOverlay {
|
|
|
232
436
|
private selectorAnchor(): number {
|
|
233
437
|
if (this.menuLevel === "run") {
|
|
234
438
|
const runControl = stripVTControlCharacters(this.renderRunControl());
|
|
235
|
-
return Math.max(0, runControl.indexOf("Run:") -
|
|
439
|
+
return Math.max(0, runControl.indexOf("Run:") - 2);
|
|
236
440
|
}
|
|
237
|
-
const label = this.tab === "communications"
|
|
441
|
+
const label = this.tab === "communications"
|
|
442
|
+
? "Messages"
|
|
443
|
+
: this.tab === "turns"
|
|
444
|
+
? "Turns"
|
|
445
|
+
: "Recipe";
|
|
238
446
|
const tabs = stripVTControlCharacters(this.renderTabs());
|
|
239
447
|
return Math.max(0, tabs.indexOf(label) - 2);
|
|
240
448
|
}
|
|
@@ -242,23 +450,36 @@ export class ActorInspectorOverlay {
|
|
|
242
450
|
private renderKeyHints(): string {
|
|
243
451
|
const hint = (keys: string, description: string) =>
|
|
244
452
|
`${this.theme.fg("accent", keys)}${this.theme.fg("dim", ` ${description}`)}`;
|
|
453
|
+
if (this.killConfirmation)
|
|
454
|
+
return ` ${hint("y/enter", "confirm kill")} ${hint("n/esc", "cancel")}`;
|
|
245
455
|
if (this.focus === "select")
|
|
246
456
|
return this.menuLevel === "value"
|
|
247
457
|
? ` ${hint("↑↓", "option")} ${hint("enter", "apply")} ${hint("←/esc", "back")}`
|
|
248
458
|
: ` ${hint("↑↓", "option")} ${hint("→/enter", "open")} ${hint("←/esc", "back")}`;
|
|
459
|
+
if (this.focus === "recipe")
|
|
460
|
+
return ` ${hint("↑↓/pgup/pgdn", "scroll")} ${hint("↑ at top", "tabs")} ${hint("←/esc", "tabs")}`;
|
|
249
461
|
if (this.focus === "detail")
|
|
250
|
-
return ` ${hint("
|
|
462
|
+
return ` ${hint("↑↓/pgup/pgdn", "scroll")} ${hint("←/esc", "back")}`;
|
|
251
463
|
if (this.focus === "list")
|
|
252
|
-
return ` ${hint("
|
|
464
|
+
return ` ${hint("↑↓/pgup/pgdn", "row")} ${hint("→/enter", "open")} ${hint("←", "tabs")} ${hint("esc", "close")}`;
|
|
465
|
+
if (this.focus === "runs") {
|
|
466
|
+
const run = this.runs().find((item) => item.run === this.selectedRun);
|
|
467
|
+
const killHint = this.killRun && run?.status === "running" && run.runInstanceId
|
|
468
|
+
? ` ${hint("k", "kill")}`
|
|
469
|
+
: "";
|
|
470
|
+
return ` ${hint("←→", "run")} ${hint("↓", "tabs")} ${hint("enter", "list")}${killHint} ${hint("esc", "close")}`;
|
|
471
|
+
}
|
|
253
472
|
return ` ${hint("←→", "navigate")} ${hint("↑↓", "change row")} ${hint("enter", "select")} ${hint("esc", "close")}`;
|
|
254
473
|
}
|
|
255
474
|
|
|
256
475
|
private renderRunControl(): string {
|
|
257
|
-
const
|
|
476
|
+
const runs = this.runs();
|
|
477
|
+
const runIndex = runs.findIndex((item) => item.run === this.selectedRun);
|
|
478
|
+
const run = runs[runIndex];
|
|
258
479
|
const focused = this.focus === "runs";
|
|
259
480
|
const active = focused || (this.focus === "select" && this.menuLevel === "run");
|
|
260
|
-
const prefix = active ? this.theme.fg("accent", "
|
|
261
|
-
const suffix = active ? this.theme.fg("accent", "
|
|
481
|
+
const prefix = active ? this.theme.fg("accent", " ← ") : " ";
|
|
482
|
+
const suffix = active ? this.theme.fg("accent", " → ") : " ";
|
|
262
483
|
if (!run) {
|
|
263
484
|
const empty = this.theme.fg("muted", `${prefix}Run: none${suffix}`);
|
|
264
485
|
return focused ? this.theme.bg("selectedBg", empty) : empty;
|
|
@@ -270,7 +491,8 @@ export class ActorInspectorOverlay {
|
|
|
270
491
|
: run.status === "done"
|
|
271
492
|
? "success"
|
|
272
493
|
: "muted";
|
|
273
|
-
const
|
|
494
|
+
const sequence = runs.length - runIndex;
|
|
495
|
+
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}`;
|
|
274
496
|
return focused ? this.theme.bg("selectedBg", value) : value;
|
|
275
497
|
}
|
|
276
498
|
|
|
@@ -297,19 +519,19 @@ export class ActorInspectorOverlay {
|
|
|
297
519
|
};
|
|
298
520
|
const messageFilters = this.activeMessageFilters();
|
|
299
521
|
const subagent = this.subagents()[this.subagentIndex] ?? "all";
|
|
300
|
-
const messagesLabel = `Messages${messageFilters.length > 0 ? `
|
|
301
|
-
const turnsLabel = `Turns${subagent === "all" ? "" : `
|
|
302
|
-
return `${tab("communications", messagesLabel)} ${tab("turns", turnsLabel)}`;
|
|
522
|
+
const messagesLabel = `Messages${messageFilters.length > 0 ? ` (${messageFilters.join(", ")})` : ""}`;
|
|
523
|
+
const turnsLabel = `Turns${subagent === "all" ? "" : ` (${subagent})`}`;
|
|
524
|
+
return `${tab("recipe", "Recipe")} ${tab("communications", messagesLabel)} ${tab("turns", turnsLabel)}`;
|
|
303
525
|
}
|
|
304
526
|
|
|
305
527
|
private renderRunSelector(width: number): string[] {
|
|
306
528
|
const runs = this.runs();
|
|
307
529
|
if (runs.length === 0) return [this.theme.fg("muted", " No owned actor runs")];
|
|
308
530
|
this.runIndex = Math.min(this.runIndex, runs.length - 1);
|
|
309
|
-
return runs.slice(0,
|
|
531
|
+
return runs.slice(0, this.contentViewportRows()).map((run, index) => {
|
|
310
532
|
const selected = index === this.runIndex;
|
|
311
533
|
const prefix = selected ? this.theme.fg("accent", " ▶ ") : " ";
|
|
312
|
-
const label = `${prefix}run:${run.run}`;
|
|
534
|
+
const label = `${prefix}#${runs.length - index} run:${run.run}`;
|
|
313
535
|
const status = ` ${run.status}`;
|
|
314
536
|
return selected
|
|
315
537
|
? this.theme.fg("accent", `${label}${status}`)
|
|
@@ -324,6 +546,7 @@ export class ActorInspectorOverlay {
|
|
|
324
546
|
this.selectedRun = undefined;
|
|
325
547
|
return [this.theme.fg("warning", " Selected run is no longer owned by this session")];
|
|
326
548
|
}
|
|
549
|
+
if (this.tab === "recipe") return this.renderRecipe(width);
|
|
327
550
|
const rows =
|
|
328
551
|
this.tab === "communications"
|
|
329
552
|
? this.communicationRows()
|
|
@@ -337,7 +560,7 @@ export class ActorInspectorOverlay {
|
|
|
337
560
|
this.theme.fg(
|
|
338
561
|
"muted",
|
|
339
562
|
filtered
|
|
340
|
-
? " No results for the active filters
|
|
563
|
+
? " No results for the active filters. Enter on the tab to adjust."
|
|
341
564
|
: this.tab === "communications"
|
|
342
565
|
? " No messages in this run"
|
|
343
566
|
: " No persisted turns in this run",
|
|
@@ -345,12 +568,23 @@ export class ActorInspectorOverlay {
|
|
|
345
568
|
];
|
|
346
569
|
}
|
|
347
570
|
this.rowIndex = Math.min(this.rowIndex, rows.length - 1);
|
|
571
|
+
const viewportRows = this.contentViewportRows();
|
|
348
572
|
const start = Math.max(
|
|
349
573
|
0,
|
|
350
|
-
Math.min(
|
|
574
|
+
Math.min(
|
|
575
|
+
this.rowIndex - Math.floor(viewportRows / 2),
|
|
576
|
+
Math.max(0, rows.length - viewportRows),
|
|
577
|
+
),
|
|
578
|
+
);
|
|
579
|
+
const stripeIndices = this.tab === "communications"
|
|
580
|
+
? this.communicationPreviews().map((preview, index) =>
|
|
581
|
+
Math.max(0, (preview.sequence ?? index + 1) - 1),
|
|
582
|
+
)
|
|
583
|
+
: this.turnItems().map((turn) => Math.max(0, turn.index - 1));
|
|
584
|
+
const visibleRows = rows.slice(start, start + viewportRows);
|
|
585
|
+
this.contentStripeIndices = visibleRows.map(
|
|
586
|
+
(_, offset) => stripeIndices[start + offset] ?? start + offset,
|
|
351
587
|
);
|
|
352
|
-
const visibleRows = rows.slice(start, start + 15);
|
|
353
|
-
this.contentStripeIndices = visibleRows.map((_, offset) => start + offset);
|
|
354
588
|
return visibleRows.map((row, offset) => {
|
|
355
589
|
const index = start + offset;
|
|
356
590
|
const selected = this.focus === "list" && index === this.rowIndex;
|
|
@@ -360,6 +594,51 @@ export class ActorInspectorOverlay {
|
|
|
360
594
|
});
|
|
361
595
|
}
|
|
362
596
|
|
|
597
|
+
private renderRecipe(width: number): string[] {
|
|
598
|
+
if (!this.selectedRun) return [];
|
|
599
|
+
const recipe = Inspector.readActorInspectorRecipe(
|
|
600
|
+
path.join(this.stateRoot, this.selectedRun),
|
|
601
|
+
);
|
|
602
|
+
const logicalLines: DetailLogicalLine[] = [
|
|
603
|
+
...this.detailSection(
|
|
604
|
+
"Identity",
|
|
605
|
+
this.readableValueLines(recipe.identity, " "),
|
|
606
|
+
),
|
|
607
|
+
...(recipe.definition
|
|
608
|
+
? this.detailSection(
|
|
609
|
+
"Recipe",
|
|
610
|
+
this.readableValueLines(recipe.definition, " "),
|
|
611
|
+
)
|
|
612
|
+
: []),
|
|
613
|
+
...this.detailSection(
|
|
614
|
+
"Launch",
|
|
615
|
+
this.readableValueLines(recipe.launch, " "),
|
|
616
|
+
),
|
|
617
|
+
...(recipe.composition.length > 0
|
|
618
|
+
? this.detailSection(
|
|
619
|
+
"Composition",
|
|
620
|
+
this.readableValueLines(recipe.composition, " "),
|
|
621
|
+
)
|
|
622
|
+
: []),
|
|
623
|
+
...this.detailSection("Diagnostics", recipe.diagnostics),
|
|
624
|
+
];
|
|
625
|
+
const wrapped = this.wrapDetailLines(
|
|
626
|
+
logicalLines.length > 0 ? logicalLines : [" No persisted recipe evidence"],
|
|
627
|
+
width,
|
|
628
|
+
);
|
|
629
|
+
const viewportRows = this.contentViewportRows();
|
|
630
|
+
const maxStart = Math.max(0, wrapped.lines.length - viewportRows);
|
|
631
|
+
this.recipeScroll = Math.min(this.recipeScroll, maxStart);
|
|
632
|
+
this.contentStripeIndices = wrapped.stripeIndices.slice(
|
|
633
|
+
this.recipeScroll,
|
|
634
|
+
this.recipeScroll + viewportRows,
|
|
635
|
+
);
|
|
636
|
+
return wrapped.lines.slice(
|
|
637
|
+
this.recipeScroll,
|
|
638
|
+
this.recipeScroll + viewportRows,
|
|
639
|
+
);
|
|
640
|
+
}
|
|
641
|
+
|
|
363
642
|
private communicationPreviews(): Inspector.ActorInspectorPreview[] {
|
|
364
643
|
if (!this.selectedRun) return [];
|
|
365
644
|
const channels =
|
|
@@ -373,12 +652,14 @@ export class ActorInspectorOverlay {
|
|
|
373
652
|
readKeys: this.readKeys,
|
|
374
653
|
run: this.selectedRun,
|
|
375
654
|
unreadOnly: this.communicationUnread,
|
|
376
|
-
})
|
|
377
|
-
(
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
this.
|
|
381
|
-
|
|
655
|
+
})
|
|
656
|
+
.filter(
|
|
657
|
+
(preview) =>
|
|
658
|
+
this.communicationFrom === "all" ||
|
|
659
|
+
this.communicationActorLabel(preview.from_display ?? preview.from) ===
|
|
660
|
+
this.communicationFrom,
|
|
661
|
+
)
|
|
662
|
+
.reverse();
|
|
382
663
|
}
|
|
383
664
|
|
|
384
665
|
private communicationActorLabel(actor?: string): string {
|
|
@@ -403,7 +684,7 @@ export class ActorInspectorOverlay {
|
|
|
403
684
|
const actor = preview.from_display ?? preview.from ?? "unknown";
|
|
404
685
|
const target = preview.channel === "room" ? "#all" : preview.to;
|
|
405
686
|
const text = preview.summary ?? preview.body_preview ?? "(no body)";
|
|
406
|
-
return
|
|
687
|
+
return `#${preview.sequence ?? "?"} ${preview.needs_response ? "! " : ""}${actor} → ${target} ${preview.type} ${text}`;
|
|
407
688
|
});
|
|
408
689
|
}
|
|
409
690
|
|
|
@@ -423,7 +704,7 @@ export class ActorInspectorOverlay {
|
|
|
423
704
|
["inbox", preview.inbox_status],
|
|
424
705
|
["timestamp", preview.timestamp],
|
|
425
706
|
];
|
|
426
|
-
|
|
707
|
+
const lines = fields
|
|
427
708
|
.filter(([, value]) => value !== undefined)
|
|
428
709
|
.map(([label, value]) =>
|
|
429
710
|
truncateToWidth(
|
|
@@ -432,74 +713,330 @@ export class ActorInspectorOverlay {
|
|
|
432
713
|
"…",
|
|
433
714
|
),
|
|
434
715
|
);
|
|
716
|
+
const viewportRows = this.contentViewportRows();
|
|
717
|
+
const maxStart = Math.max(0, lines.length - viewportRows);
|
|
718
|
+
this.detailScroll = Math.min(this.detailScroll, maxStart);
|
|
719
|
+
return lines.slice(this.detailScroll, this.detailScroll + viewportRows);
|
|
435
720
|
}
|
|
436
721
|
const turn = this.detailTurn;
|
|
437
722
|
if (!turn) return [this.theme.fg("warning", " Turn row is no longer available")];
|
|
438
|
-
const
|
|
439
|
-
|
|
440
|
-
const
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
723
|
+
const logicalLines = this.turnEvidenceLines(turn);
|
|
724
|
+
const wrapped = this.wrapDetailLines(logicalLines, width);
|
|
725
|
+
const viewportRows = this.contentViewportRows();
|
|
726
|
+
const maxStart = Math.max(0, wrapped.lines.length - viewportRows);
|
|
727
|
+
this.detailScroll = Math.min(this.detailScroll, maxStart);
|
|
728
|
+
this.contentStripeIndices = wrapped.stripeIndices.slice(
|
|
729
|
+
this.detailScroll,
|
|
730
|
+
this.detailScroll + viewportRows,
|
|
731
|
+
);
|
|
732
|
+
return wrapped.lines.slice(
|
|
733
|
+
this.detailScroll,
|
|
734
|
+
this.detailScroll + viewportRows,
|
|
735
|
+
);
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
private wrapDetailLines(
|
|
739
|
+
lines: DetailLogicalLine[],
|
|
740
|
+
width: number,
|
|
741
|
+
): { lines: string[]; stripeIndices: number[] } {
|
|
742
|
+
const wrappedLines: string[] = [];
|
|
743
|
+
const stripeIndices: number[] = [];
|
|
744
|
+
let sectionIndex = 0;
|
|
745
|
+
for (const line of lines) {
|
|
746
|
+
if (typeof line !== "string") {
|
|
747
|
+
sectionIndex += 1;
|
|
748
|
+
continue;
|
|
749
|
+
}
|
|
750
|
+
const physicalLines = line.replaceAll("\t", " ").split(/\r?\n/);
|
|
751
|
+
for (const physicalLine of physicalLines) {
|
|
752
|
+
if (!physicalLine.trim()) continue;
|
|
753
|
+
const leading = physicalLine.match(/^ */)?.[0] ?? "";
|
|
754
|
+
const content = physicalLine.slice(leading.length);
|
|
755
|
+
const contentWidth = Math.max(1, width - visibleWidth(leading));
|
|
756
|
+
const labeled = /^(\s*(?:-\s+)?[^:]+:\s+)(\S.*)$/u.exec(physicalLine);
|
|
757
|
+
let wrapped: string[];
|
|
758
|
+
if (
|
|
759
|
+
visibleWidth(physicalLine) > width &&
|
|
760
|
+
labeled &&
|
|
761
|
+
visibleWidth(labeled[1]) < width
|
|
762
|
+
) {
|
|
763
|
+
const prefix = labeled[1];
|
|
764
|
+
const value = labeled[2];
|
|
765
|
+
const firstWidth = Math.max(1, width - visibleWidth(prefix));
|
|
766
|
+
const first = this.wrapPreferred(value, firstWidth)[0] ??
|
|
767
|
+
this.takeVisiblePrefix(value, firstWidth);
|
|
768
|
+
const remainder = value.slice(first.length).trimStart();
|
|
769
|
+
const continuation = `${leading} `;
|
|
770
|
+
const continuationWidth = Math.max(
|
|
771
|
+
1,
|
|
772
|
+
width - visibleWidth(continuation),
|
|
773
|
+
);
|
|
774
|
+
wrapped = [
|
|
775
|
+
`${prefix}${first}`,
|
|
776
|
+
...(remainder
|
|
777
|
+
? this.wrapPreferred(remainder, continuationWidth).map(
|
|
778
|
+
(visualLine) => `${continuation}${visualLine}`,
|
|
779
|
+
)
|
|
780
|
+
: []),
|
|
781
|
+
];
|
|
782
|
+
} else {
|
|
783
|
+
wrapped = visibleWidth(physicalLine) <= width
|
|
784
|
+
? [physicalLine]
|
|
785
|
+
: this.wrapPreferred(content, contentWidth).map(
|
|
786
|
+
(visualLine) => `${leading}${visualLine}`,
|
|
787
|
+
);
|
|
788
|
+
}
|
|
789
|
+
for (const visualLine of wrapped) {
|
|
790
|
+
if (!visualLine.trim()) continue;
|
|
791
|
+
wrappedLines.push(visualLine);
|
|
792
|
+
stripeIndices.push(sectionIndex);
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
return { lines: wrappedLines, stripeIndices };
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
private wrapPreferred(content: string, width: number): string[] {
|
|
800
|
+
const lines: string[] = [];
|
|
801
|
+
let remaining = stripVTControlCharacters(content).trimEnd();
|
|
802
|
+
while (remaining && visibleWidth(remaining) > width) {
|
|
803
|
+
const candidate = this.takeVisiblePrefix(remaining, width);
|
|
804
|
+
let splitAt = -1;
|
|
805
|
+
let trimBoundary = false;
|
|
806
|
+
for (let index = 1; index < candidate.length; index += 1) {
|
|
807
|
+
if (/\s/u.test(candidate[index]!)) {
|
|
808
|
+
splitAt = index + 1;
|
|
809
|
+
trimBoundary = true;
|
|
810
|
+
} else if (/[-+=/_,.:]/u.test(candidate[index]!)) {
|
|
811
|
+
splitAt = index;
|
|
812
|
+
trimBoundary = false;
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
if (splitAt <= 0) splitAt = candidate.length;
|
|
816
|
+
const head = remaining.slice(0, splitAt).trimEnd();
|
|
817
|
+
if (!head) {
|
|
818
|
+
lines.push(candidate);
|
|
819
|
+
remaining = remaining.slice(candidate.length);
|
|
820
|
+
continue;
|
|
821
|
+
}
|
|
822
|
+
lines.push(head);
|
|
823
|
+
remaining = remaining.slice(splitAt);
|
|
824
|
+
if (trimBoundary) remaining = remaining.trimStart();
|
|
825
|
+
}
|
|
826
|
+
if (remaining) lines.push(remaining);
|
|
827
|
+
return lines;
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
private detailSection(
|
|
831
|
+
title: string,
|
|
832
|
+
content: string[],
|
|
833
|
+
): DetailLogicalLine[] {
|
|
834
|
+
if (content.length === 0) return [];
|
|
835
|
+
return [
|
|
836
|
+
` ${title}`,
|
|
837
|
+
...content.flatMap((line) =>
|
|
838
|
+
line
|
|
839
|
+
.split(/\r?\n/)
|
|
840
|
+
.filter((physicalLine) => physicalLine.trim().length > 0)
|
|
841
|
+
.map((physicalLine) => ` ${physicalLine}`),
|
|
842
|
+
),
|
|
843
|
+
DETAIL_SECTION_BREAK,
|
|
457
844
|
];
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
private readableValueLines(
|
|
848
|
+
value: unknown,
|
|
849
|
+
indent = "",
|
|
850
|
+
depth = 0,
|
|
851
|
+
): string[] {
|
|
852
|
+
if (depth > 8) return [`${indent}[nested value]`];
|
|
853
|
+
if (typeof value === "string") {
|
|
854
|
+
const trimmed = value.trim();
|
|
855
|
+
if (/^[{[]/.test(trimmed)) {
|
|
856
|
+
try {
|
|
857
|
+
return this.readableValueLines(JSON.parse(trimmed), indent, depth + 1);
|
|
858
|
+
} catch {
|
|
859
|
+
// Keep non-JSON text as written.
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
return value.split(/\r?\n/).map((line) => `${indent}${line}`);
|
|
863
|
+
}
|
|
864
|
+
if (value === null || value === undefined)
|
|
865
|
+
return [`${indent}${String(value)}`];
|
|
866
|
+
if (typeof value !== "object") return [`${indent}${String(value)}`];
|
|
867
|
+
if (Array.isArray(value)) {
|
|
868
|
+
if (value.length === 0) return [`${indent}(empty)`];
|
|
869
|
+
return value.flatMap((item) => {
|
|
870
|
+
if (
|
|
871
|
+
item &&
|
|
872
|
+
typeof item === "object" &&
|
|
873
|
+
(item as { type?: unknown }).type === "text" &&
|
|
874
|
+
typeof (item as { text?: unknown }).text === "string"
|
|
875
|
+
) {
|
|
876
|
+
return this.readableValueLines(
|
|
877
|
+
(item as { text: string }).text,
|
|
878
|
+
indent,
|
|
879
|
+
depth + 1,
|
|
880
|
+
);
|
|
881
|
+
}
|
|
882
|
+
if (!item || typeof item !== "object")
|
|
883
|
+
return [`${indent}- ${String(item)}`];
|
|
884
|
+
const [first = "", ...rest] = this.readableValueLines(
|
|
885
|
+
item,
|
|
886
|
+
`${indent} `,
|
|
887
|
+
depth + 1,
|
|
888
|
+
);
|
|
889
|
+
return [
|
|
890
|
+
`${indent}- ${first.trimStart()}`,
|
|
891
|
+
...rest,
|
|
892
|
+
];
|
|
893
|
+
});
|
|
894
|
+
}
|
|
895
|
+
const entries = Object.entries(value as Record<string, unknown>);
|
|
896
|
+
if (entries.length === 0) return [`${indent}(empty)`];
|
|
897
|
+
return entries.flatMap(([key, item]) => {
|
|
898
|
+
if (!item || typeof item !== "object") {
|
|
899
|
+
const scalar = typeof item === "string" ? item : String(item);
|
|
900
|
+
const [first = "", ...rest] = scalar.split(/\r?\n/);
|
|
901
|
+
return [
|
|
902
|
+
`${indent}${key}: ${first}`,
|
|
903
|
+
...rest.map((line) => `${indent} ${line}`),
|
|
904
|
+
];
|
|
905
|
+
}
|
|
906
|
+
return [
|
|
907
|
+
`${indent}${key}:`,
|
|
908
|
+
...this.readableValueLines(item, `${indent} `, depth + 1),
|
|
909
|
+
];
|
|
910
|
+
});
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
private turnEvidenceLines(
|
|
914
|
+
turn: Inspector.ActorInspectorTurnItem,
|
|
915
|
+
): DetailLogicalLine[] {
|
|
916
|
+
const lines: DetailLogicalLine[] = [];
|
|
917
|
+
const commandNumber = /^command-(\d+)$/.exec(turn.commandId)?.[1];
|
|
918
|
+
const subagent = commandNumber
|
|
919
|
+
? String(Number.parseInt(commandNumber, 10))
|
|
920
|
+
: turn.commandId;
|
|
921
|
+
const stage =
|
|
922
|
+
turn.stage && turn.stage !== "subagent" && turn.stage !== "command"
|
|
923
|
+
? ` (${turn.stage})`
|
|
924
|
+
: "";
|
|
925
|
+
lines.push(` Subagent ${subagent}${stage}`, DETAIL_SECTION_BREAK);
|
|
926
|
+
if (turn.userText)
|
|
927
|
+
lines.push(
|
|
928
|
+
...this.detailSection("User", [this.readablePromptText(turn.userText)]),
|
|
929
|
+
);
|
|
930
|
+
if (turn.thinking)
|
|
931
|
+
lines.push(...this.detailSection("Thinking", [turn.thinking]));
|
|
932
|
+
if (turn.assistantText)
|
|
933
|
+
lines.push(...this.detailSection("Assistant", [turn.assistantText]));
|
|
458
934
|
for (const [index, tool] of turn.toolCalls.entries()) {
|
|
459
|
-
|
|
460
|
-
|
|
935
|
+
const content = [
|
|
936
|
+
...(tool.arguments === undefined
|
|
937
|
+
? []
|
|
938
|
+
: ["Input", ...this.readableValueLines(tool.arguments, " ")]),
|
|
939
|
+
...(tool.result === undefined
|
|
940
|
+
? []
|
|
941
|
+
: ["Result", ...this.readableValueLines(tool.result, " ")]),
|
|
942
|
+
`call id: ${tool.id}`,
|
|
943
|
+
];
|
|
944
|
+
lines.push(
|
|
945
|
+
...this.detailSection(
|
|
946
|
+
`Tool ${index + 1} (${tool.name}${tool.resultError ? ", Error" : ""})`,
|
|
947
|
+
content,
|
|
948
|
+
),
|
|
461
949
|
);
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
950
|
+
}
|
|
951
|
+
const execution = [
|
|
952
|
+
...(!turn.thinking ? ["thinking: not persisted"] : []),
|
|
953
|
+
...(turn.model ? [`model: ${turn.model}`] : []),
|
|
954
|
+
...(turn.stopReason ? [`stop reason: ${turn.stopReason}`] : []),
|
|
955
|
+
...(turn.error ? [`error: ${turn.error}`] : []),
|
|
956
|
+
...(turn.usage === undefined
|
|
957
|
+
? []
|
|
958
|
+
: ["Usage", ...this.readableValueLines(turn.usage, " ")]),
|
|
959
|
+
...(turn.unmatchedToolResults > 0
|
|
960
|
+
? [`unmatched tool results: ${turn.unmatchedToolResults}`]
|
|
961
|
+
: []),
|
|
962
|
+
];
|
|
963
|
+
if (execution.length > 0)
|
|
964
|
+
lines.push(...this.detailSection("Execution", execution));
|
|
965
|
+
if (turn.diagnostics.length > 0)
|
|
966
|
+
lines.push(...this.detailSection("Diagnostics", turn.diagnostics));
|
|
967
|
+
const provenance = [
|
|
968
|
+
"Session",
|
|
969
|
+
` ${turn.sessionFile}`,
|
|
970
|
+
...(turn.sessionTruncated ? [" turn list truncated"] : []),
|
|
971
|
+
...(turn.promptFile
|
|
972
|
+
? [
|
|
973
|
+
"Prompt",
|
|
974
|
+
` ${turn.promptFile}${turn.promptBytes ? ` (${turn.promptBytes} bytes)` : ""}`,
|
|
975
|
+
]
|
|
976
|
+
: []),
|
|
977
|
+
];
|
|
978
|
+
lines.push(...this.detailSection("Provenance", provenance));
|
|
979
|
+
return lines.length > 0 ? lines : [" No persisted turn evidence"];
|
|
476
980
|
}
|
|
477
981
|
|
|
478
|
-
private
|
|
982
|
+
private readablePromptText(text: string): string {
|
|
983
|
+
const wrapped = /^\s*<file\b[^>]*>\s*([\s\S]*?)\s*<\/file>\s*$/i.exec(text);
|
|
984
|
+
return wrapped?.[1]?.trim() || text;
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
private allTurnItems(): Inspector.ActorInspectorTurnItem[] {
|
|
479
988
|
if (!this.selectedRun) return [];
|
|
989
|
+
if (this.turnCache?.run === this.selectedRun) return this.turnCache.turns;
|
|
990
|
+
const turns = this.readTurns(path.join(this.stateRoot, this.selectedRun));
|
|
991
|
+
this.turnCache = { run: this.selectedRun, turns };
|
|
992
|
+
return turns;
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
private turnItems(): Inspector.ActorInspectorTurnItem[] {
|
|
480
996
|
const selectedSubagent = this.subagents()[this.subagentIndex];
|
|
481
|
-
return
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
997
|
+
return this.allTurnItems()
|
|
998
|
+
.filter(
|
|
999
|
+
(turn) =>
|
|
1000
|
+
!selectedSubagent ||
|
|
1001
|
+
selectedSubagent === "all" ||
|
|
1002
|
+
turn.commandId === selectedSubagent,
|
|
1003
|
+
)
|
|
1004
|
+
.reverse();
|
|
489
1005
|
}
|
|
490
1006
|
|
|
491
1007
|
private turnRows(): string[] {
|
|
492
|
-
return this.turnItems().map(
|
|
493
|
-
(turn)
|
|
494
|
-
|
|
495
|
-
|
|
1008
|
+
return this.turnItems().map((turn) => {
|
|
1009
|
+
const commandNumber = /^command-(\d+)$/.exec(turn.commandId)?.[1];
|
|
1010
|
+
const subagent = commandNumber
|
|
1011
|
+
? `Subagent ${Number.parseInt(commandNumber, 10)}`
|
|
1012
|
+
: `Subagent ${turn.commandId}`;
|
|
1013
|
+
const stage =
|
|
1014
|
+
turn.stage && turn.stage !== "subagent" && turn.stage !== "command"
|
|
1015
|
+
? ` (${turn.stage})`
|
|
1016
|
+
: "";
|
|
1017
|
+
const toolNames = [...new Set(turn.toolCalls.map((tool) => tool.name))];
|
|
1018
|
+
const action = turn.toolCalls.length === 0
|
|
1019
|
+
? ""
|
|
1020
|
+
: turn.toolCalls.length === 1
|
|
1021
|
+
? ` (${toolNames[0]})`
|
|
1022
|
+
: toolNames.length === 1
|
|
1023
|
+
? ` (${toolNames[0]}, ${turn.toolCalls.length} calls)`
|
|
1024
|
+
: turn.toolCalls.length <= 2
|
|
1025
|
+
? ` (${toolNames.join(", ")})`
|
|
1026
|
+
: ` (${turn.toolCalls.length} tools)`;
|
|
1027
|
+
const failed = turn.error || turn.toolCalls.some((tool) => tool.resultError)
|
|
1028
|
+
? " (error)"
|
|
1029
|
+
: "";
|
|
1030
|
+
const preview = this.readablePromptText(
|
|
1031
|
+
turn.assistantText ?? turn.userText ?? "(no text)",
|
|
1032
|
+
).replaceAll(/\s+/g, " ").trim();
|
|
1033
|
+
return `#${turn.index} ${subagent}${stage} ${turn.model ?? "unknown model"}${action}${failed} ${preview}`;
|
|
1034
|
+
});
|
|
496
1035
|
}
|
|
497
1036
|
|
|
498
1037
|
private subagents(): string[] {
|
|
499
1038
|
if (!this.selectedRun || this.tab !== "turns") return ["all"];
|
|
500
|
-
const ids =
|
|
501
|
-
path.join(this.stateRoot, this.selectedRun),
|
|
502
|
-
).map((turn) => turn.commandId);
|
|
1039
|
+
const ids = this.allTurnItems().map((turn) => turn.commandId);
|
|
503
1040
|
return ["all", ...new Set(ids)];
|
|
504
1041
|
}
|
|
505
1042
|
|
|
@@ -548,11 +1085,10 @@ export class ActorInspectorOverlay {
|
|
|
548
1085
|
private applyMenuOption(value?: string): void {
|
|
549
1086
|
if (!value) return;
|
|
550
1087
|
if (this.menuLevel === "run") {
|
|
551
|
-
this.
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
this.communicationFrom = "all";
|
|
1088
|
+
this.selectRun(
|
|
1089
|
+
value,
|
|
1090
|
+
Math.max(0, this.runs().findIndex((run) => run.run === value)),
|
|
1091
|
+
);
|
|
556
1092
|
this.focus = "runs";
|
|
557
1093
|
return;
|
|
558
1094
|
}
|
|
@@ -594,16 +1130,37 @@ export class ActorInspectorOverlay {
|
|
|
594
1130
|
parentIndex?: number,
|
|
595
1131
|
omitLeftBorder = false,
|
|
596
1132
|
omitRightBorder = false,
|
|
1133
|
+
maxVisibleOptions = options.length,
|
|
597
1134
|
): string[] {
|
|
598
|
-
const
|
|
599
|
-
const
|
|
1135
|
+
const visibleLimit = Math.max(1, Math.min(maxVisibleOptions, options.length));
|
|
1136
|
+
const anchorIndex = Math.max(
|
|
1137
|
+
0,
|
|
1138
|
+
Math.min(
|
|
1139
|
+
options.length - 1,
|
|
1140
|
+
focusedIndex >= 0 ? focusedIndex : (parentIndex ?? 0),
|
|
1141
|
+
),
|
|
1142
|
+
);
|
|
1143
|
+
const maxStart = Math.max(0, options.length - visibleLimit);
|
|
1144
|
+
const start = Math.max(
|
|
1145
|
+
0,
|
|
1146
|
+
Math.min(anchorIndex - Math.floor(visibleLimit / 2), maxStart),
|
|
1147
|
+
);
|
|
1148
|
+
const visibleOptions = options.slice(start, start + visibleLimit);
|
|
1149
|
+
const hiddenAbove = start > 0;
|
|
1150
|
+
const hiddenBelow = start + visibleOptions.length < options.length;
|
|
1151
|
+
const contentWidth = Math.max(
|
|
1152
|
+
4,
|
|
1153
|
+
...options.map((option) => visibleWidth(option) + 4),
|
|
1154
|
+
);
|
|
1155
|
+
const border = (left: string, right: string, marker = "") =>
|
|
600
1156
|
this.theme.fg(
|
|
601
1157
|
"border",
|
|
602
|
-
`${omitLeftBorder ? "" : left}${"─".repeat(contentWidth)}${omitRightBorder ? "" : right}`,
|
|
1158
|
+
`${omitLeftBorder ? "" : left}${marker}${"─".repeat(Math.max(0, contentWidth - visibleWidth(marker)))}${omitRightBorder ? "" : right}`,
|
|
603
1159
|
);
|
|
604
1160
|
return [
|
|
605
|
-
border("╭", "╮"),
|
|
606
|
-
...
|
|
1161
|
+
border("╭", "╮", hiddenAbove ? "↑" : ""),
|
|
1162
|
+
...visibleOptions.map((option, localIndex) => {
|
|
1163
|
+
const index = start + localIndex;
|
|
607
1164
|
const focused = index === focusedIndex;
|
|
608
1165
|
const row = this.fit(`${focused ? " ▶ " : " "}${option}`, contentWidth);
|
|
609
1166
|
const colored = focused || option === current || index === parentIndex
|
|
@@ -614,7 +1171,7 @@ export class ActorInspectorOverlay {
|
|
|
614
1171
|
: colored;
|
|
615
1172
|
return `${omitLeftBorder ? "" : this.theme.fg("border", "│")}${styled}${omitRightBorder ? "" : this.theme.fg("border", "│")}`;
|
|
616
1173
|
}),
|
|
617
|
-
border("╰", "╯"),
|
|
1174
|
+
border("╰", "╯", hiddenBelow ? "↓" : ""),
|
|
618
1175
|
];
|
|
619
1176
|
}
|
|
620
1177
|
|
|
@@ -622,12 +1179,16 @@ export class ActorInspectorOverlay {
|
|
|
622
1179
|
const filters = this.filterOptions().map(
|
|
623
1180
|
(filter, index) => `${filter}: ${this.filterValue(index)}`,
|
|
624
1181
|
);
|
|
1182
|
+
const maxVisibleOptions = Math.max(1, this.contentViewportRows() - 1);
|
|
625
1183
|
const parentFocus = this.menuLevel === "filter" ? this.selectorIndex : -1;
|
|
626
1184
|
const parent = this.renderMenuBox(
|
|
627
1185
|
filters,
|
|
628
1186
|
parentFocus,
|
|
629
1187
|
undefined,
|
|
630
1188
|
this.menuLevel === "value" ? this.filterControlIndex : undefined,
|
|
1189
|
+
false,
|
|
1190
|
+
false,
|
|
1191
|
+
maxVisibleOptions,
|
|
631
1192
|
);
|
|
632
1193
|
if (this.menuLevel !== "value") return parent;
|
|
633
1194
|
const parentShared = this.renderMenuBox(
|
|
@@ -637,11 +1198,16 @@ export class ActorInspectorOverlay {
|
|
|
637
1198
|
this.filterControlIndex,
|
|
638
1199
|
false,
|
|
639
1200
|
true,
|
|
1201
|
+
maxVisibleOptions,
|
|
640
1202
|
);
|
|
641
1203
|
const child = this.renderMenuBox(
|
|
642
1204
|
this.valueOptions(),
|
|
643
1205
|
this.selectorIndex,
|
|
644
1206
|
this.currentFilterValue(),
|
|
1207
|
+
undefined,
|
|
1208
|
+
false,
|
|
1209
|
+
false,
|
|
1210
|
+
maxVisibleOptions,
|
|
645
1211
|
);
|
|
646
1212
|
return Array.from({ length: Math.max(parent.length, child.length) }, (_, index) =>
|
|
647
1213
|
child[index]
|
|
@@ -661,6 +1227,10 @@ export class ActorInspectorOverlay {
|
|
|
661
1227
|
if (this.detailOpen) this.focus = "detail";
|
|
662
1228
|
}
|
|
663
1229
|
|
|
1230
|
+
private backDetail(): void {
|
|
1231
|
+
this.closeDetail();
|
|
1232
|
+
}
|
|
1233
|
+
|
|
664
1234
|
private closeDetail(): void {
|
|
665
1235
|
this.detailOpen = false;
|
|
666
1236
|
this.detailCommunication = undefined;
|