@llblab/pi-kit 0.1.4 → 0.1.6
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/CHANGELOG.md +9 -0
- package/README.md +5 -5
- package/node_modules/@llblab/pi-actors/BACKLOG.md +1 -1
- package/node_modules/@llblab/pi-actors/CHANGELOG.md +14 -1
- package/node_modules/@llblab/pi-actors/README.md +19 -3
- package/node_modules/@llblab/pi-actors/dist/lib/extension-runtime.js +29 -10
- package/node_modules/@llblab/pi-actors/dist/lib/inspector-overlay.d.ts +3 -0
- package/node_modules/@llblab/pi-actors/dist/lib/inspector-overlay.js +120 -38
- package/node_modules/@llblab/pi-actors/dist/lib/limits.d.ts +1 -0
- package/node_modules/@llblab/pi-actors/dist/lib/limits.js +1 -0
- package/node_modules/@llblab/pi-actors/dist/lib/observability.js +6 -1
- package/node_modules/@llblab/pi-actors/dist/lib/run-ui-runtime.d.ts +10 -3
- package/node_modules/@llblab/pi-actors/dist/lib/run-ui-runtime.js +109 -52
- package/node_modules/@llblab/pi-actors/dist/lib/runs-artifacts.js +24 -4
- package/node_modules/@llblab/pi-actors/dist/lib/runtime.d.ts +2 -0
- package/node_modules/@llblab/pi-actors/dist/lib/runtime.js +24 -4
- package/node_modules/@llblab/pi-actors/dist/lib/session-evidence.d.ts +1 -0
- package/node_modules/@llblab/pi-actors/dist/lib/session-evidence.js +3 -2
- package/node_modules/@llblab/pi-actors/dist/lib/state-readers.d.ts +5 -1
- package/node_modules/@llblab/pi-actors/dist/lib/state-readers.js +30 -3
- package/node_modules/@llblab/pi-actors/dist/lib/tools-access.d.ts +1 -0
- package/node_modules/@llblab/pi-actors/dist/lib/tools-access.js +13 -11
- package/node_modules/@llblab/pi-actors/dist/lib/tools-inspect.js +5 -5
- package/node_modules/@llblab/pi-actors/dist/lib/tools-message.d.ts +1 -0
- package/node_modules/@llblab/pi-actors/dist/lib/tools-message.js +3 -1
- package/node_modules/@llblab/pi-actors/docs/README.md +1 -0
- package/node_modules/@llblab/pi-actors/docs/actor-inspector.md +21 -5
- package/node_modules/@llblab/pi-actors/docs/async-runs.md +16 -4
- package/node_modules/@llblab/pi-actors/docs/command-templates.md +5 -4
- package/node_modules/@llblab/pi-actors/docs/inspection.md +83 -0
- package/node_modules/@llblab/pi-actors/docs/recipe-library.md +1 -1
- package/node_modules/@llblab/pi-actors/docs/template-recipes.md +225 -66
- package/node_modules/@llblab/pi-actors/docs/tool-registry.md +24 -3
- package/node_modules/@llblab/pi-actors/lib/extension-runtime.ts +26 -10
- package/node_modules/@llblab/pi-actors/lib/inspector-overlay.ts +161 -33
- package/node_modules/@llblab/pi-actors/lib/limits.ts +1 -0
- package/node_modules/@llblab/pi-actors/lib/observability.ts +5 -1
- package/node_modules/@llblab/pi-actors/lib/run-ui-runtime.ts +128 -50
- package/node_modules/@llblab/pi-actors/lib/runs-artifacts.ts +34 -4
- package/node_modules/@llblab/pi-actors/lib/runtime.ts +26 -7
- package/node_modules/@llblab/pi-actors/lib/session-evidence.ts +7 -2
- package/node_modules/@llblab/pi-actors/lib/state-readers.ts +45 -3
- package/node_modules/@llblab/pi-actors/lib/tools-access.ts +26 -12
- package/node_modules/@llblab/pi-actors/lib/tools-inspect.ts +9 -5
- package/node_modules/@llblab/pi-actors/lib/tools-message.ts +8 -1
- package/node_modules/@llblab/pi-actors/package.json +1 -1
- package/node_modules/@llblab/pi-telegram/AGENTS.md +1 -1
- package/node_modules/@llblab/pi-telegram/CHANGELOG.md +13 -0
- package/node_modules/@llblab/pi-telegram/README.md +1 -1
- package/node_modules/@llblab/pi-telegram/docs/architecture.md +3 -3
- package/node_modules/@llblab/pi-telegram/index.ts +25 -0
- package/node_modules/@llblab/pi-telegram/lib/journal.ts +192 -9
- package/node_modules/@llblab/pi-telegram/lib/runtime.ts +52 -2
- package/node_modules/@llblab/pi-telegram/lib/status.ts +3 -3
- package/node_modules/@llblab/pi-telegram/lib/telegram-api.ts +5 -1
- package/node_modules/@llblab/pi-telegram/package.json +1 -1
- package/node_modules/@llblab/pi-telegram/skills/generated-control-surface/SKILL.md +14 -6
- package/package.json +4 -4
|
@@ -22,9 +22,21 @@ Specialize a maintained Recipe without copying its contract:
|
|
|
22
22
|
register_tool name=music_player from=music-player/playback defaults={"source":"~/Music/1MIX"}
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
-
`from`, `template`, and `draft` are distinct source modes. `from` accepts exact `<skill>/<recipe>` identity or an explicit `.json` / `.md` path and inherits async behavior, args/types, source defaults, artifacts, Control, and runtime-owned origins. `defaults` may set only effective caller-owned args and must satisfy their types. `template` is only for trusted command definitions;
|
|
25
|
+
`from`, `template`, and `draft` are distinct source modes. `from` accepts exact `<skill>/<recipe>` identity or an explicit `.json` / `.md` path and inherits async behavior, args/types, source defaults, artifacts, Control, and runtime-owned origins. `defaults` may set only effective caller-owned args and must satisfy their types. `template` is only for trusted command definitions; specialization accepts caller `defaults`, while authored composition values belong in a Recipe file.
|
|
26
26
|
|
|
27
|
-
Promote an immutable captured draft only with its draft path and explicit target name
|
|
27
|
+
Promote an immutable captured draft only with its draft path and explicit target name:
|
|
28
|
+
|
|
29
|
+
```text
|
|
30
|
+
register_tool name=repo_check draft=~/.pi/agent/recipes/drafts/<captured-recipe>.json
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Delete a persisted definition through the same fenced mutation path:
|
|
34
|
+
|
|
35
|
+
```text
|
|
36
|
+
register_tool name=repo_check template=null
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
An empty `template` string is the equivalent deletion form. Do not delete registry files manually. Host-visible dynamic definitions may remain visible until reload because Pi cannot unregister them, but extension-local lookup rejects the deleted definition immediately. Name collisions for creation or replacement require `update=true`. Invalid content fails before active mutation.
|
|
28
40
|
|
|
29
41
|
Registration resolves the effective delegated contract before persistence, then reports logical `source`, effective `required_args` / `optional_args`, and distinct `persisted`, `registry_active`, `host_registered`, `active_tool`, and `callable_now` states. A callable result points to the actual generated tool as the next action. An uncallable result names its `activation_boundary` and status/doctor action without suggesting spawn as a substitute. Diagnose one maintained source with `inspect target=recipes view=doctor identity=<skill>/<recipe>`; diagnose final activation, source, schema summary, and separate spawn/tool usage with `inspect target=tool:<name> view=status`. Treat the tool as callable in the current session only when `callable_now` is true; persistence alone is not activation proof. Registration results omit raw persisted paths and executable template/config payloads.
|
|
30
42
|
|
|
@@ -41,10 +53,19 @@ inspect target=recipes view=status
|
|
|
41
53
|
inspect target=tool:<name> view=status
|
|
42
54
|
```
|
|
43
55
|
|
|
44
|
-
Recipe inspection reports generation, scan/watch state, active, shadowed, invalid, disabled, component rejection, diagnostic, risk, usage, and review evidence. Tool status reports current activation plus separate `tool_calls` and `spawn_calls`; tool schema reports the caller-owned capability contract. A registered tool is not a running actor.
|
|
56
|
+
Recipe inspection reports generation, scan/watch state, active, shadowed, invalid, disabled, component rejection, diagnostic, risk, usage, and review evidence. Tool status reports current activation plus separate `tool_calls` and `spawn_calls`; tool schema reports the caller-owned capability contract. Inspect the complete target/view contract in [Management Inspection](./inspection.md). A registered tool is not a running actor.
|
|
45
57
|
|
|
46
58
|
`spawn recipe=<name>` executes a Recipe and reports `launch_kind: "spawn"`; it does not prove that a registered tool was exposed or invoked. Registered-tool execution reports `launch_kind: "tool"`.
|
|
47
59
|
|
|
60
|
+
Async registered tools add runtime-owned optional invocation parameters after resolving the Recipe contract:
|
|
61
|
+
|
|
62
|
+
| Parameter | Availability | Purpose |
|
|
63
|
+
| --- | --- | --- |
|
|
64
|
+
| `run_id` | non-singleton async tools | Overrides the generated logical Run id for this invocation |
|
|
65
|
+
| `transport_context` | all async tools | Preserves an originating transport route for detached terminal follow-up |
|
|
66
|
+
|
|
67
|
+
These parameters are not Recipe-authored args. Singleton tools do not expose `run_id` because their canonical Run identity is owned by the Skill Recipe.
|
|
68
|
+
|
|
48
69
|
## Automatic Review
|
|
49
70
|
|
|
50
71
|
Automatic draft/tool review remains silent and mechanically fenced:
|
|
@@ -35,6 +35,8 @@ export function createActorExtensionRuntime(
|
|
|
35
35
|
pi: Pi.ExtensionAPI,
|
|
36
36
|
): ActorExtensionRuntime {
|
|
37
37
|
let activeRunContext: Pi.ExtensionContext | undefined;
|
|
38
|
+
let activeRunOwnerId: string | undefined;
|
|
39
|
+
const runOwnerIdsByContext = new WeakMap<Pi.ExtensionContext, string>();
|
|
38
40
|
const recipeResolutionContextsBySession = new Map<
|
|
39
41
|
string,
|
|
40
42
|
RecipeResolution.RecipeResolutionContext
|
|
@@ -55,9 +57,20 @@ export function createActorExtensionRuntime(
|
|
|
55
57
|
getRunOwnerId,
|
|
56
58
|
getThinkingLevel: () => pi.getThinkingLevel(),
|
|
57
59
|
});
|
|
58
|
-
|
|
60
|
+
let recipeReload: Runtime.RecipeToolReloadWatcher | undefined;
|
|
61
|
+
let runUiRuntime: RunUiRuntime.RunUiRuntime;
|
|
62
|
+
const closeActiveSessionRuntimes = (): void => {
|
|
63
|
+
const ownerId = activeRunOwnerId;
|
|
64
|
+
activeRunContext = undefined;
|
|
65
|
+
activeRunOwnerId = undefined;
|
|
66
|
+
runUiRuntime?.close();
|
|
67
|
+
automaticReview.close();
|
|
68
|
+
recipeReload?.close();
|
|
69
|
+
if (ownerId) recipeResolutionContextsBySession.delete(ownerId);
|
|
70
|
+
};
|
|
71
|
+
runUiRuntime = RunUiRuntime.createRunUiRuntime({
|
|
59
72
|
getActiveContext: () => activeRunContext,
|
|
60
|
-
|
|
73
|
+
onCallbackError: closeActiveSessionRuntimes,
|
|
61
74
|
onRunEvent: automaticReview.schedule,
|
|
62
75
|
pi,
|
|
63
76
|
});
|
|
@@ -102,11 +115,12 @@ export function createActorExtensionRuntime(
|
|
|
102
115
|
reservedToolNames: Tools.RESERVED_TOOL_NAMES,
|
|
103
116
|
setActiveTools: (toolNames) => pi.setActiveTools(toolNames),
|
|
104
117
|
});
|
|
105
|
-
|
|
118
|
+
recipeReload = Runtime.createRecipeToolReloadWatcher(runtime, {
|
|
106
119
|
getResolutionContext: () =>
|
|
107
120
|
activeRunContext
|
|
108
121
|
? getRecipeResolutionContext(activeRunContext)
|
|
109
122
|
: undefined,
|
|
123
|
+
onCallbackError: closeActiveSessionRuntimes,
|
|
110
124
|
});
|
|
111
125
|
return {
|
|
112
126
|
beforeAgentStart(systemPrompt, skills, ctx) {
|
|
@@ -131,11 +145,11 @@ export function createActorExtensionRuntime(
|
|
|
131
145
|
if (activeRunContext === ctx) automaticReview.schedule();
|
|
132
146
|
},
|
|
133
147
|
onSessionShutdown(reason, ctx) {
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
runUiRuntime.shutdown(reason, ctx);
|
|
148
|
+
const ownerId = runOwnerIdsByContext.get(ctx);
|
|
149
|
+
runOwnerIdsByContext.delete(ctx);
|
|
150
|
+
if (activeRunContext === ctx) closeActiveSessionRuntimes();
|
|
151
|
+
if (ownerId) recipeResolutionContextsBySession.delete(ownerId);
|
|
152
|
+
runUiRuntime.shutdown(reason, ownerId, ctx);
|
|
139
153
|
},
|
|
140
154
|
async onSessionStart(ctx) {
|
|
141
155
|
const sessionId = getRunOwnerId(ctx);
|
|
@@ -145,13 +159,15 @@ export function createActorExtensionRuntime(
|
|
|
145
159
|
);
|
|
146
160
|
ctx.ui.setWidget("zz-pi-actors-comms", undefined);
|
|
147
161
|
activeRunContext = ctx;
|
|
162
|
+
activeRunOwnerId = sessionId;
|
|
163
|
+
runOwnerIdsByContext.set(ctx, sessionId);
|
|
148
164
|
runUiRuntime.close();
|
|
149
165
|
automaticReview.close();
|
|
150
166
|
recipeReload.close();
|
|
151
167
|
await Temp.prepareExtensionTempDir(Paths.EXTENSION_RUNTIME_PATHS.tempDir);
|
|
152
|
-
if (activeRunContext !== ctx) return;
|
|
168
|
+
if (activeRunContext !== ctx || activeRunOwnerId !== sessionId) return;
|
|
153
169
|
automaticReview.start(ctx);
|
|
154
|
-
runUiRuntime.start(ctx);
|
|
170
|
+
runUiRuntime.start(ctx, sessionId);
|
|
155
171
|
recipeReload.watch(ctx);
|
|
156
172
|
},
|
|
157
173
|
registerCoreTools() {
|
|
@@ -10,6 +10,7 @@ import * as path from "node:path";
|
|
|
10
10
|
import type { Theme } from "@earendil-works/pi-coding-agent";
|
|
11
11
|
import {
|
|
12
12
|
matchesKey,
|
|
13
|
+
sliceByColumn,
|
|
13
14
|
truncateToWidth,
|
|
14
15
|
visibleWidth,
|
|
15
16
|
type TUI,
|
|
@@ -30,6 +31,11 @@ export type ActorInspectorTab = "recipe" | "trace" | "control";
|
|
|
30
31
|
type InspectorFocus = "runs" | "tabs" | "document" | "list" | "detail" | "select";
|
|
31
32
|
type SelectorMode = "run" | "source";
|
|
32
33
|
|
|
34
|
+
interface SelectorOption {
|
|
35
|
+
content: string;
|
|
36
|
+
preserveColors?: boolean;
|
|
37
|
+
}
|
|
38
|
+
|
|
33
39
|
export interface ActorInspectorActionResult {
|
|
34
40
|
ok: boolean;
|
|
35
41
|
message: string;
|
|
@@ -73,6 +79,7 @@ export class ActorInspectorOverlay {
|
|
|
73
79
|
private readonly theme: Theme;
|
|
74
80
|
private readonly tui: TUI;
|
|
75
81
|
private readonly refreshTimer: NodeJS.Timeout;
|
|
82
|
+
private contentSelectedRows = new Set<number>();
|
|
76
83
|
private contentStripeIndices: number[] = [];
|
|
77
84
|
private documentScroll = 0;
|
|
78
85
|
private detailScroll = 0;
|
|
@@ -244,10 +251,11 @@ export class ActorInspectorOverlay {
|
|
|
244
251
|
if (this.killConfirmation) return this.renderKillDialog(innerWidth);
|
|
245
252
|
|
|
246
253
|
const lines = [this.border("╭", " Actor Inspector ", "╮", innerWidth)];
|
|
247
|
-
lines.push(this.row(this.renderRunControl(run, runs.length - this.runIndex), innerWidth));
|
|
254
|
+
lines.push(this.row(this.renderRunControl(run, runs.length - this.runIndex - 1), innerWidth));
|
|
248
255
|
lines.push(this.row(this.renderTabs(run), innerWidth));
|
|
249
256
|
lines.push(this.border("├", "", "┤", innerWidth));
|
|
250
257
|
|
|
258
|
+
this.contentSelectedRows.clear();
|
|
251
259
|
this.contentStripeIndices = [];
|
|
252
260
|
let content = this.renderContent(run, innerWidth);
|
|
253
261
|
if (this.feedback) {
|
|
@@ -257,6 +265,9 @@ export class ActorInspectorOverlay {
|
|
|
257
265
|
? "error"
|
|
258
266
|
: "warning";
|
|
259
267
|
content = [this.theme.fg(color, ` ${this.feedback.message}`), ...content];
|
|
268
|
+
this.contentSelectedRows = new Set(
|
|
269
|
+
[...this.contentSelectedRows].map((index) => index + 1),
|
|
270
|
+
);
|
|
260
271
|
this.contentStripeIndices = [0, ...this.contentStripeIndices];
|
|
261
272
|
}
|
|
262
273
|
const viewportRows = this.contentViewportRows();
|
|
@@ -266,9 +277,27 @@ export class ActorInspectorOverlay {
|
|
|
266
277
|
content[index] ?? "",
|
|
267
278
|
innerWidth,
|
|
268
279
|
this.contentStripeIndices[index] ?? index,
|
|
280
|
+
this.contentSelectedRows.has(index),
|
|
269
281
|
));
|
|
270
282
|
}
|
|
271
283
|
lines.push(this.footerBorder(this.renderKeyHints(), innerWidth));
|
|
284
|
+
if (this.focus === "select") {
|
|
285
|
+
const runSelector = this.selectorMode === "run";
|
|
286
|
+
const startRow = runSelector ? 2 : 3;
|
|
287
|
+
const horizontalOffset = runSelector ? 4 : 12;
|
|
288
|
+
const availableRows = Math.max(3, lines.length - startRow - 1);
|
|
289
|
+
const menu = this.renderSelector(run, availableRows);
|
|
290
|
+
for (const [offset, menuLine] of menu.entries()) {
|
|
291
|
+
const target = startRow + offset;
|
|
292
|
+
if (target >= lines.length - 1) break;
|
|
293
|
+
lines[target] = this.compositeMenuLine(
|
|
294
|
+
lines[target]!,
|
|
295
|
+
menuLine,
|
|
296
|
+
innerWidth,
|
|
297
|
+
horizontalOffset,
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
272
301
|
return lines;
|
|
273
302
|
}
|
|
274
303
|
|
|
@@ -428,10 +457,10 @@ export class ActorInspectorOverlay {
|
|
|
428
457
|
private renderKillDialog(innerWidth: number): string[] {
|
|
429
458
|
const confirmation = this.killConfirmation!;
|
|
430
459
|
const cancel = this.killDialogChoice === "cancel"
|
|
431
|
-
? this.theme.bg("
|
|
460
|
+
? this.theme.bg("selectedBg", this.theme.fg("accent", " Cancel "))
|
|
432
461
|
: this.theme.fg("muted", " Cancel ");
|
|
433
462
|
const kill = this.killDialogChoice === "kill"
|
|
434
|
-
? this.theme.bg("
|
|
463
|
+
? this.theme.bg("selectedBg", this.theme.fg("error", " Kill actor "))
|
|
435
464
|
: this.theme.fg("error", " Kill actor ");
|
|
436
465
|
const body = [
|
|
437
466
|
"",
|
|
@@ -464,23 +493,32 @@ export class ActorInspectorOverlay {
|
|
|
464
493
|
const suffix = active ? this.theme.fg("accent", " → ") : " ";
|
|
465
494
|
if (!run) {
|
|
466
495
|
const empty = this.theme.fg("muted", `${prefix}Run: none${suffix}`);
|
|
467
|
-
return
|
|
496
|
+
return active ? this.theme.bg("selectedBg", empty) : empty;
|
|
468
497
|
}
|
|
469
498
|
const value = `${prefix}${this.theme.fg("muted", "Run: ")}${this.theme.fg("text", `#${sequence}`)} ${this.theme.fg("accent", run.run)} ${this.theme.fg(this.statusColor(run.status), run.status)}${suffix}`;
|
|
470
|
-
return
|
|
499
|
+
return active ? this.theme.bg("selectedBg", value) : value;
|
|
471
500
|
}
|
|
472
501
|
|
|
473
502
|
private renderTabs(run?: ActorInspector.ActorInspectorRunItem): string {
|
|
474
503
|
const source = this.traceSource(run);
|
|
475
504
|
return TABS.map((tab, index) => {
|
|
476
505
|
const base = `${tab[0]!.toUpperCase()}${tab.slice(1)}`;
|
|
477
|
-
const label = tab === "trace" && source !== "all" ? `${base}
|
|
506
|
+
const label = tab === "trace" && source !== "all" ? `${base}: ${source}` : base;
|
|
478
507
|
const selected = index === this.tabIndex;
|
|
479
|
-
const
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
508
|
+
const bracketed = selected && this.focus !== "runs";
|
|
509
|
+
if (!selected) {
|
|
510
|
+
const display = bracketed ? `[ ${label} ]` : ` ${label} `;
|
|
511
|
+
return this.theme.fg("muted", ` ${display} `);
|
|
512
|
+
}
|
|
513
|
+
const coloredLabel = tab === "trace" && source !== "all"
|
|
514
|
+
? `${this.theme.fg("accent", `${base}:`)} ${this.theme.fg("text", source)}`
|
|
515
|
+
: this.theme.fg("accent", label);
|
|
516
|
+
const colored = bracketed
|
|
517
|
+
? ` ${this.theme.fg("accent", "[ ")}${coloredLabel}${this.theme.fg("accent", " ]")} `
|
|
518
|
+
: ` ${coloredLabel} `;
|
|
519
|
+
const active = this.focus === "tabs"
|
|
520
|
+
|| (this.focus === "select" && this.selectorMode === "source");
|
|
521
|
+
return active ? this.theme.bg("selectedBg", colored) : colored;
|
|
484
522
|
}).join(" ");
|
|
485
523
|
}
|
|
486
524
|
|
|
@@ -489,7 +527,6 @@ export class ActorInspectorOverlay {
|
|
|
489
527
|
width: number,
|
|
490
528
|
): string[] {
|
|
491
529
|
if (!run) return [this.theme.fg("muted", " No owned actor runs")];
|
|
492
|
-
if (this.focus === "select") return this.renderSelector(run);
|
|
493
530
|
if (this.focus === "detail") return this.renderTraceDetail(width);
|
|
494
531
|
if (this.tab === "trace") return this.renderTraceList(run);
|
|
495
532
|
const stateDir = path.join(this.stateRoot, run.run);
|
|
@@ -509,31 +546,64 @@ export class ActorInspectorOverlay {
|
|
|
509
546
|
);
|
|
510
547
|
}
|
|
511
548
|
|
|
512
|
-
private renderSelector(
|
|
549
|
+
private renderSelector(
|
|
550
|
+
run: ActorInspector.ActorInspectorRunItem,
|
|
551
|
+
availableRows: number,
|
|
552
|
+
): string[] {
|
|
513
553
|
const options = this.selectorMode === "run"
|
|
514
|
-
? this.
|
|
515
|
-
: this.traceSources(run).map((source) =>
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
554
|
+
? this.runSelectorOptions()
|
|
555
|
+
: this.traceSources(run).map((source) => ({
|
|
556
|
+
content: `${this.theme.fg("accent", "Trace:")} ${this.theme.fg("text", source)}`,
|
|
557
|
+
preserveColors: true,
|
|
558
|
+
}));
|
|
559
|
+
return this.renderMenuBox(options, this.selectorIndex, availableRows);
|
|
519
560
|
}
|
|
520
561
|
|
|
521
|
-
private
|
|
562
|
+
private runSelectorOptions(): SelectorOption[] {
|
|
563
|
+
const runs = this.runs();
|
|
564
|
+
const sequenceWidth = Math.max(
|
|
565
|
+
2,
|
|
566
|
+
...runs.map((_item, index) => `#${runs.length - index - 1}`.length),
|
|
567
|
+
);
|
|
568
|
+
const runWidth = Math.max(1, ...runs.map((item) => visibleWidth(item.run)));
|
|
569
|
+
return runs.map((item, index) => ({
|
|
570
|
+
content: [
|
|
571
|
+
this.theme.fg("text", this.fit(`#${runs.length - index - 1}`, sequenceWidth)),
|
|
572
|
+
this.theme.fg("accent", this.fit(item.run, runWidth)),
|
|
573
|
+
this.theme.fg(this.statusColor(item.status), item.status),
|
|
574
|
+
].join(" "),
|
|
575
|
+
preserveColors: true,
|
|
576
|
+
}));
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
private renderMenuBox(
|
|
580
|
+
options: SelectorOption[],
|
|
581
|
+
focusedIndex: number,
|
|
582
|
+
availableRows: number,
|
|
583
|
+
): string[] {
|
|
522
584
|
if (options.length === 0) return [this.theme.fg("muted", " No options")];
|
|
523
|
-
const visibleLimit = Math.max(
|
|
585
|
+
const visibleLimit = Math.max(
|
|
586
|
+
1,
|
|
587
|
+
Math.min(availableRows - 2, options.length),
|
|
588
|
+
);
|
|
524
589
|
const maxStart = Math.max(0, options.length - visibleLimit);
|
|
525
590
|
const start = Math.max(0, Math.min(focusedIndex - Math.floor(visibleLimit / 2), maxStart));
|
|
526
591
|
const visible = options.slice(start, start + visibleLimit);
|
|
527
|
-
const width = Math.max(8, ...options.map((option) => visibleWidth(option) + 4));
|
|
592
|
+
const width = Math.max(8, ...options.map((option) => visibleWidth(option.content) + 4));
|
|
528
593
|
const border = (left: string, right: string, marker = "") =>
|
|
529
594
|
this.theme.fg("borderAccent", `${left}${marker}${"─".repeat(Math.max(0, width - visibleWidth(marker)))}${right}`);
|
|
530
595
|
return [
|
|
531
596
|
border("╭", "╮", start > 0 ? "↑" : ""),
|
|
532
597
|
...visible.map((option, offset) => {
|
|
533
598
|
const index = start + offset;
|
|
534
|
-
const
|
|
535
|
-
|
|
536
|
-
|
|
599
|
+
const marker = index === focusedIndex
|
|
600
|
+
? this.theme.fg("accent", " ▶ ")
|
|
601
|
+
: " ";
|
|
602
|
+
const row = this.fit(`${marker}${option.content}`, width);
|
|
603
|
+
const colored = index === focusedIndex && !option.preserveColors
|
|
604
|
+
? this.theme.fg("accent", row)
|
|
605
|
+
: row;
|
|
606
|
+
const styled = index === focusedIndex ? this.theme.bg("selectedBg", colored) : colored;
|
|
537
607
|
return `${this.theme.fg("borderAccent", "│")}${styled}${this.theme.fg("borderAccent", "│")}`;
|
|
538
608
|
}),
|
|
539
609
|
border("╰", "╯", start + visible.length < options.length ? "↓" : ""),
|
|
@@ -562,6 +632,14 @@ export class ActorInspectorOverlay {
|
|
|
562
632
|
this.contentStripeIndices = items
|
|
563
633
|
.slice(start, start + viewportRows)
|
|
564
634
|
.map((_item, offset) => start + offset);
|
|
635
|
+
const selectedViewportRow = banner.length + this.rowIndex - start;
|
|
636
|
+
if (
|
|
637
|
+
this.focus === "list" &&
|
|
638
|
+
selectedViewportRow >= banner.length &&
|
|
639
|
+
selectedViewportRow < viewportRows
|
|
640
|
+
) {
|
|
641
|
+
this.contentSelectedRows.add(selectedViewportRow);
|
|
642
|
+
}
|
|
565
643
|
return [...banner, ...items.slice(start, start + viewportRows - banner.length).map((item, offset) => {
|
|
566
644
|
const index = start + offset;
|
|
567
645
|
const detail = item.detail && typeof item.detail === "object"
|
|
@@ -575,7 +653,7 @@ export class ActorInspectorOverlay {
|
|
|
575
653
|
const prefix = this.focus === "list" && index === this.rowIndex
|
|
576
654
|
? this.theme.fg("accent", " ▶ ")
|
|
577
655
|
: " ";
|
|
578
|
-
const row = `${prefix}${this.theme.fg("text", `#${items.length - index}`)} ${marker} ${this.theme.fg("muted", item.source)}/${this.theme.fg(item.level === "error" ? "error" : "accent", item.kind)} ${this.theme.fg("text", item.summary)}`;
|
|
656
|
+
const row = `${prefix}${this.theme.fg("text", `#${items.length - index - 1}`)} ${marker} ${this.theme.fg("muted", item.source)}/${this.theme.fg(item.level === "error" ? "error" : "accent", item.kind)} ${this.theme.fg("text", item.summary)}`;
|
|
579
657
|
return this.focus === "list" && index === this.rowIndex
|
|
580
658
|
? this.theme.fg("accent", row)
|
|
581
659
|
: row;
|
|
@@ -734,11 +812,24 @@ export class ActorInspectorOverlay {
|
|
|
734
812
|
return [
|
|
735
813
|
`${indent}[`,
|
|
736
814
|
...value.flatMap((item, index) => {
|
|
737
|
-
const
|
|
815
|
+
const itemDepth = depth + 1;
|
|
816
|
+
const itemIndent = " ".repeat(itemDepth);
|
|
817
|
+
const marker = `#${index}`;
|
|
738
818
|
const itemInline = this.inlineDocumentValue(item);
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
819
|
+
let lines: string[];
|
|
820
|
+
if (itemInline !== undefined) {
|
|
821
|
+
lines = [`${itemIndent}${marker}: ${itemInline}`];
|
|
822
|
+
} else if (item && typeof item === "object") {
|
|
823
|
+
const nested = this.document(item, itemDepth);
|
|
824
|
+
lines = [
|
|
825
|
+
`${itemIndent}${marker}: ${nested[0]!.slice(itemIndent.length)}`,
|
|
826
|
+
...nested.slice(1),
|
|
827
|
+
];
|
|
828
|
+
} else {
|
|
829
|
+
lines = this.labeledScalarLines(marker, item, itemDepth);
|
|
830
|
+
}
|
|
831
|
+
if (index < value.length - 1) lines[lines.length - 1] += ",";
|
|
832
|
+
return lines;
|
|
742
833
|
}),
|
|
743
834
|
`${indent}]`,
|
|
744
835
|
];
|
|
@@ -792,9 +883,13 @@ export class ActorInspectorOverlay {
|
|
|
792
883
|
private styleDocumentLine(line: string): string {
|
|
793
884
|
const indent = line.match(/^ */u)?.[0] ?? "";
|
|
794
885
|
const content = line.slice(indent.length);
|
|
795
|
-
if (
|
|
886
|
+
if (content.endsWith(":")) {
|
|
796
887
|
return `${indent}${this.theme.fg("accent", content)}`;
|
|
797
888
|
}
|
|
889
|
+
const indexed = /^(#\d+:)(.*)$/u.exec(content);
|
|
890
|
+
if (indexed) {
|
|
891
|
+
return `${indent}${this.theme.fg("accent", indexed[1])}${this.theme.fg("text", indexed[2])}`;
|
|
892
|
+
}
|
|
798
893
|
const labeled = /^([^:]+:)(.*)$/u.exec(content);
|
|
799
894
|
if (labeled) {
|
|
800
895
|
return `${indent}${this.theme.fg("muted", labeled[1])}${this.theme.fg("text", labeled[2])}`;
|
|
@@ -868,13 +963,46 @@ export class ActorInspectorOverlay {
|
|
|
868
963
|
return `${this.theme.fg("borderAccent", `╰${prefix}`)}${content}${this.theme.fg("borderAccent", `${suffix}${fill}╯`)}`;
|
|
869
964
|
}
|
|
870
965
|
|
|
966
|
+
private compositeMenuLine(
|
|
967
|
+
baseLine: string,
|
|
968
|
+
menuLine: string,
|
|
969
|
+
innerWidth: number,
|
|
970
|
+
horizontalOffset: number,
|
|
971
|
+
): string {
|
|
972
|
+
const offset = Math.max(0, Math.min(horizontalOffset, innerWidth));
|
|
973
|
+
const menuWidth = Math.min(innerWidth - offset, visibleWidth(menuLine));
|
|
974
|
+
const baseInner = sliceByColumn(baseLine, 1, innerWidth, true);
|
|
975
|
+
const prefix = sliceByColumn(baseInner, 0, offset, true);
|
|
976
|
+
const suffixStart = offset + menuWidth;
|
|
977
|
+
const suffix = sliceByColumn(
|
|
978
|
+
baseInner,
|
|
979
|
+
suffixStart,
|
|
980
|
+
Math.max(0, innerWidth - suffixStart),
|
|
981
|
+
true,
|
|
982
|
+
);
|
|
983
|
+
const remainder = this.fit(suffix, innerWidth - suffixStart);
|
|
984
|
+
return `${this.theme.fg("borderAccent", "│")}${this.fit(prefix, offset)}${truncateToWidth(menuLine, menuWidth, "")}${remainder}${this.theme.fg("borderAccent", "│")}`;
|
|
985
|
+
}
|
|
986
|
+
|
|
871
987
|
private stripeBackground(content: string, index: number): string {
|
|
872
988
|
return index % 2 === 0 ? content : this.theme.bg("customMessageBg", content);
|
|
873
989
|
}
|
|
874
990
|
|
|
875
|
-
private stripedRow(
|
|
876
|
-
|
|
877
|
-
|
|
991
|
+
private stripedRow(
|
|
992
|
+
content: string,
|
|
993
|
+
width: number,
|
|
994
|
+
index: number,
|
|
995
|
+
selected: boolean,
|
|
996
|
+
): string {
|
|
997
|
+
if (!selected) {
|
|
998
|
+
const fitted = this.fit(content, width);
|
|
999
|
+
return `${this.theme.fg("borderAccent", "│")}${this.stripeBackground(fitted, index)}${this.theme.fg("borderAccent", "│")}`;
|
|
1000
|
+
}
|
|
1001
|
+
const selectedContent = truncateToWidth(`${content} `, width, "");
|
|
1002
|
+
const remainder = " ".repeat(
|
|
1003
|
+
Math.max(0, width - visibleWidth(selectedContent)),
|
|
1004
|
+
);
|
|
1005
|
+
return `${this.theme.fg("borderAccent", "│")}${this.theme.bg("selectedBg", selectedContent)}${this.stripeBackground(remainder, index)}${this.theme.fg("borderAccent", "│")}`;
|
|
878
1006
|
}
|
|
879
1007
|
|
|
880
1008
|
private row(content: string, width: number): string {
|
|
@@ -12,6 +12,7 @@ export const CONTROL_INPUT_MAX_BYTES = 380;
|
|
|
12
12
|
export const CONTROL_WIRE_MAX_BYTES = 512;
|
|
13
13
|
export const INSPECTOR_BODY_PREVIEW_CHARS = 320;
|
|
14
14
|
export const DOCTOR_ACTION_PREVIEW_CHARS = 72;
|
|
15
|
+
export const SESSION_EVIDENCE_MAX_BYTES = 4 * 1024 * 1024;
|
|
15
16
|
export const SESSION_EVIDENCE_MAX_TURNS = 100;
|
|
16
17
|
export const SESSION_EVIDENCE_TEXT_CHARS = 4_000;
|
|
17
18
|
export const SESSION_EVIDENCE_MAX_TOOL_CALLS = 100;
|
|
@@ -418,7 +418,11 @@ export function createRunTerminalReconciliationLoop(input: {
|
|
|
418
418
|
input.refreshWatcher();
|
|
419
419
|
input.reconcile();
|
|
420
420
|
} catch (error) {
|
|
421
|
-
|
|
421
|
+
try {
|
|
422
|
+
input.onError?.(error);
|
|
423
|
+
} catch {
|
|
424
|
+
/* reconciliation callbacks must never escape into the host event loop */
|
|
425
|
+
}
|
|
422
426
|
}
|
|
423
427
|
};
|
|
424
428
|
const close = (): void => {
|