@llblab/pi-actors 0.41.0 → 0.41.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +2 -2
- package/BACKLOG.md +0 -19
- package/CHANGELOG.md +7 -0
- package/dist/index.js +51 -8
- package/dist/lib/inspector-overlay.d.ts +11 -0
- package/dist/lib/inspector-overlay.js +321 -75
- package/dist/lib/inspector.js +4 -1
- package/dist/lib/observability.d.ts +38 -3
- package/dist/lib/observability.js +151 -33
- package/dist/lib/tools-response.d.ts +1 -1
- package/dist/lib/tools-response.js +5 -7
- package/dist/skills/actors/SKILL.md +3 -7
- package/dist/skills/swarm/SKILL.md +1 -1
- package/docs/actor-inspector.md +18 -13
- package/docs/async-runs.md +1 -1
- package/index.ts +49 -8
- package/lib/inspector-overlay.ts +418 -82
- package/lib/inspector.ts +6 -3
- package/lib/observability.ts +205 -39
- package/lib/tools-response.ts +5 -6
- package/package.json +1 -1
- package/skills/actors/SKILL.md +3 -7
- package/skills/swarm/SKILL.md +1 -1
|
@@ -5,8 +5,9 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import * as path from "node:path";
|
|
7
7
|
import { stripVTControlCharacters } from "node:util";
|
|
8
|
-
import { matchesKey, truncateToWidth, visibleWidth, } from "@earendil-works/pi-tui";
|
|
8
|
+
import { matchesKey, truncateToWidth, visibleWidth, wrapTextWithAnsi, } from "@earendil-works/pi-tui";
|
|
9
9
|
import * as Inspector from "./inspector.js";
|
|
10
|
+
const DETAIL_SECTION_BREAK = { sectionBreak: true };
|
|
10
11
|
export class ActorInspectorOverlay {
|
|
11
12
|
done;
|
|
12
13
|
ownerId;
|
|
@@ -23,6 +24,7 @@ export class ActorInspectorOverlay {
|
|
|
23
24
|
detailOpen = false;
|
|
24
25
|
detailScroll = 0;
|
|
25
26
|
detailTurn;
|
|
27
|
+
detailView = "evidence";
|
|
26
28
|
readKeys = new Set();
|
|
27
29
|
focus = "tabs";
|
|
28
30
|
filterControlIndex = 0;
|
|
@@ -58,7 +60,7 @@ export class ActorInspectorOverlay {
|
|
|
58
60
|
this.focus = this.menuLevel === "run" ? "runs" : "tabs";
|
|
59
61
|
}
|
|
60
62
|
else if (this.focus === "detail")
|
|
61
|
-
this.
|
|
63
|
+
this.backDetail();
|
|
62
64
|
else
|
|
63
65
|
this.done();
|
|
64
66
|
this.tui.requestRender();
|
|
@@ -84,12 +86,22 @@ export class ActorInspectorOverlay {
|
|
|
84
86
|
else if (matchesKey(data, "down"))
|
|
85
87
|
this.detailScroll += 1;
|
|
86
88
|
else if (matchesKey(data, "left"))
|
|
87
|
-
this.
|
|
89
|
+
this.backDetail();
|
|
90
|
+
else if ((matchesKey(data, "right") || matchesKey(data, "return")) &&
|
|
91
|
+
this.detailView === "evidence" &&
|
|
92
|
+
this.tab === "turns") {
|
|
93
|
+
this.detailView = "transcript";
|
|
94
|
+
this.detailScroll = 0;
|
|
95
|
+
}
|
|
88
96
|
this.tui.requestRender();
|
|
89
97
|
return;
|
|
90
98
|
}
|
|
91
99
|
if (this.focus === "runs") {
|
|
92
|
-
if (matchesKey(data, "
|
|
100
|
+
if (matchesKey(data, "left"))
|
|
101
|
+
this.cycleRun(runs, -1);
|
|
102
|
+
else if (matchesKey(data, "right"))
|
|
103
|
+
this.cycleRun(runs, 1);
|
|
104
|
+
else if (matchesKey(data, "down"))
|
|
93
105
|
this.focus = "tabs";
|
|
94
106
|
else if (matchesKey(data, "return") && runs.length > 0)
|
|
95
107
|
this.openRunMenu();
|
|
@@ -131,7 +143,7 @@ export class ActorInspectorOverlay {
|
|
|
131
143
|
lines.push(this.row(this.renderRunControl(), innerWidth));
|
|
132
144
|
let selector = this.focus === "select"
|
|
133
145
|
? this.menuLevel === "run"
|
|
134
|
-
? this.renderMenuBox(this.runs().map((run) =>
|
|
146
|
+
? this.renderMenuBox(this.runs().map((run, index, runs) => `#${runs.length - index} ${run.run} ${run.status}`), this.selectorIndex, undefined, undefined, false, false, this.contentViewportRows())
|
|
135
147
|
: this.renderFilterMenus(innerWidth)
|
|
136
148
|
: [];
|
|
137
149
|
const selectorAnchor = this.selectorAnchor();
|
|
@@ -160,11 +172,13 @@ export class ActorInspectorOverlay {
|
|
|
160
172
|
: this.selectedRun
|
|
161
173
|
? this.renderTimeline(innerWidth)
|
|
162
174
|
: this.renderRunSelector(innerWidth);
|
|
163
|
-
const
|
|
175
|
+
const contentViewportRows = this.contentViewportRows();
|
|
164
176
|
const selectorRows = selector.slice(1);
|
|
165
|
-
for (let index = 0; index < Math.max(content.length,
|
|
177
|
+
for (let index = 0; index < Math.max(content.length, contentViewportRows); index += 1) {
|
|
166
178
|
const base = content[index] ?? "";
|
|
167
|
-
const stripeIndex =
|
|
179
|
+
const stripeIndex = index < content.length
|
|
180
|
+
? this.contentStripeIndices[index] ?? index
|
|
181
|
+
: 0;
|
|
168
182
|
const popup = selectorRows[index];
|
|
169
183
|
if (!popup) {
|
|
170
184
|
lines.push(this.stripedRow(base, innerWidth, stripeIndex));
|
|
@@ -195,6 +209,26 @@ export class ActorInspectorOverlay {
|
|
|
195
209
|
if (!this.selectedRun)
|
|
196
210
|
this.selectedRun = runs[0]?.run;
|
|
197
211
|
}
|
|
212
|
+
contentViewportRows() {
|
|
213
|
+
const overlayRows = Math.floor(this.tui.terminal.rows * 0.94);
|
|
214
|
+
return Math.max(4, Math.min(16, overlayRows - 7));
|
|
215
|
+
}
|
|
216
|
+
selectRun(run, index) {
|
|
217
|
+
this.selectedRun = run;
|
|
218
|
+
this.runIndex = index;
|
|
219
|
+
this.rowIndex = 0;
|
|
220
|
+
this.subagentIndex = 0;
|
|
221
|
+
this.communicationFrom = "all";
|
|
222
|
+
}
|
|
223
|
+
cycleRun(runs, direction) {
|
|
224
|
+
if (runs.length === 0)
|
|
225
|
+
return;
|
|
226
|
+
const currentIndex = Math.max(0, runs.findIndex((run) => run.run === this.selectedRun));
|
|
227
|
+
const nextIndex = (currentIndex + direction + runs.length) % runs.length;
|
|
228
|
+
const next = runs[nextIndex];
|
|
229
|
+
if (next)
|
|
230
|
+
this.selectRun(next.run, nextIndex);
|
|
231
|
+
}
|
|
198
232
|
listItemCount() {
|
|
199
233
|
return this.tab === "communications"
|
|
200
234
|
? this.communicationPreviews().length
|
|
@@ -203,7 +237,7 @@ export class ActorInspectorOverlay {
|
|
|
203
237
|
selectorAnchor() {
|
|
204
238
|
if (this.menuLevel === "run") {
|
|
205
239
|
const runControl = stripVTControlCharacters(this.renderRunControl());
|
|
206
|
-
return Math.max(0, runControl.indexOf("Run:") -
|
|
240
|
+
return Math.max(0, runControl.indexOf("Run:") - 2);
|
|
207
241
|
}
|
|
208
242
|
const label = this.tab === "communications" ? "Messages" : "Turns";
|
|
209
243
|
const tabs = stripVTControlCharacters(this.renderTabs());
|
|
@@ -216,17 +250,23 @@ export class ActorInspectorOverlay {
|
|
|
216
250
|
? ` ${hint("↑↓", "option")} ${hint("enter", "apply")} ${hint("←/esc", "back")}`
|
|
217
251
|
: ` ${hint("↑↓", "option")} ${hint("→/enter", "open")} ${hint("←/esc", "back")}`;
|
|
218
252
|
if (this.focus === "detail")
|
|
219
|
-
return
|
|
253
|
+
return this.tab === "turns" && this.detailView === "evidence"
|
|
254
|
+
? ` ${hint("↑↓", "scroll")} ${hint("→/enter", "readable")} ${hint("←/esc", "back")}`
|
|
255
|
+
: ` ${hint("↑↓", "scroll")} ${hint("←/esc", this.detailView === "transcript" ? "evidence" : "back")}`;
|
|
220
256
|
if (this.focus === "list")
|
|
221
257
|
return ` ${hint("↑↓", "row")} ${hint("→/enter", "open")} ${hint("esc", "close")}`;
|
|
258
|
+
if (this.focus === "runs")
|
|
259
|
+
return ` ${hint("←→", "run")} ${hint("↓", "tabs")} ${hint("enter", "list")} ${hint("esc", "close")}`;
|
|
222
260
|
return ` ${hint("←→", "navigate")} ${hint("↑↓", "change row")} ${hint("enter", "select")} ${hint("esc", "close")}`;
|
|
223
261
|
}
|
|
224
262
|
renderRunControl() {
|
|
225
|
-
const
|
|
263
|
+
const runs = this.runs();
|
|
264
|
+
const runIndex = runs.findIndex((item) => item.run === this.selectedRun);
|
|
265
|
+
const run = runs[runIndex];
|
|
226
266
|
const focused = this.focus === "runs";
|
|
227
267
|
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", "
|
|
268
|
+
const prefix = active ? this.theme.fg("accent", " ← ") : " ";
|
|
269
|
+
const suffix = active ? this.theme.fg("accent", " → ") : " ";
|
|
230
270
|
if (!run) {
|
|
231
271
|
const empty = this.theme.fg("muted", `${prefix}Run: none${suffix}`);
|
|
232
272
|
return focused ? this.theme.bg("selectedBg", empty) : empty;
|
|
@@ -238,7 +278,8 @@ export class ActorInspectorOverlay {
|
|
|
238
278
|
: run.status === "done"
|
|
239
279
|
? "success"
|
|
240
280
|
: "muted";
|
|
241
|
-
const
|
|
281
|
+
const sequence = runs.length - runIndex;
|
|
282
|
+
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
283
|
return focused ? this.theme.bg("selectedBg", value) : value;
|
|
243
284
|
}
|
|
244
285
|
activeMessageFilters() {
|
|
@@ -263,8 +304,8 @@ export class ActorInspectorOverlay {
|
|
|
263
304
|
};
|
|
264
305
|
const messageFilters = this.activeMessageFilters();
|
|
265
306
|
const subagent = this.subagents()[this.subagentIndex] ?? "all";
|
|
266
|
-
const messagesLabel = `Messages${messageFilters.length > 0 ? `
|
|
267
|
-
const turnsLabel = `Turns${subagent === "all" ? "" : `
|
|
307
|
+
const messagesLabel = `Messages${messageFilters.length > 0 ? ` (${messageFilters.join(", ")})` : ""}`;
|
|
308
|
+
const turnsLabel = `Turns${subagent === "all" ? "" : ` (${subagent})`}`;
|
|
268
309
|
return `${tab("communications", messagesLabel)} ${tab("turns", turnsLabel)}`;
|
|
269
310
|
}
|
|
270
311
|
renderRunSelector(width) {
|
|
@@ -272,10 +313,10 @@ export class ActorInspectorOverlay {
|
|
|
272
313
|
if (runs.length === 0)
|
|
273
314
|
return [this.theme.fg("muted", " No owned actor runs")];
|
|
274
315
|
this.runIndex = Math.min(this.runIndex, runs.length - 1);
|
|
275
|
-
return runs.slice(0,
|
|
316
|
+
return runs.slice(0, this.contentViewportRows()).map((run, index) => {
|
|
276
317
|
const selected = index === this.runIndex;
|
|
277
318
|
const prefix = selected ? this.theme.fg("accent", " ▶ ") : " ";
|
|
278
|
-
const label = `${prefix}run:${run.run}`;
|
|
319
|
+
const label = `${prefix}#${runs.length - index} run:${run.run}`;
|
|
279
320
|
const status = ` ${run.status}`;
|
|
280
321
|
return selected
|
|
281
322
|
? this.theme.fg("accent", `${label}${status}`)
|
|
@@ -300,16 +341,20 @@ export class ActorInspectorOverlay {
|
|
|
300
341
|
: (this.subagents()[this.subagentIndex] ?? "all") !== "all";
|
|
301
342
|
return [
|
|
302
343
|
this.theme.fg("muted", filtered
|
|
303
|
-
? " No results for the active filters
|
|
344
|
+
? " No results for the active filters. Enter on the tab to adjust."
|
|
304
345
|
: this.tab === "communications"
|
|
305
346
|
? " No messages in this run"
|
|
306
347
|
: " No persisted turns in this run"),
|
|
307
348
|
];
|
|
308
349
|
}
|
|
309
350
|
this.rowIndex = Math.min(this.rowIndex, rows.length - 1);
|
|
310
|
-
const
|
|
311
|
-
const
|
|
312
|
-
|
|
351
|
+
const viewportRows = this.contentViewportRows();
|
|
352
|
+
const start = Math.max(0, Math.min(this.rowIndex - Math.floor(viewportRows / 2), Math.max(0, rows.length - viewportRows)));
|
|
353
|
+
const stripeIndices = this.tab === "communications"
|
|
354
|
+
? this.communicationPreviews().map((preview, index) => Math.max(0, (preview.sequence ?? index + 1) - 1))
|
|
355
|
+
: this.turnItems().map((turn) => Math.max(0, turn.index - 1));
|
|
356
|
+
const visibleRows = rows.slice(start, start + viewportRows);
|
|
357
|
+
this.contentStripeIndices = visibleRows.map((_, offset) => stripeIndices[start + offset] ?? start + offset);
|
|
313
358
|
return visibleRows.map((row, offset) => {
|
|
314
359
|
const index = start + offset;
|
|
315
360
|
const selected = this.focus === "list" && index === this.rowIndex;
|
|
@@ -331,9 +376,11 @@ export class ActorInspectorOverlay {
|
|
|
331
376
|
readKeys: this.readKeys,
|
|
332
377
|
run: this.selectedRun,
|
|
333
378
|
unreadOnly: this.communicationUnread,
|
|
334
|
-
})
|
|
379
|
+
})
|
|
380
|
+
.filter((preview) => this.communicationFrom === "all" ||
|
|
335
381
|
this.communicationActorLabel(preview.from_display ?? preview.from) ===
|
|
336
|
-
this.communicationFrom)
|
|
382
|
+
this.communicationFrom)
|
|
383
|
+
.reverse();
|
|
337
384
|
}
|
|
338
385
|
communicationActorLabel(actor) {
|
|
339
386
|
const value = actor ?? "unknown";
|
|
@@ -352,7 +399,7 @@ export class ActorInspectorOverlay {
|
|
|
352
399
|
const actor = preview.from_display ?? preview.from ?? "unknown";
|
|
353
400
|
const target = preview.channel === "room" ? "#all" : preview.to;
|
|
354
401
|
const text = preview.summary ?? preview.body_preview ?? "(no body)";
|
|
355
|
-
return
|
|
402
|
+
return `#${preview.sequence ?? "?"} ${preview.needs_response ? "! " : ""}${actor} → ${target} ${preview.type} ${text}`;
|
|
356
403
|
});
|
|
357
404
|
}
|
|
358
405
|
renderDetail(width) {
|
|
@@ -379,54 +426,238 @@ export class ActorInspectorOverlay {
|
|
|
379
426
|
const turn = this.detailTurn;
|
|
380
427
|
if (!turn)
|
|
381
428
|
return [this.theme.fg("warning", " Turn row is no longer available")];
|
|
382
|
-
const
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
429
|
+
const logicalLines = this.detailView === "transcript"
|
|
430
|
+
? this.turnTranscriptLines(turn)
|
|
431
|
+
: this.turnEvidenceLines(turn);
|
|
432
|
+
const wrapped = this.wrapDetailLines(logicalLines, width);
|
|
433
|
+
const viewportRows = this.contentViewportRows();
|
|
434
|
+
const maxStart = Math.max(0, wrapped.lines.length - viewportRows);
|
|
435
|
+
this.detailScroll = Math.min(this.detailScroll, maxStart);
|
|
436
|
+
this.contentStripeIndices = wrapped.stripeIndices.slice(this.detailScroll, this.detailScroll + viewportRows);
|
|
437
|
+
return wrapped.lines.slice(this.detailScroll, this.detailScroll + viewportRows);
|
|
438
|
+
}
|
|
439
|
+
wrapDetailLines(lines, width) {
|
|
440
|
+
const wrappedLines = [];
|
|
441
|
+
const stripeIndices = [];
|
|
442
|
+
let sectionIndex = 0;
|
|
443
|
+
for (const line of lines) {
|
|
444
|
+
if (typeof line !== "string") {
|
|
445
|
+
sectionIndex += 1;
|
|
446
|
+
continue;
|
|
447
|
+
}
|
|
448
|
+
const physicalLines = line.replaceAll("\t", " ").split(/\r?\n/);
|
|
449
|
+
for (const physicalLine of physicalLines) {
|
|
450
|
+
if (!physicalLine.trim())
|
|
451
|
+
continue;
|
|
452
|
+
const leading = physicalLine.match(/^ */)?.[0] ?? "";
|
|
453
|
+
const content = physicalLine.slice(leading.length);
|
|
454
|
+
const contentWidth = Math.max(1, width - visibleWidth(leading));
|
|
455
|
+
const wrapped = visibleWidth(physicalLine) <= width
|
|
456
|
+
? [physicalLine]
|
|
457
|
+
: wrapTextWithAnsi(content, contentWidth).map((visualLine) => `${leading}${visualLine}`);
|
|
458
|
+
for (const visualLine of wrapped) {
|
|
459
|
+
if (!visualLine.trim())
|
|
460
|
+
continue;
|
|
461
|
+
wrappedLines.push(visualLine);
|
|
462
|
+
stripeIndices.push(sectionIndex);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
return { lines: wrappedLines, stripeIndices };
|
|
467
|
+
}
|
|
468
|
+
detailSection(title, content) {
|
|
469
|
+
if (content.length === 0)
|
|
470
|
+
return [];
|
|
471
|
+
return [
|
|
472
|
+
` ${title}`,
|
|
473
|
+
...content.flatMap((line) => line
|
|
474
|
+
.split(/\r?\n/)
|
|
475
|
+
.filter((physicalLine) => physicalLine.trim().length > 0)
|
|
476
|
+
.map((physicalLine) => ` ${physicalLine}`)),
|
|
477
|
+
DETAIL_SECTION_BREAK,
|
|
478
|
+
];
|
|
479
|
+
}
|
|
480
|
+
readableValueLines(value, indent = "", depth = 0) {
|
|
481
|
+
if (depth > 8)
|
|
482
|
+
return [`${indent}[nested value]`];
|
|
483
|
+
if (typeof value === "string") {
|
|
484
|
+
const trimmed = value.trim();
|
|
485
|
+
if (/^[{[]/.test(trimmed)) {
|
|
486
|
+
try {
|
|
487
|
+
return this.readableValueLines(JSON.parse(trimmed), indent, depth + 1);
|
|
488
|
+
}
|
|
489
|
+
catch {
|
|
490
|
+
// Keep non-JSON text as written.
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
return value.split(/\r?\n/).map((line) => `${indent}${line}`);
|
|
494
|
+
}
|
|
495
|
+
if (value === null || value === undefined)
|
|
496
|
+
return [`${indent}${String(value)}`];
|
|
497
|
+
if (typeof value !== "object")
|
|
498
|
+
return [`${indent}${String(value)}`];
|
|
499
|
+
if (Array.isArray(value)) {
|
|
500
|
+
if (value.length === 0)
|
|
501
|
+
return [`${indent}(empty)`];
|
|
502
|
+
return value.flatMap((item) => {
|
|
503
|
+
if (item &&
|
|
504
|
+
typeof item === "object" &&
|
|
505
|
+
item.type === "text" &&
|
|
506
|
+
typeof item.text === "string") {
|
|
507
|
+
return this.readableValueLines(item.text, indent, depth + 1);
|
|
508
|
+
}
|
|
509
|
+
if (!item || typeof item !== "object")
|
|
510
|
+
return [`${indent}- ${String(item)}`];
|
|
511
|
+
return [
|
|
512
|
+
`${indent}-`,
|
|
513
|
+
...this.readableValueLines(item, `${indent} `, depth + 1),
|
|
514
|
+
];
|
|
515
|
+
});
|
|
516
|
+
}
|
|
517
|
+
const entries = Object.entries(value);
|
|
518
|
+
if (entries.length === 0)
|
|
519
|
+
return [`${indent}(empty)`];
|
|
520
|
+
return entries.flatMap(([key, item]) => {
|
|
521
|
+
if (!item || typeof item !== "object") {
|
|
522
|
+
const scalar = typeof item === "string" ? item : String(item);
|
|
523
|
+
const [first = "", ...rest] = scalar.split(/\r?\n/);
|
|
524
|
+
return [
|
|
525
|
+
`${indent}${key}: ${first}`,
|
|
526
|
+
...rest.map((line) => `${indent} ${line}`),
|
|
527
|
+
];
|
|
528
|
+
}
|
|
529
|
+
return [
|
|
530
|
+
`${indent}${key}:`,
|
|
531
|
+
...this.readableValueLines(item, `${indent} `, depth + 1),
|
|
532
|
+
];
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
turnEvidenceLines(turn) {
|
|
536
|
+
const lines = [];
|
|
537
|
+
const commandNumber = /^command-(\d+)$/.exec(turn.commandId)?.[1];
|
|
538
|
+
const subagent = commandNumber
|
|
539
|
+
? String(Number.parseInt(commandNumber, 10))
|
|
540
|
+
: turn.commandId;
|
|
541
|
+
const stage = turn.stage && turn.stage !== "subagent" && turn.stage !== "command"
|
|
542
|
+
? ` (${turn.stage})`
|
|
543
|
+
: "";
|
|
544
|
+
lines.push(` Subagent ${subagent}${stage}`, DETAIL_SECTION_BREAK);
|
|
545
|
+
if (turn.userText)
|
|
546
|
+
lines.push(...this.detailSection("User", [turn.userText]));
|
|
547
|
+
if (turn.thinking)
|
|
548
|
+
lines.push(...this.detailSection("Thinking", [turn.thinking]));
|
|
549
|
+
if (turn.assistantText)
|
|
550
|
+
lines.push(...this.detailSection("Assistant", [turn.assistantText]));
|
|
551
|
+
for (const [index, tool] of turn.toolCalls.entries()) {
|
|
552
|
+
const content = [
|
|
553
|
+
...(tool.arguments === undefined
|
|
554
|
+
? []
|
|
555
|
+
: ["Input", ...this.readableValueLines(tool.arguments, " ")]),
|
|
556
|
+
...(tool.result === undefined
|
|
557
|
+
? []
|
|
558
|
+
: ["Result", ...this.readableValueLines(tool.result, " ")]),
|
|
559
|
+
`call id: ${tool.id}`,
|
|
560
|
+
];
|
|
561
|
+
lines.push(...this.detailSection(`Tool ${index + 1} (${tool.name}${tool.resultError ? ", Error" : ""})`, content));
|
|
562
|
+
}
|
|
563
|
+
const execution = [
|
|
564
|
+
...(!turn.thinking ? ["thinking: not persisted"] : []),
|
|
565
|
+
...(turn.model ? [`model: ${turn.model}`] : []),
|
|
566
|
+
...(turn.stopReason ? [`stop reason: ${turn.stopReason}`] : []),
|
|
567
|
+
...(turn.error ? [`error: ${turn.error}`] : []),
|
|
568
|
+
...(turn.usage === undefined
|
|
569
|
+
? []
|
|
570
|
+
: ["Usage", ...this.readableValueLines(turn.usage, " ")]),
|
|
571
|
+
...(turn.unmatchedToolResults > 0
|
|
572
|
+
? [`unmatched tool results: ${turn.unmatchedToolResults}`]
|
|
388
573
|
: []),
|
|
389
|
-
|
|
390
|
-
|
|
574
|
+
];
|
|
575
|
+
if (execution.length > 0)
|
|
576
|
+
lines.push(...this.detailSection("Execution", execution));
|
|
577
|
+
if (turn.diagnostics.length > 0)
|
|
578
|
+
lines.push(...this.detailSection("Diagnostics", turn.diagnostics));
|
|
579
|
+
const provenance = [
|
|
580
|
+
"Session",
|
|
581
|
+
` ${turn.sessionFile}`,
|
|
582
|
+
...(turn.sessionTruncated ? [" turn list truncated"] : []),
|
|
583
|
+
...(turn.promptFile
|
|
584
|
+
? [
|
|
585
|
+
"Prompt",
|
|
586
|
+
` ${turn.promptFile}${turn.promptBytes ? ` (${turn.promptBytes} bytes)` : ""}`,
|
|
587
|
+
]
|
|
391
588
|
: []),
|
|
392
|
-
...(turn.
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
...(turn.error ? [` error ${turn.error}`] : []),
|
|
399
|
-
` tools ${turn.toolCalls.length}`,
|
|
589
|
+
...(turn.recipeContext === undefined
|
|
590
|
+
? []
|
|
591
|
+
: [
|
|
592
|
+
"Recipe Context",
|
|
593
|
+
...this.readableValueLines(turn.recipeContext, " "),
|
|
594
|
+
]),
|
|
400
595
|
];
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
596
|
+
lines.push(...this.detailSection("Provenance", provenance));
|
|
597
|
+
return lines.length > 0 ? lines : [" No persisted turn evidence"];
|
|
598
|
+
}
|
|
599
|
+
readablePromptText(text) {
|
|
600
|
+
const wrapped = /^\s*<file\b[^>]*>\s*([\s\S]*?)\s*<\/file>\s*$/i.exec(text);
|
|
601
|
+
return wrapped?.[1]?.trim() || text;
|
|
602
|
+
}
|
|
603
|
+
turnTranscriptLines(turn) {
|
|
604
|
+
const lines = [];
|
|
605
|
+
if (turn.userText)
|
|
606
|
+
lines.push(...this.detailSection("User", [this.readablePromptText(turn.userText)]));
|
|
607
|
+
if (turn.thinking)
|
|
608
|
+
lines.push(...this.detailSection("Thinking", [turn.thinking]));
|
|
609
|
+
if (turn.assistantText)
|
|
610
|
+
lines.push(...this.detailSection("Assistant", [turn.assistantText]));
|
|
611
|
+
for (const tool of turn.toolCalls) {
|
|
612
|
+
const content = [
|
|
613
|
+
...(tool.arguments === undefined
|
|
614
|
+
? []
|
|
615
|
+
: ["Input", ...this.readableValueLines(tool.arguments, " ")]),
|
|
616
|
+
...(tool.result === undefined
|
|
617
|
+
? []
|
|
618
|
+
: ["Result", ...this.readableValueLines(tool.result, " ")]),
|
|
619
|
+
];
|
|
620
|
+
lines.push(...this.detailSection(`Tool (${tool.name}${tool.resultError ? ", Error" : ""})`, content));
|
|
407
621
|
}
|
|
408
|
-
if (turn.
|
|
409
|
-
|
|
410
|
-
|
|
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, "…"));
|
|
622
|
+
if (turn.error)
|
|
623
|
+
lines.push(...this.detailSection("Error", [turn.error]));
|
|
624
|
+
return lines.length > 0 ? lines : [" No readable turn content"];
|
|
419
625
|
}
|
|
420
626
|
turnItems() {
|
|
421
627
|
if (!this.selectedRun)
|
|
422
628
|
return [];
|
|
423
629
|
const selectedSubagent = this.subagents()[this.subagentIndex];
|
|
424
|
-
return Inspector.readActorInspectorTurns(path.join(this.stateRoot, this.selectedRun))
|
|
630
|
+
return Inspector.readActorInspectorTurns(path.join(this.stateRoot, this.selectedRun))
|
|
631
|
+
.filter((turn) => !selectedSubagent ||
|
|
425
632
|
selectedSubagent === "all" ||
|
|
426
|
-
turn.commandId === selectedSubagent)
|
|
633
|
+
turn.commandId === selectedSubagent)
|
|
634
|
+
.reverse();
|
|
427
635
|
}
|
|
428
636
|
turnRows() {
|
|
429
|
-
return this.turnItems().map((turn) =>
|
|
637
|
+
return this.turnItems().map((turn) => {
|
|
638
|
+
const commandNumber = /^command-(\d+)$/.exec(turn.commandId)?.[1];
|
|
639
|
+
const subagent = commandNumber
|
|
640
|
+
? `Subagent ${Number.parseInt(commandNumber, 10)}`
|
|
641
|
+
: `Subagent ${turn.commandId}`;
|
|
642
|
+
const stage = turn.stage && turn.stage !== "subagent" && turn.stage !== "command"
|
|
643
|
+
? ` (${turn.stage})`
|
|
644
|
+
: "";
|
|
645
|
+
const toolNames = [...new Set(turn.toolCalls.map((tool) => tool.name))];
|
|
646
|
+
const action = turn.toolCalls.length === 0
|
|
647
|
+
? ""
|
|
648
|
+
: turn.toolCalls.length === 1
|
|
649
|
+
? ` (${toolNames[0]})`
|
|
650
|
+
: toolNames.length === 1
|
|
651
|
+
? ` (${toolNames[0]}, ${turn.toolCalls.length} calls)`
|
|
652
|
+
: turn.toolCalls.length <= 2
|
|
653
|
+
? ` (${toolNames.join(", ")})`
|
|
654
|
+
: ` (${turn.toolCalls.length} tools)`;
|
|
655
|
+
const failed = turn.error || turn.toolCalls.some((tool) => tool.resultError)
|
|
656
|
+
? " (error)"
|
|
657
|
+
: "";
|
|
658
|
+
const preview = this.readablePromptText(turn.assistantText ?? turn.userText ?? "(no text)").replaceAll(/\s+/g, " ").trim();
|
|
659
|
+
return `#${turn.index} ${subagent}${stage} ${turn.model ?? "unknown model"}${action}${failed} ${preview}`;
|
|
660
|
+
});
|
|
430
661
|
}
|
|
431
662
|
subagents() {
|
|
432
663
|
if (!this.selectedRun || this.tab !== "turns")
|
|
@@ -478,11 +709,7 @@ export class ActorInspectorOverlay {
|
|
|
478
709
|
if (!value)
|
|
479
710
|
return;
|
|
480
711
|
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";
|
|
712
|
+
this.selectRun(value, Math.max(0, this.runs().findIndex((run) => run.run === value)));
|
|
486
713
|
this.focus = "runs";
|
|
487
714
|
return;
|
|
488
715
|
}
|
|
@@ -518,12 +745,20 @@ export class ActorInspectorOverlay {
|
|
|
518
745
|
currentFilterValue() {
|
|
519
746
|
return this.filterValue(this.filterControlIndex);
|
|
520
747
|
}
|
|
521
|
-
renderMenuBox(options, focusedIndex, current, parentIndex, omitLeftBorder = false, omitRightBorder = false) {
|
|
522
|
-
const
|
|
523
|
-
const
|
|
748
|
+
renderMenuBox(options, focusedIndex, current, parentIndex, omitLeftBorder = false, omitRightBorder = false, maxVisibleOptions = options.length) {
|
|
749
|
+
const visibleLimit = Math.max(1, Math.min(maxVisibleOptions, options.length));
|
|
750
|
+
const anchorIndex = Math.max(0, Math.min(options.length - 1, focusedIndex >= 0 ? focusedIndex : (parentIndex ?? 0)));
|
|
751
|
+
const maxStart = Math.max(0, options.length - visibleLimit);
|
|
752
|
+
const start = Math.max(0, Math.min(anchorIndex - Math.floor(visibleLimit / 2), maxStart));
|
|
753
|
+
const visibleOptions = options.slice(start, start + visibleLimit);
|
|
754
|
+
const hiddenAbove = start > 0;
|
|
755
|
+
const hiddenBelow = start + visibleOptions.length < options.length;
|
|
756
|
+
const contentWidth = Math.max(4, ...options.map((option) => visibleWidth(option) + 4));
|
|
757
|
+
const border = (left, right, marker = "") => this.theme.fg("border", `${omitLeftBorder ? "" : left}${marker}${"─".repeat(Math.max(0, contentWidth - visibleWidth(marker)))}${omitRightBorder ? "" : right}`);
|
|
524
758
|
return [
|
|
525
|
-
border("╭", "╮"),
|
|
526
|
-
...
|
|
759
|
+
border("╭", "╮", hiddenAbove ? "↑" : ""),
|
|
760
|
+
...visibleOptions.map((option, localIndex) => {
|
|
761
|
+
const index = start + localIndex;
|
|
527
762
|
const focused = index === focusedIndex;
|
|
528
763
|
const row = this.fit(`${focused ? " ▶ " : " "}${option}`, contentWidth);
|
|
529
764
|
const colored = focused || option === current || index === parentIndex
|
|
@@ -534,17 +769,18 @@ export class ActorInspectorOverlay {
|
|
|
534
769
|
: colored;
|
|
535
770
|
return `${omitLeftBorder ? "" : this.theme.fg("border", "│")}${styled}${omitRightBorder ? "" : this.theme.fg("border", "│")}`;
|
|
536
771
|
}),
|
|
537
|
-
border("╰", "╯"),
|
|
772
|
+
border("╰", "╯", hiddenBelow ? "↓" : ""),
|
|
538
773
|
];
|
|
539
774
|
}
|
|
540
775
|
renderFilterMenus(_width) {
|
|
541
776
|
const filters = this.filterOptions().map((filter, index) => `${filter}: ${this.filterValue(index)}`);
|
|
777
|
+
const maxVisibleOptions = Math.max(1, this.contentViewportRows() - 1);
|
|
542
778
|
const parentFocus = this.menuLevel === "filter" ? this.selectorIndex : -1;
|
|
543
|
-
const parent = this.renderMenuBox(filters, parentFocus, undefined, this.menuLevel === "value" ? this.filterControlIndex : undefined);
|
|
779
|
+
const parent = this.renderMenuBox(filters, parentFocus, undefined, this.menuLevel === "value" ? this.filterControlIndex : undefined, false, false, maxVisibleOptions);
|
|
544
780
|
if (this.menuLevel !== "value")
|
|
545
781
|
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());
|
|
782
|
+
const parentShared = this.renderMenuBox(filters, parentFocus, undefined, this.filterControlIndex, false, true, maxVisibleOptions);
|
|
783
|
+
const child = this.renderMenuBox(this.valueOptions(), this.selectorIndex, this.currentFilterValue(), undefined, false, false, maxVisibleOptions);
|
|
548
784
|
return Array.from({ length: Math.max(parent.length, child.length) }, (_, index) => child[index]
|
|
549
785
|
? `${parentShared[index] ?? " ".repeat(visibleWidth(parentShared[0]))}${child[index]}`
|
|
550
786
|
: (parent[index] ?? ""));
|
|
@@ -559,15 +795,25 @@ export class ActorInspectorOverlay {
|
|
|
559
795
|
else
|
|
560
796
|
this.detailTurn = this.turnItems()[this.rowIndex];
|
|
561
797
|
this.detailScroll = 0;
|
|
798
|
+
this.detailView = "evidence";
|
|
562
799
|
this.detailOpen = Boolean(this.detailCommunication || this.detailTurn);
|
|
563
800
|
if (this.detailOpen)
|
|
564
801
|
this.focus = "detail";
|
|
565
802
|
}
|
|
803
|
+
backDetail() {
|
|
804
|
+
if (this.detailView === "transcript") {
|
|
805
|
+
this.detailView = "evidence";
|
|
806
|
+
this.detailScroll = 0;
|
|
807
|
+
return;
|
|
808
|
+
}
|
|
809
|
+
this.closeDetail();
|
|
810
|
+
}
|
|
566
811
|
closeDetail() {
|
|
567
812
|
this.detailOpen = false;
|
|
568
813
|
this.detailCommunication = undefined;
|
|
569
814
|
this.detailTurn = undefined;
|
|
570
815
|
this.detailScroll = 0;
|
|
816
|
+
this.detailView = "evidence";
|
|
571
817
|
this.focus = "list";
|
|
572
818
|
}
|
|
573
819
|
border(left, title, right, width) {
|
package/dist/lib/inspector.js
CHANGED
|
@@ -31,7 +31,10 @@ export function readActorInspectorRuns(stateRoot, ownerId) {
|
|
|
31
31
|
: {}),
|
|
32
32
|
}];
|
|
33
33
|
})
|
|
34
|
-
.sort((left, right) =>
|
|
34
|
+
.sort((left, right) => {
|
|
35
|
+
const recency = String(right.updatedAt ?? "").localeCompare(String(left.updatedAt ?? ""));
|
|
36
|
+
return recency || right.run.localeCompare(left.run);
|
|
37
|
+
});
|
|
35
38
|
}
|
|
36
39
|
catch (error) {
|
|
37
40
|
if (error.code === "ENOENT")
|