@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
|
@@ -7,9 +7,17 @@ import * as path from "node:path";
|
|
|
7
7
|
import { stripVTControlCharacters } from "node:util";
|
|
8
8
|
import { matchesKey, truncateToWidth, visibleWidth, } from "@earendil-works/pi-tui";
|
|
9
9
|
import * as Inspector from "./inspector.js";
|
|
10
|
+
const INSPECTOR_TABS = [
|
|
11
|
+
"recipe",
|
|
12
|
+
"communications",
|
|
13
|
+
"turns",
|
|
14
|
+
];
|
|
15
|
+
const DETAIL_SECTION_BREAK = { sectionBreak: true };
|
|
10
16
|
export class ActorInspectorOverlay {
|
|
11
17
|
done;
|
|
18
|
+
killRun;
|
|
12
19
|
ownerId;
|
|
20
|
+
readTurns;
|
|
13
21
|
stateRoot;
|
|
14
22
|
theme;
|
|
15
23
|
tui;
|
|
@@ -23,22 +31,32 @@ export class ActorInspectorOverlay {
|
|
|
23
31
|
detailOpen = false;
|
|
24
32
|
detailScroll = 0;
|
|
25
33
|
detailTurn;
|
|
34
|
+
feedback;
|
|
35
|
+
killConfirmation;
|
|
26
36
|
readKeys = new Set();
|
|
27
37
|
focus = "tabs";
|
|
28
38
|
filterControlIndex = 0;
|
|
29
39
|
menuLevel = "filter";
|
|
30
40
|
selectorIndex = 0;
|
|
41
|
+
recipeScroll = 0;
|
|
31
42
|
rowIndex = 0;
|
|
32
43
|
selectedRun;
|
|
33
44
|
subagentIndex = 0;
|
|
34
|
-
tab = "
|
|
45
|
+
tab = "recipe";
|
|
46
|
+
turnCache;
|
|
35
47
|
constructor(options) {
|
|
36
48
|
this.done = options.done;
|
|
49
|
+
this.killRun = options.killRun;
|
|
37
50
|
this.ownerId = options.ownerId;
|
|
51
|
+
this.readTurns = options.readTurns ?? Inspector.readActorInspectorTurns;
|
|
38
52
|
this.stateRoot = options.stateRoot;
|
|
39
53
|
this.theme = options.theme;
|
|
40
54
|
this.tui = options.tui;
|
|
41
|
-
this.refreshTimer = setInterval(() =>
|
|
55
|
+
this.refreshTimer = setInterval(() => {
|
|
56
|
+
if (this.focus !== "list" && this.focus !== "detail")
|
|
57
|
+
this.turnCache = undefined;
|
|
58
|
+
this.tui.requestRender();
|
|
59
|
+
}, 1000);
|
|
42
60
|
this.refreshTimer.unref?.();
|
|
43
61
|
}
|
|
44
62
|
handleInput(data) {
|
|
@@ -48,6 +66,22 @@ export class ActorInspectorOverlay {
|
|
|
48
66
|
this.done();
|
|
49
67
|
return;
|
|
50
68
|
}
|
|
69
|
+
if (this.killConfirmation) {
|
|
70
|
+
if (matchesKey(data, "escape") || data.toLowerCase() === "n") {
|
|
71
|
+
this.feedback = { ok: false, message: "Kill cancelled." };
|
|
72
|
+
this.killConfirmation = undefined;
|
|
73
|
+
}
|
|
74
|
+
else if (matchesKey(data, "return") || data.toLowerCase() === "y") {
|
|
75
|
+
this.confirmKill();
|
|
76
|
+
}
|
|
77
|
+
this.tui.requestRender();
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
if (data.toLowerCase() === "k") {
|
|
81
|
+
this.requestKill(runs);
|
|
82
|
+
this.tui.requestRender();
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
51
85
|
if (matchesKey(data, "escape")) {
|
|
52
86
|
if (this.focus === "select") {
|
|
53
87
|
if (this.menuLevel === "value") {
|
|
@@ -58,7 +92,9 @@ export class ActorInspectorOverlay {
|
|
|
58
92
|
this.focus = this.menuLevel === "run" ? "runs" : "tabs";
|
|
59
93
|
}
|
|
60
94
|
else if (this.focus === "detail")
|
|
61
|
-
this.
|
|
95
|
+
this.backDetail();
|
|
96
|
+
else if (this.focus === "recipe")
|
|
97
|
+
this.focus = "tabs";
|
|
62
98
|
else
|
|
63
99
|
this.done();
|
|
64
100
|
this.tui.requestRender();
|
|
@@ -78,30 +114,62 @@ export class ActorInspectorOverlay {
|
|
|
78
114
|
this.tui.requestRender();
|
|
79
115
|
return;
|
|
80
116
|
}
|
|
117
|
+
if (this.focus === "recipe") {
|
|
118
|
+
if (matchesKey(data, "up")) {
|
|
119
|
+
if (this.recipeScroll === 0)
|
|
120
|
+
this.focus = "tabs";
|
|
121
|
+
else
|
|
122
|
+
this.recipeScroll -= 1;
|
|
123
|
+
}
|
|
124
|
+
else if (matchesKey(data, "down"))
|
|
125
|
+
this.recipeScroll += 1;
|
|
126
|
+
else if (matchesKey(data, "pageUp"))
|
|
127
|
+
this.recipeScroll = Math.max(0, this.recipeScroll - this.contentViewportRows());
|
|
128
|
+
else if (matchesKey(data, "pageDown"))
|
|
129
|
+
this.recipeScroll += this.contentViewportRows();
|
|
130
|
+
else if (matchesKey(data, "left"))
|
|
131
|
+
this.focus = "tabs";
|
|
132
|
+
this.tui.requestRender();
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
81
135
|
if (this.focus === "detail") {
|
|
82
136
|
if (matchesKey(data, "up"))
|
|
83
137
|
this.detailScroll = Math.max(0, this.detailScroll - 1);
|
|
84
138
|
else if (matchesKey(data, "down"))
|
|
85
139
|
this.detailScroll += 1;
|
|
140
|
+
else if (matchesKey(data, "pageUp"))
|
|
141
|
+
this.detailScroll = Math.max(0, this.detailScroll - this.contentViewportRows());
|
|
142
|
+
else if (matchesKey(data, "pageDown"))
|
|
143
|
+
this.detailScroll += this.contentViewportRows();
|
|
86
144
|
else if (matchesKey(data, "left"))
|
|
87
|
-
this.
|
|
145
|
+
this.backDetail();
|
|
88
146
|
this.tui.requestRender();
|
|
89
147
|
return;
|
|
90
148
|
}
|
|
91
149
|
if (this.focus === "runs") {
|
|
92
|
-
if (matchesKey(data, "
|
|
150
|
+
if (matchesKey(data, "left"))
|
|
151
|
+
this.cycleRun(runs, -1);
|
|
152
|
+
else if (matchesKey(data, "right"))
|
|
153
|
+
this.cycleRun(runs, 1);
|
|
154
|
+
else if (matchesKey(data, "down"))
|
|
93
155
|
this.focus = "tabs";
|
|
94
156
|
else if (matchesKey(data, "return") && runs.length > 0)
|
|
95
157
|
this.openRunMenu();
|
|
96
158
|
}
|
|
97
159
|
else if (this.focus === "tabs") {
|
|
98
160
|
if (matchesKey(data, "left") || matchesKey(data, "right")) {
|
|
99
|
-
|
|
161
|
+
const current = INSPECTOR_TABS.indexOf(this.tab);
|
|
162
|
+
const direction = matchesKey(data, "right") ? 1 : -1;
|
|
163
|
+
this.tab = INSPECTOR_TABS[(current + direction + INSPECTOR_TABS.length) % INSPECTOR_TABS.length];
|
|
100
164
|
this.filterControlIndex = 0;
|
|
165
|
+
this.recipeScroll = 0;
|
|
101
166
|
this.rowIndex = 0;
|
|
102
167
|
}
|
|
103
168
|
else if (matchesKey(data, "up"))
|
|
104
169
|
this.focus = "runs";
|
|
170
|
+
else if ((matchesKey(data, "down") || matchesKey(data, "return")) &&
|
|
171
|
+
this.tab === "recipe")
|
|
172
|
+
this.focus = "recipe";
|
|
105
173
|
else if (matchesKey(data, "down") && this.listItemCount() > 0)
|
|
106
174
|
this.focus = "list";
|
|
107
175
|
else if (matchesKey(data, "return"))
|
|
@@ -109,7 +177,13 @@ export class ActorInspectorOverlay {
|
|
|
109
177
|
}
|
|
110
178
|
else if (this.focus === "list") {
|
|
111
179
|
const count = this.tab === "communications" ? this.communicationPreviews().length : this.turnItems().length;
|
|
112
|
-
if (matchesKey(data, "
|
|
180
|
+
if (matchesKey(data, "left"))
|
|
181
|
+
this.focus = "tabs";
|
|
182
|
+
else if (matchesKey(data, "pageUp"))
|
|
183
|
+
this.rowIndex = Math.max(0, this.rowIndex - this.contentViewportRows());
|
|
184
|
+
else if (matchesKey(data, "pageDown"))
|
|
185
|
+
this.rowIndex = Math.min(Math.max(0, count - 1), this.rowIndex + this.contentViewportRows());
|
|
186
|
+
else if (matchesKey(data, "up")) {
|
|
113
187
|
if (this.rowIndex === 0)
|
|
114
188
|
this.focus = "tabs";
|
|
115
189
|
else
|
|
@@ -131,7 +205,7 @@ export class ActorInspectorOverlay {
|
|
|
131
205
|
lines.push(this.row(this.renderRunControl(), innerWidth));
|
|
132
206
|
let selector = this.focus === "select"
|
|
133
207
|
? this.menuLevel === "run"
|
|
134
|
-
? this.renderMenuBox(this.runs().map((run) =>
|
|
208
|
+
? this.renderMenuBox(this.runs().map((run, index, runs) => `#${runs.length - index} ${run.run} ${run.status}`), this.selectorIndex, undefined, undefined, false, false, this.contentViewportRows())
|
|
135
209
|
: this.renderFilterMenus(innerWidth)
|
|
136
210
|
: [];
|
|
137
211
|
const selectorAnchor = this.selectorAnchor();
|
|
@@ -155,16 +229,30 @@ export class ActorInspectorOverlay {
|
|
|
155
229
|
else
|
|
156
230
|
lines.push(this.border("├", "", "┤", innerWidth));
|
|
157
231
|
this.contentStripeIndices = [];
|
|
158
|
-
const
|
|
159
|
-
? this.
|
|
160
|
-
: this.
|
|
161
|
-
? this.
|
|
162
|
-
: this.
|
|
163
|
-
|
|
232
|
+
const baseContent = this.killConfirmation
|
|
233
|
+
? this.renderKillConfirmation()
|
|
234
|
+
: this.detailOpen
|
|
235
|
+
? this.renderDetail(innerWidth)
|
|
236
|
+
: this.selectedRun
|
|
237
|
+
? this.renderTimeline(innerWidth)
|
|
238
|
+
: this.renderRunSelector(innerWidth);
|
|
239
|
+
const content = this.feedback && !this.killConfirmation
|
|
240
|
+
? [
|
|
241
|
+
this.theme.fg(this.feedback.ok
|
|
242
|
+
? "success"
|
|
243
|
+
: /^Kill (failed|rejected):/.test(this.feedback.message)
|
|
244
|
+
? "error"
|
|
245
|
+
: "warning", ` ${this.feedback.message}`),
|
|
246
|
+
...baseContent.slice(0, Math.max(0, this.contentViewportRows() - 1)),
|
|
247
|
+
]
|
|
248
|
+
: baseContent;
|
|
249
|
+
const contentViewportRows = this.contentViewportRows();
|
|
164
250
|
const selectorRows = selector.slice(1);
|
|
165
|
-
for (let index = 0; index < Math.max(content.length,
|
|
251
|
+
for (let index = 0; index < Math.max(content.length, contentViewportRows); index += 1) {
|
|
166
252
|
const base = content[index] ?? "";
|
|
167
|
-
const stripeIndex =
|
|
253
|
+
const stripeIndex = index < content.length
|
|
254
|
+
? this.contentStripeIndices[index] ?? index
|
|
255
|
+
: 0;
|
|
168
256
|
const popup = selectorRows[index];
|
|
169
257
|
if (!popup) {
|
|
170
258
|
lines.push(this.stripedRow(base, innerWidth, stripeIndex));
|
|
@@ -195,7 +283,89 @@ export class ActorInspectorOverlay {
|
|
|
195
283
|
if (!this.selectedRun)
|
|
196
284
|
this.selectedRun = runs[0]?.run;
|
|
197
285
|
}
|
|
286
|
+
contentViewportRows() {
|
|
287
|
+
const overlayRows = Math.floor(this.tui.terminal.rows * 0.94);
|
|
288
|
+
return Math.max(4, Math.min(16, overlayRows - 7));
|
|
289
|
+
}
|
|
290
|
+
selectRun(run, index) {
|
|
291
|
+
this.selectedRun = run;
|
|
292
|
+
this.turnCache = undefined;
|
|
293
|
+
this.runIndex = index;
|
|
294
|
+
this.recipeScroll = 0;
|
|
295
|
+
this.rowIndex = 0;
|
|
296
|
+
this.subagentIndex = 0;
|
|
297
|
+
this.communicationFrom = "all";
|
|
298
|
+
}
|
|
299
|
+
cycleRun(runs, direction) {
|
|
300
|
+
if (runs.length === 0)
|
|
301
|
+
return;
|
|
302
|
+
const currentIndex = Math.max(0, runs.findIndex((run) => run.run === this.selectedRun));
|
|
303
|
+
const nextIndex = (currentIndex + direction + runs.length) % runs.length;
|
|
304
|
+
const next = runs[nextIndex];
|
|
305
|
+
if (next)
|
|
306
|
+
this.selectRun(next.run, nextIndex);
|
|
307
|
+
}
|
|
308
|
+
requestKill(runs) {
|
|
309
|
+
this.feedback = undefined;
|
|
310
|
+
if (this.focus !== "runs") {
|
|
311
|
+
this.feedback = { ok: false, message: "Focus Run before using Kill." };
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
const run = runs.find((item) => item.run === this.selectedRun);
|
|
315
|
+
if (!run) {
|
|
316
|
+
this.feedback = { ok: false, message: "Kill unavailable: no owned run." };
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
if (!this.killRun) {
|
|
320
|
+
this.feedback = { ok: false, message: "Kill unavailable in this Inspector." };
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
if (run.status !== "running") {
|
|
324
|
+
this.feedback = {
|
|
325
|
+
ok: false,
|
|
326
|
+
message: `Kill unavailable: run is ${run.status}.`,
|
|
327
|
+
};
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
if (!run.runInstanceId) {
|
|
331
|
+
this.feedback = {
|
|
332
|
+
ok: false,
|
|
333
|
+
message: "Kill unavailable: run generation unavailable.",
|
|
334
|
+
};
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
this.killConfirmation = {
|
|
338
|
+
run: run.run,
|
|
339
|
+
runInstanceId: run.runInstanceId,
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
confirmKill() {
|
|
343
|
+
const confirmation = this.killConfirmation;
|
|
344
|
+
this.killConfirmation = undefined;
|
|
345
|
+
if (!confirmation || !this.killRun) {
|
|
346
|
+
this.feedback = { ok: false, message: "Kill unavailable in this Inspector." };
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
try {
|
|
350
|
+
this.feedback = this.killRun(confirmation.run, confirmation.runInstanceId);
|
|
351
|
+
}
|
|
352
|
+
catch (error) {
|
|
353
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
354
|
+
this.feedback = {
|
|
355
|
+
ok: false,
|
|
356
|
+
message: `Kill failed: ${message.replaceAll(/\s+/g, " ").slice(0, 160)}.`,
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
renderKillConfirmation() {
|
|
361
|
+
return [
|
|
362
|
+
this.theme.fg("error", ` Kill running actor run:${this.killConfirmation?.run}?`),
|
|
363
|
+
this.theme.fg("muted", " This sends canonical control.kill and cannot be undone."),
|
|
364
|
+
];
|
|
365
|
+
}
|
|
198
366
|
listItemCount() {
|
|
367
|
+
if (this.tab === "recipe")
|
|
368
|
+
return 0;
|
|
199
369
|
return this.tab === "communications"
|
|
200
370
|
? this.communicationPreviews().length
|
|
201
371
|
: this.turnItems().length;
|
|
@@ -203,30 +373,47 @@ export class ActorInspectorOverlay {
|
|
|
203
373
|
selectorAnchor() {
|
|
204
374
|
if (this.menuLevel === "run") {
|
|
205
375
|
const runControl = stripVTControlCharacters(this.renderRunControl());
|
|
206
|
-
return Math.max(0, runControl.indexOf("Run:") -
|
|
376
|
+
return Math.max(0, runControl.indexOf("Run:") - 2);
|
|
207
377
|
}
|
|
208
|
-
const label = this.tab === "communications"
|
|
378
|
+
const label = this.tab === "communications"
|
|
379
|
+
? "Messages"
|
|
380
|
+
: this.tab === "turns"
|
|
381
|
+
? "Turns"
|
|
382
|
+
: "Recipe";
|
|
209
383
|
const tabs = stripVTControlCharacters(this.renderTabs());
|
|
210
384
|
return Math.max(0, tabs.indexOf(label) - 2);
|
|
211
385
|
}
|
|
212
386
|
renderKeyHints() {
|
|
213
387
|
const hint = (keys, description) => `${this.theme.fg("accent", keys)}${this.theme.fg("dim", ` ${description}`)}`;
|
|
388
|
+
if (this.killConfirmation)
|
|
389
|
+
return ` ${hint("y/enter", "confirm kill")} ${hint("n/esc", "cancel")}`;
|
|
214
390
|
if (this.focus === "select")
|
|
215
391
|
return this.menuLevel === "value"
|
|
216
392
|
? ` ${hint("↑↓", "option")} ${hint("enter", "apply")} ${hint("←/esc", "back")}`
|
|
217
393
|
: ` ${hint("↑↓", "option")} ${hint("→/enter", "open")} ${hint("←/esc", "back")}`;
|
|
394
|
+
if (this.focus === "recipe")
|
|
395
|
+
return ` ${hint("↑↓/pgup/pgdn", "scroll")} ${hint("↑ at top", "tabs")} ${hint("←/esc", "tabs")}`;
|
|
218
396
|
if (this.focus === "detail")
|
|
219
|
-
return ` ${hint("
|
|
397
|
+
return ` ${hint("↑↓/pgup/pgdn", "scroll")} ${hint("←/esc", "back")}`;
|
|
220
398
|
if (this.focus === "list")
|
|
221
|
-
return ` ${hint("
|
|
399
|
+
return ` ${hint("↑↓/pgup/pgdn", "row")} ${hint("→/enter", "open")} ${hint("←", "tabs")} ${hint("esc", "close")}`;
|
|
400
|
+
if (this.focus === "runs") {
|
|
401
|
+
const run = this.runs().find((item) => item.run === this.selectedRun);
|
|
402
|
+
const killHint = this.killRun && run?.status === "running" && run.runInstanceId
|
|
403
|
+
? ` ${hint("k", "kill")}`
|
|
404
|
+
: "";
|
|
405
|
+
return ` ${hint("←→", "run")} ${hint("↓", "tabs")} ${hint("enter", "list")}${killHint} ${hint("esc", "close")}`;
|
|
406
|
+
}
|
|
222
407
|
return ` ${hint("←→", "navigate")} ${hint("↑↓", "change row")} ${hint("enter", "select")} ${hint("esc", "close")}`;
|
|
223
408
|
}
|
|
224
409
|
renderRunControl() {
|
|
225
|
-
const
|
|
410
|
+
const runs = this.runs();
|
|
411
|
+
const runIndex = runs.findIndex((item) => item.run === this.selectedRun);
|
|
412
|
+
const run = runs[runIndex];
|
|
226
413
|
const focused = this.focus === "runs";
|
|
227
414
|
const active = focused || (this.focus === "select" && this.menuLevel === "run");
|
|
228
|
-
const prefix = active ? this.theme.fg("accent", "
|
|
229
|
-
const suffix = active ? this.theme.fg("accent", "
|
|
415
|
+
const prefix = active ? this.theme.fg("accent", " ← ") : " ";
|
|
416
|
+
const suffix = active ? this.theme.fg("accent", " → ") : " ";
|
|
230
417
|
if (!run) {
|
|
231
418
|
const empty = this.theme.fg("muted", `${prefix}Run: none${suffix}`);
|
|
232
419
|
return focused ? this.theme.bg("selectedBg", empty) : empty;
|
|
@@ -238,7 +425,8 @@ export class ActorInspectorOverlay {
|
|
|
238
425
|
: run.status === "done"
|
|
239
426
|
? "success"
|
|
240
427
|
: "muted";
|
|
241
|
-
const
|
|
428
|
+
const sequence = runs.length - runIndex;
|
|
429
|
+
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}`;
|
|
242
430
|
return focused ? this.theme.bg("selectedBg", value) : value;
|
|
243
431
|
}
|
|
244
432
|
activeMessageFilters() {
|
|
@@ -263,19 +451,19 @@ export class ActorInspectorOverlay {
|
|
|
263
451
|
};
|
|
264
452
|
const messageFilters = this.activeMessageFilters();
|
|
265
453
|
const subagent = this.subagents()[this.subagentIndex] ?? "all";
|
|
266
|
-
const messagesLabel = `Messages${messageFilters.length > 0 ? `
|
|
267
|
-
const turnsLabel = `Turns${subagent === "all" ? "" : `
|
|
268
|
-
return `${tab("communications", messagesLabel)} ${tab("turns", turnsLabel)}`;
|
|
454
|
+
const messagesLabel = `Messages${messageFilters.length > 0 ? ` (${messageFilters.join(", ")})` : ""}`;
|
|
455
|
+
const turnsLabel = `Turns${subagent === "all" ? "" : ` (${subagent})`}`;
|
|
456
|
+
return `${tab("recipe", "Recipe")} ${tab("communications", messagesLabel)} ${tab("turns", turnsLabel)}`;
|
|
269
457
|
}
|
|
270
458
|
renderRunSelector(width) {
|
|
271
459
|
const runs = this.runs();
|
|
272
460
|
if (runs.length === 0)
|
|
273
461
|
return [this.theme.fg("muted", " No owned actor runs")];
|
|
274
462
|
this.runIndex = Math.min(this.runIndex, runs.length - 1);
|
|
275
|
-
return runs.slice(0,
|
|
463
|
+
return runs.slice(0, this.contentViewportRows()).map((run, index) => {
|
|
276
464
|
const selected = index === this.runIndex;
|
|
277
465
|
const prefix = selected ? this.theme.fg("accent", " ▶ ") : " ";
|
|
278
|
-
const label = `${prefix}run:${run.run}`;
|
|
466
|
+
const label = `${prefix}#${runs.length - index} run:${run.run}`;
|
|
279
467
|
const status = ` ${run.status}`;
|
|
280
468
|
return selected
|
|
281
469
|
? this.theme.fg("accent", `${label}${status}`)
|
|
@@ -290,6 +478,8 @@ export class ActorInspectorOverlay {
|
|
|
290
478
|
this.selectedRun = undefined;
|
|
291
479
|
return [this.theme.fg("warning", " Selected run is no longer owned by this session")];
|
|
292
480
|
}
|
|
481
|
+
if (this.tab === "recipe")
|
|
482
|
+
return this.renderRecipe(width);
|
|
293
483
|
const rows = this.tab === "communications"
|
|
294
484
|
? this.communicationRows()
|
|
295
485
|
: this.turnRows();
|
|
@@ -300,16 +490,20 @@ export class ActorInspectorOverlay {
|
|
|
300
490
|
: (this.subagents()[this.subagentIndex] ?? "all") !== "all";
|
|
301
491
|
return [
|
|
302
492
|
this.theme.fg("muted", filtered
|
|
303
|
-
? " No results for the active filters
|
|
493
|
+
? " No results for the active filters. Enter on the tab to adjust."
|
|
304
494
|
: this.tab === "communications"
|
|
305
495
|
? " No messages in this run"
|
|
306
496
|
: " No persisted turns in this run"),
|
|
307
497
|
];
|
|
308
498
|
}
|
|
309
499
|
this.rowIndex = Math.min(this.rowIndex, rows.length - 1);
|
|
310
|
-
const
|
|
311
|
-
const
|
|
312
|
-
|
|
500
|
+
const viewportRows = this.contentViewportRows();
|
|
501
|
+
const start = Math.max(0, Math.min(this.rowIndex - Math.floor(viewportRows / 2), Math.max(0, rows.length - viewportRows)));
|
|
502
|
+
const stripeIndices = this.tab === "communications"
|
|
503
|
+
? this.communicationPreviews().map((preview, index) => Math.max(0, (preview.sequence ?? index + 1) - 1))
|
|
504
|
+
: this.turnItems().map((turn) => Math.max(0, turn.index - 1));
|
|
505
|
+
const visibleRows = rows.slice(start, start + viewportRows);
|
|
506
|
+
this.contentStripeIndices = visibleRows.map((_, offset) => stripeIndices[start + offset] ?? start + offset);
|
|
313
507
|
return visibleRows.map((row, offset) => {
|
|
314
508
|
const index = start + offset;
|
|
315
509
|
const selected = this.focus === "list" && index === this.rowIndex;
|
|
@@ -318,6 +512,28 @@ export class ActorInspectorOverlay {
|
|
|
318
512
|
: ` ${row}`;
|
|
319
513
|
});
|
|
320
514
|
}
|
|
515
|
+
renderRecipe(width) {
|
|
516
|
+
if (!this.selectedRun)
|
|
517
|
+
return [];
|
|
518
|
+
const recipe = Inspector.readActorInspectorRecipe(path.join(this.stateRoot, this.selectedRun));
|
|
519
|
+
const logicalLines = [
|
|
520
|
+
...this.detailSection("Identity", this.readableValueLines(recipe.identity, " ")),
|
|
521
|
+
...(recipe.definition
|
|
522
|
+
? this.detailSection("Recipe", this.readableValueLines(recipe.definition, " "))
|
|
523
|
+
: []),
|
|
524
|
+
...this.detailSection("Launch", this.readableValueLines(recipe.launch, " ")),
|
|
525
|
+
...(recipe.composition.length > 0
|
|
526
|
+
? this.detailSection("Composition", this.readableValueLines(recipe.composition, " "))
|
|
527
|
+
: []),
|
|
528
|
+
...this.detailSection("Diagnostics", recipe.diagnostics),
|
|
529
|
+
];
|
|
530
|
+
const wrapped = this.wrapDetailLines(logicalLines.length > 0 ? logicalLines : [" No persisted recipe evidence"], width);
|
|
531
|
+
const viewportRows = this.contentViewportRows();
|
|
532
|
+
const maxStart = Math.max(0, wrapped.lines.length - viewportRows);
|
|
533
|
+
this.recipeScroll = Math.min(this.recipeScroll, maxStart);
|
|
534
|
+
this.contentStripeIndices = wrapped.stripeIndices.slice(this.recipeScroll, this.recipeScroll + viewportRows);
|
|
535
|
+
return wrapped.lines.slice(this.recipeScroll, this.recipeScroll + viewportRows);
|
|
536
|
+
}
|
|
321
537
|
communicationPreviews() {
|
|
322
538
|
if (!this.selectedRun)
|
|
323
539
|
return [];
|
|
@@ -331,9 +547,11 @@ export class ActorInspectorOverlay {
|
|
|
331
547
|
readKeys: this.readKeys,
|
|
332
548
|
run: this.selectedRun,
|
|
333
549
|
unreadOnly: this.communicationUnread,
|
|
334
|
-
})
|
|
550
|
+
})
|
|
551
|
+
.filter((preview) => this.communicationFrom === "all" ||
|
|
335
552
|
this.communicationActorLabel(preview.from_display ?? preview.from) ===
|
|
336
|
-
this.communicationFrom)
|
|
553
|
+
this.communicationFrom)
|
|
554
|
+
.reverse();
|
|
337
555
|
}
|
|
338
556
|
communicationActorLabel(actor) {
|
|
339
557
|
const value = actor ?? "unknown";
|
|
@@ -352,7 +570,7 @@ export class ActorInspectorOverlay {
|
|
|
352
570
|
const actor = preview.from_display ?? preview.from ?? "unknown";
|
|
353
571
|
const target = preview.channel === "room" ? "#all" : preview.to;
|
|
354
572
|
const text = preview.summary ?? preview.body_preview ?? "(no body)";
|
|
355
|
-
return
|
|
573
|
+
return `#${preview.sequence ?? "?"} ${preview.needs_response ? "! " : ""}${actor} → ${target} ${preview.type} ${text}`;
|
|
356
574
|
});
|
|
357
575
|
}
|
|
358
576
|
renderDetail(width) {
|
|
@@ -372,66 +590,287 @@ export class ActorInspectorOverlay {
|
|
|
372
590
|
["inbox", preview.inbox_status],
|
|
373
591
|
["timestamp", preview.timestamp],
|
|
374
592
|
];
|
|
375
|
-
|
|
593
|
+
const lines = fields
|
|
376
594
|
.filter(([, value]) => value !== undefined)
|
|
377
595
|
.map(([label, value]) => truncateToWidth(` ${label.padEnd(12, " ")} ${typeof value === "string" ? value : JSON.stringify(value)}`, width, "…"));
|
|
596
|
+
const viewportRows = this.contentViewportRows();
|
|
597
|
+
const maxStart = Math.max(0, lines.length - viewportRows);
|
|
598
|
+
this.detailScroll = Math.min(this.detailScroll, maxStart);
|
|
599
|
+
return lines.slice(this.detailScroll, this.detailScroll + viewportRows);
|
|
378
600
|
}
|
|
379
601
|
const turn = this.detailTurn;
|
|
380
602
|
if (!turn)
|
|
381
603
|
return [this.theme.fg("warning", " Turn row is no longer available")];
|
|
382
|
-
const
|
|
383
|
-
const
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
604
|
+
const logicalLines = this.turnEvidenceLines(turn);
|
|
605
|
+
const wrapped = this.wrapDetailLines(logicalLines, width);
|
|
606
|
+
const viewportRows = this.contentViewportRows();
|
|
607
|
+
const maxStart = Math.max(0, wrapped.lines.length - viewportRows);
|
|
608
|
+
this.detailScroll = Math.min(this.detailScroll, maxStart);
|
|
609
|
+
this.contentStripeIndices = wrapped.stripeIndices.slice(this.detailScroll, this.detailScroll + viewportRows);
|
|
610
|
+
return wrapped.lines.slice(this.detailScroll, this.detailScroll + viewportRows);
|
|
611
|
+
}
|
|
612
|
+
wrapDetailLines(lines, width) {
|
|
613
|
+
const wrappedLines = [];
|
|
614
|
+
const stripeIndices = [];
|
|
615
|
+
let sectionIndex = 0;
|
|
616
|
+
for (const line of lines) {
|
|
617
|
+
if (typeof line !== "string") {
|
|
618
|
+
sectionIndex += 1;
|
|
619
|
+
continue;
|
|
620
|
+
}
|
|
621
|
+
const physicalLines = line.replaceAll("\t", " ").split(/\r?\n/);
|
|
622
|
+
for (const physicalLine of physicalLines) {
|
|
623
|
+
if (!physicalLine.trim())
|
|
624
|
+
continue;
|
|
625
|
+
const leading = physicalLine.match(/^ */)?.[0] ?? "";
|
|
626
|
+
const content = physicalLine.slice(leading.length);
|
|
627
|
+
const contentWidth = Math.max(1, width - visibleWidth(leading));
|
|
628
|
+
const labeled = /^(\s*(?:-\s+)?[^:]+:\s+)(\S.*)$/u.exec(physicalLine);
|
|
629
|
+
let wrapped;
|
|
630
|
+
if (visibleWidth(physicalLine) > width &&
|
|
631
|
+
labeled &&
|
|
632
|
+
visibleWidth(labeled[1]) < width) {
|
|
633
|
+
const prefix = labeled[1];
|
|
634
|
+
const value = labeled[2];
|
|
635
|
+
const firstWidth = Math.max(1, width - visibleWidth(prefix));
|
|
636
|
+
const first = this.wrapPreferred(value, firstWidth)[0] ??
|
|
637
|
+
this.takeVisiblePrefix(value, firstWidth);
|
|
638
|
+
const remainder = value.slice(first.length).trimStart();
|
|
639
|
+
const continuation = `${leading} `;
|
|
640
|
+
const continuationWidth = Math.max(1, width - visibleWidth(continuation));
|
|
641
|
+
wrapped = [
|
|
642
|
+
`${prefix}${first}`,
|
|
643
|
+
...(remainder
|
|
644
|
+
? this.wrapPreferred(remainder, continuationWidth).map((visualLine) => `${continuation}${visualLine}`)
|
|
645
|
+
: []),
|
|
646
|
+
];
|
|
647
|
+
}
|
|
648
|
+
else {
|
|
649
|
+
wrapped = visibleWidth(physicalLine) <= width
|
|
650
|
+
? [physicalLine]
|
|
651
|
+
: this.wrapPreferred(content, contentWidth).map((visualLine) => `${leading}${visualLine}`);
|
|
652
|
+
}
|
|
653
|
+
for (const visualLine of wrapped) {
|
|
654
|
+
if (!visualLine.trim())
|
|
655
|
+
continue;
|
|
656
|
+
wrappedLines.push(visualLine);
|
|
657
|
+
stripeIndices.push(sectionIndex);
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
return { lines: wrappedLines, stripeIndices };
|
|
662
|
+
}
|
|
663
|
+
wrapPreferred(content, width) {
|
|
664
|
+
const lines = [];
|
|
665
|
+
let remaining = stripVTControlCharacters(content).trimEnd();
|
|
666
|
+
while (remaining && visibleWidth(remaining) > width) {
|
|
667
|
+
const candidate = this.takeVisiblePrefix(remaining, width);
|
|
668
|
+
let splitAt = -1;
|
|
669
|
+
let trimBoundary = false;
|
|
670
|
+
for (let index = 1; index < candidate.length; index += 1) {
|
|
671
|
+
if (/\s/u.test(candidate[index])) {
|
|
672
|
+
splitAt = index + 1;
|
|
673
|
+
trimBoundary = true;
|
|
674
|
+
}
|
|
675
|
+
else if (/[-+=/_,.:]/u.test(candidate[index])) {
|
|
676
|
+
splitAt = index;
|
|
677
|
+
trimBoundary = false;
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
if (splitAt <= 0)
|
|
681
|
+
splitAt = candidate.length;
|
|
682
|
+
const head = remaining.slice(0, splitAt).trimEnd();
|
|
683
|
+
if (!head) {
|
|
684
|
+
lines.push(candidate);
|
|
685
|
+
remaining = remaining.slice(candidate.length);
|
|
686
|
+
continue;
|
|
687
|
+
}
|
|
688
|
+
lines.push(head);
|
|
689
|
+
remaining = remaining.slice(splitAt);
|
|
690
|
+
if (trimBoundary)
|
|
691
|
+
remaining = remaining.trimStart();
|
|
692
|
+
}
|
|
693
|
+
if (remaining)
|
|
694
|
+
lines.push(remaining);
|
|
695
|
+
return lines;
|
|
696
|
+
}
|
|
697
|
+
detailSection(title, content) {
|
|
698
|
+
if (content.length === 0)
|
|
699
|
+
return [];
|
|
700
|
+
return [
|
|
701
|
+
` ${title}`,
|
|
702
|
+
...content.flatMap((line) => line
|
|
703
|
+
.split(/\r?\n/)
|
|
704
|
+
.filter((physicalLine) => physicalLine.trim().length > 0)
|
|
705
|
+
.map((physicalLine) => ` ${physicalLine}`)),
|
|
706
|
+
DETAIL_SECTION_BREAK,
|
|
707
|
+
];
|
|
708
|
+
}
|
|
709
|
+
readableValueLines(value, indent = "", depth = 0) {
|
|
710
|
+
if (depth > 8)
|
|
711
|
+
return [`${indent}[nested value]`];
|
|
712
|
+
if (typeof value === "string") {
|
|
713
|
+
const trimmed = value.trim();
|
|
714
|
+
if (/^[{[]/.test(trimmed)) {
|
|
715
|
+
try {
|
|
716
|
+
return this.readableValueLines(JSON.parse(trimmed), indent, depth + 1);
|
|
717
|
+
}
|
|
718
|
+
catch {
|
|
719
|
+
// Keep non-JSON text as written.
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
return value.split(/\r?\n/).map((line) => `${indent}${line}`);
|
|
723
|
+
}
|
|
724
|
+
if (value === null || value === undefined)
|
|
725
|
+
return [`${indent}${String(value)}`];
|
|
726
|
+
if (typeof value !== "object")
|
|
727
|
+
return [`${indent}${String(value)}`];
|
|
728
|
+
if (Array.isArray(value)) {
|
|
729
|
+
if (value.length === 0)
|
|
730
|
+
return [`${indent}(empty)`];
|
|
731
|
+
return value.flatMap((item) => {
|
|
732
|
+
if (item &&
|
|
733
|
+
typeof item === "object" &&
|
|
734
|
+
item.type === "text" &&
|
|
735
|
+
typeof item.text === "string") {
|
|
736
|
+
return this.readableValueLines(item.text, indent, depth + 1);
|
|
737
|
+
}
|
|
738
|
+
if (!item || typeof item !== "object")
|
|
739
|
+
return [`${indent}- ${String(item)}`];
|
|
740
|
+
const [first = "", ...rest] = this.readableValueLines(item, `${indent} `, depth + 1);
|
|
741
|
+
return [
|
|
742
|
+
`${indent}- ${first.trimStart()}`,
|
|
743
|
+
...rest,
|
|
744
|
+
];
|
|
745
|
+
});
|
|
746
|
+
}
|
|
747
|
+
const entries = Object.entries(value);
|
|
748
|
+
if (entries.length === 0)
|
|
749
|
+
return [`${indent}(empty)`];
|
|
750
|
+
return entries.flatMap(([key, item]) => {
|
|
751
|
+
if (!item || typeof item !== "object") {
|
|
752
|
+
const scalar = typeof item === "string" ? item : String(item);
|
|
753
|
+
const [first = "", ...rest] = scalar.split(/\r?\n/);
|
|
754
|
+
return [
|
|
755
|
+
`${indent}${key}: ${first}`,
|
|
756
|
+
...rest.map((line) => `${indent} ${line}`),
|
|
757
|
+
];
|
|
758
|
+
}
|
|
759
|
+
return [
|
|
760
|
+
`${indent}${key}:`,
|
|
761
|
+
...this.readableValueLines(item, `${indent} `, depth + 1),
|
|
762
|
+
];
|
|
763
|
+
});
|
|
764
|
+
}
|
|
765
|
+
turnEvidenceLines(turn) {
|
|
766
|
+
const lines = [];
|
|
767
|
+
const commandNumber = /^command-(\d+)$/.exec(turn.commandId)?.[1];
|
|
768
|
+
const subagent = commandNumber
|
|
769
|
+
? String(Number.parseInt(commandNumber, 10))
|
|
770
|
+
: turn.commandId;
|
|
771
|
+
const stage = turn.stage && turn.stage !== "subagent" && turn.stage !== "command"
|
|
772
|
+
? ` (${turn.stage})`
|
|
773
|
+
: "";
|
|
774
|
+
lines.push(` Subagent ${subagent}${stage}`, DETAIL_SECTION_BREAK);
|
|
775
|
+
if (turn.userText)
|
|
776
|
+
lines.push(...this.detailSection("User", [this.readablePromptText(turn.userText)]));
|
|
777
|
+
if (turn.thinking)
|
|
778
|
+
lines.push(...this.detailSection("Thinking", [turn.thinking]));
|
|
779
|
+
if (turn.assistantText)
|
|
780
|
+
lines.push(...this.detailSection("Assistant", [turn.assistantText]));
|
|
781
|
+
for (const [index, tool] of turn.toolCalls.entries()) {
|
|
782
|
+
const content = [
|
|
783
|
+
...(tool.arguments === undefined
|
|
784
|
+
? []
|
|
785
|
+
: ["Input", ...this.readableValueLines(tool.arguments, " ")]),
|
|
786
|
+
...(tool.result === undefined
|
|
787
|
+
? []
|
|
788
|
+
: ["Result", ...this.readableValueLines(tool.result, " ")]),
|
|
789
|
+
`call id: ${tool.id}`,
|
|
790
|
+
];
|
|
791
|
+
lines.push(...this.detailSection(`Tool ${index + 1} (${tool.name}${tool.resultError ? ", Error" : ""})`, content));
|
|
792
|
+
}
|
|
793
|
+
const execution = [
|
|
794
|
+
...(!turn.thinking ? ["thinking: not persisted"] : []),
|
|
795
|
+
...(turn.model ? [`model: ${turn.model}`] : []),
|
|
796
|
+
...(turn.stopReason ? [`stop reason: ${turn.stopReason}`] : []),
|
|
797
|
+
...(turn.error ? [`error: ${turn.error}`] : []),
|
|
798
|
+
...(turn.usage === undefined
|
|
799
|
+
? []
|
|
800
|
+
: ["Usage", ...this.readableValueLines(turn.usage, " ")]),
|
|
801
|
+
...(turn.unmatchedToolResults > 0
|
|
802
|
+
? [`unmatched tool results: ${turn.unmatchedToolResults}`]
|
|
388
803
|
: []),
|
|
389
|
-
|
|
390
|
-
|
|
804
|
+
];
|
|
805
|
+
if (execution.length > 0)
|
|
806
|
+
lines.push(...this.detailSection("Execution", execution));
|
|
807
|
+
if (turn.diagnostics.length > 0)
|
|
808
|
+
lines.push(...this.detailSection("Diagnostics", turn.diagnostics));
|
|
809
|
+
const provenance = [
|
|
810
|
+
"Session",
|
|
811
|
+
` ${turn.sessionFile}`,
|
|
812
|
+
...(turn.sessionTruncated ? [" turn list truncated"] : []),
|
|
813
|
+
...(turn.promptFile
|
|
814
|
+
? [
|
|
815
|
+
"Prompt",
|
|
816
|
+
` ${turn.promptFile}${turn.promptBytes ? ` (${turn.promptBytes} bytes)` : ""}`,
|
|
817
|
+
]
|
|
391
818
|
: []),
|
|
392
|
-
...(turn.userText ? [` user ${turn.userText}`] : []),
|
|
393
|
-
...(turn.assistantText ? [` assistant ${turn.assistantText}`] : []),
|
|
394
|
-
` reasoning ${turn.thinking ? "persisted thinking block" : "unavailable"}`,
|
|
395
|
-
...(turn.thinking ? [` thinking ${turn.thinking}`] : []),
|
|
396
|
-
...(turn.stopReason ? [` stop reason ${turn.stopReason}`] : []),
|
|
397
|
-
...(turn.usage !== undefined ? [` usage ${value(turn.usage)}`] : []),
|
|
398
|
-
...(turn.error ? [` error ${turn.error}`] : []),
|
|
399
|
-
` tools ${turn.toolCalls.length}`,
|
|
400
819
|
];
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
if (tool.arguments !== undefined)
|
|
404
|
-
fields.push(` arguments ${value(tool.arguments)}`);
|
|
405
|
-
if (tool.result !== undefined)
|
|
406
|
-
fields.push(` result ${value(tool.result)}`);
|
|
407
|
-
}
|
|
408
|
-
if (turn.unmatchedToolResults > 0)
|
|
409
|
-
fields.push(` unmatched ${turn.unmatchedToolResults}`);
|
|
410
|
-
if (turn.sessionTruncated)
|
|
411
|
-
fields.push(" session turn list truncated");
|
|
412
|
-
for (const diagnostic of turn.diagnostics)
|
|
413
|
-
fields.push(` diagnostic ${diagnostic}`);
|
|
414
|
-
const maxStart = Math.max(0, fields.length - 16);
|
|
415
|
-
this.detailScroll = Math.min(this.detailScroll, maxStart);
|
|
416
|
-
return fields
|
|
417
|
-
.slice(this.detailScroll, this.detailScroll + 16)
|
|
418
|
-
.map((field) => truncateToWidth(field, width, "…"));
|
|
820
|
+
lines.push(...this.detailSection("Provenance", provenance));
|
|
821
|
+
return lines.length > 0 ? lines : [" No persisted turn evidence"];
|
|
419
822
|
}
|
|
420
|
-
|
|
823
|
+
readablePromptText(text) {
|
|
824
|
+
const wrapped = /^\s*<file\b[^>]*>\s*([\s\S]*?)\s*<\/file>\s*$/i.exec(text);
|
|
825
|
+
return wrapped?.[1]?.trim() || text;
|
|
826
|
+
}
|
|
827
|
+
allTurnItems() {
|
|
421
828
|
if (!this.selectedRun)
|
|
422
829
|
return [];
|
|
830
|
+
if (this.turnCache?.run === this.selectedRun)
|
|
831
|
+
return this.turnCache.turns;
|
|
832
|
+
const turns = this.readTurns(path.join(this.stateRoot, this.selectedRun));
|
|
833
|
+
this.turnCache = { run: this.selectedRun, turns };
|
|
834
|
+
return turns;
|
|
835
|
+
}
|
|
836
|
+
turnItems() {
|
|
423
837
|
const selectedSubagent = this.subagents()[this.subagentIndex];
|
|
424
|
-
return
|
|
838
|
+
return this.allTurnItems()
|
|
839
|
+
.filter((turn) => !selectedSubagent ||
|
|
425
840
|
selectedSubagent === "all" ||
|
|
426
|
-
turn.commandId === selectedSubagent)
|
|
841
|
+
turn.commandId === selectedSubagent)
|
|
842
|
+
.reverse();
|
|
427
843
|
}
|
|
428
844
|
turnRows() {
|
|
429
|
-
return this.turnItems().map((turn) =>
|
|
845
|
+
return this.turnItems().map((turn) => {
|
|
846
|
+
const commandNumber = /^command-(\d+)$/.exec(turn.commandId)?.[1];
|
|
847
|
+
const subagent = commandNumber
|
|
848
|
+
? `Subagent ${Number.parseInt(commandNumber, 10)}`
|
|
849
|
+
: `Subagent ${turn.commandId}`;
|
|
850
|
+
const stage = turn.stage && turn.stage !== "subagent" && turn.stage !== "command"
|
|
851
|
+
? ` (${turn.stage})`
|
|
852
|
+
: "";
|
|
853
|
+
const toolNames = [...new Set(turn.toolCalls.map((tool) => tool.name))];
|
|
854
|
+
const action = turn.toolCalls.length === 0
|
|
855
|
+
? ""
|
|
856
|
+
: turn.toolCalls.length === 1
|
|
857
|
+
? ` (${toolNames[0]})`
|
|
858
|
+
: toolNames.length === 1
|
|
859
|
+
? ` (${toolNames[0]}, ${turn.toolCalls.length} calls)`
|
|
860
|
+
: turn.toolCalls.length <= 2
|
|
861
|
+
? ` (${toolNames.join(", ")})`
|
|
862
|
+
: ` (${turn.toolCalls.length} tools)`;
|
|
863
|
+
const failed = turn.error || turn.toolCalls.some((tool) => tool.resultError)
|
|
864
|
+
? " (error)"
|
|
865
|
+
: "";
|
|
866
|
+
const preview = this.readablePromptText(turn.assistantText ?? turn.userText ?? "(no text)").replaceAll(/\s+/g, " ").trim();
|
|
867
|
+
return `#${turn.index} ${subagent}${stage} ${turn.model ?? "unknown model"}${action}${failed} ${preview}`;
|
|
868
|
+
});
|
|
430
869
|
}
|
|
431
870
|
subagents() {
|
|
432
871
|
if (!this.selectedRun || this.tab !== "turns")
|
|
433
872
|
return ["all"];
|
|
434
|
-
const ids =
|
|
873
|
+
const ids = this.allTurnItems().map((turn) => turn.commandId);
|
|
435
874
|
return ["all", ...new Set(ids)];
|
|
436
875
|
}
|
|
437
876
|
valueOptions() {
|
|
@@ -478,11 +917,7 @@ export class ActorInspectorOverlay {
|
|
|
478
917
|
if (!value)
|
|
479
918
|
return;
|
|
480
919
|
if (this.menuLevel === "run") {
|
|
481
|
-
this.
|
|
482
|
-
this.runIndex = Math.max(0, this.runs().findIndex((run) => run.run === value));
|
|
483
|
-
this.rowIndex = 0;
|
|
484
|
-
this.subagentIndex = 0;
|
|
485
|
-
this.communicationFrom = "all";
|
|
920
|
+
this.selectRun(value, Math.max(0, this.runs().findIndex((run) => run.run === value)));
|
|
486
921
|
this.focus = "runs";
|
|
487
922
|
return;
|
|
488
923
|
}
|
|
@@ -518,12 +953,20 @@ export class ActorInspectorOverlay {
|
|
|
518
953
|
currentFilterValue() {
|
|
519
954
|
return this.filterValue(this.filterControlIndex);
|
|
520
955
|
}
|
|
521
|
-
renderMenuBox(options, focusedIndex, current, parentIndex, omitLeftBorder = false, omitRightBorder = false) {
|
|
522
|
-
const
|
|
523
|
-
const
|
|
956
|
+
renderMenuBox(options, focusedIndex, current, parentIndex, omitLeftBorder = false, omitRightBorder = false, maxVisibleOptions = options.length) {
|
|
957
|
+
const visibleLimit = Math.max(1, Math.min(maxVisibleOptions, options.length));
|
|
958
|
+
const anchorIndex = Math.max(0, Math.min(options.length - 1, focusedIndex >= 0 ? focusedIndex : (parentIndex ?? 0)));
|
|
959
|
+
const maxStart = Math.max(0, options.length - visibleLimit);
|
|
960
|
+
const start = Math.max(0, Math.min(anchorIndex - Math.floor(visibleLimit / 2), maxStart));
|
|
961
|
+
const visibleOptions = options.slice(start, start + visibleLimit);
|
|
962
|
+
const hiddenAbove = start > 0;
|
|
963
|
+
const hiddenBelow = start + visibleOptions.length < options.length;
|
|
964
|
+
const contentWidth = Math.max(4, ...options.map((option) => visibleWidth(option) + 4));
|
|
965
|
+
const border = (left, right, marker = "") => this.theme.fg("border", `${omitLeftBorder ? "" : left}${marker}${"─".repeat(Math.max(0, contentWidth - visibleWidth(marker)))}${omitRightBorder ? "" : right}`);
|
|
524
966
|
return [
|
|
525
|
-
border("╭", "╮"),
|
|
526
|
-
...
|
|
967
|
+
border("╭", "╮", hiddenAbove ? "↑" : ""),
|
|
968
|
+
...visibleOptions.map((option, localIndex) => {
|
|
969
|
+
const index = start + localIndex;
|
|
527
970
|
const focused = index === focusedIndex;
|
|
528
971
|
const row = this.fit(`${focused ? " ▶ " : " "}${option}`, contentWidth);
|
|
529
972
|
const colored = focused || option === current || index === parentIndex
|
|
@@ -534,17 +977,18 @@ export class ActorInspectorOverlay {
|
|
|
534
977
|
: colored;
|
|
535
978
|
return `${omitLeftBorder ? "" : this.theme.fg("border", "│")}${styled}${omitRightBorder ? "" : this.theme.fg("border", "│")}`;
|
|
536
979
|
}),
|
|
537
|
-
border("╰", "╯"),
|
|
980
|
+
border("╰", "╯", hiddenBelow ? "↓" : ""),
|
|
538
981
|
];
|
|
539
982
|
}
|
|
540
983
|
renderFilterMenus(_width) {
|
|
541
984
|
const filters = this.filterOptions().map((filter, index) => `${filter}: ${this.filterValue(index)}`);
|
|
985
|
+
const maxVisibleOptions = Math.max(1, this.contentViewportRows() - 1);
|
|
542
986
|
const parentFocus = this.menuLevel === "filter" ? this.selectorIndex : -1;
|
|
543
|
-
const parent = this.renderMenuBox(filters, parentFocus, undefined, this.menuLevel === "value" ? this.filterControlIndex : undefined);
|
|
987
|
+
const parent = this.renderMenuBox(filters, parentFocus, undefined, this.menuLevel === "value" ? this.filterControlIndex : undefined, false, false, maxVisibleOptions);
|
|
544
988
|
if (this.menuLevel !== "value")
|
|
545
989
|
return parent;
|
|
546
|
-
const parentShared = this.renderMenuBox(filters, parentFocus, undefined, this.filterControlIndex, false, true);
|
|
547
|
-
const child = this.renderMenuBox(this.valueOptions(), this.selectorIndex, this.currentFilterValue());
|
|
990
|
+
const parentShared = this.renderMenuBox(filters, parentFocus, undefined, this.filterControlIndex, false, true, maxVisibleOptions);
|
|
991
|
+
const child = this.renderMenuBox(this.valueOptions(), this.selectorIndex, this.currentFilterValue(), undefined, false, false, maxVisibleOptions);
|
|
548
992
|
return Array.from({ length: Math.max(parent.length, child.length) }, (_, index) => child[index]
|
|
549
993
|
? `${parentShared[index] ?? " ".repeat(visibleWidth(parentShared[0]))}${child[index]}`
|
|
550
994
|
: (parent[index] ?? ""));
|
|
@@ -563,6 +1007,9 @@ export class ActorInspectorOverlay {
|
|
|
563
1007
|
if (this.detailOpen)
|
|
564
1008
|
this.focus = "detail";
|
|
565
1009
|
}
|
|
1010
|
+
backDetail() {
|
|
1011
|
+
this.closeDetail();
|
|
1012
|
+
}
|
|
566
1013
|
closeDetail() {
|
|
567
1014
|
this.detailOpen = false;
|
|
568
1015
|
this.detailCommunication = undefined;
|