@llblab/pi-actors 0.43.0 → 0.44.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 +16 -10
- package/CHANGELOG.md +425 -536
- package/README.md +13 -11
- package/dist/index.js +1 -1
- package/dist/lib/async-runs.d.ts +2 -1
- package/dist/lib/async-runs.js +24 -34
- package/dist/lib/automatic-review-runtime.d.ts +1 -1
- package/dist/lib/automatic-review-runtime.js +5 -5
- package/dist/lib/command-templates.d.ts +2 -0
- package/dist/lib/command-templates.js +38 -4
- package/dist/lib/control-projection.d.ts +20 -0
- package/dist/lib/control-projection.js +66 -0
- package/dist/lib/control.d.ts +3 -0
- package/dist/lib/control.js +27 -14
- package/dist/lib/draft-sleep.js +3 -3
- package/dist/lib/file-state.d.ts +4 -1
- package/dist/lib/file-state.js +118 -44
- package/dist/lib/inspector-overlay.d.ts +2 -0
- package/dist/lib/inspector-overlay.js +124 -69
- package/dist/lib/limits.d.ts +15 -3
- package/dist/lib/limits.js +15 -3
- package/dist/lib/observability.d.ts +4 -2
- package/dist/lib/observability.js +43 -36
- package/dist/lib/prompts.d.ts +1 -1
- package/dist/lib/prompts.js +1 -1
- package/dist/lib/recipe-control.js +6 -2
- package/dist/lib/review-control.d.ts +1 -1
- package/dist/lib/review-control.js +4 -5
- package/dist/lib/run-evidence-policy.d.ts +95 -0
- package/dist/lib/run-evidence-policy.js +177 -0
- package/dist/lib/run-ui-runtime.js +2 -0
- package/dist/lib/runs-control-delivery.d.ts +8 -1
- package/dist/lib/runs-control-delivery.js +38 -15
- package/dist/lib/runs-controls.d.ts +8 -4
- package/dist/lib/runs-controls.js +189 -50
- package/dist/lib/runs-retention.js +27 -14
- package/dist/lib/runs-trace.d.ts +26 -2
- package/dist/lib/runs-trace.js +411 -18
- package/dist/lib/runtime-identity.d.ts +7 -0
- package/dist/lib/runtime-identity.js +35 -0
- package/dist/lib/runtime-triage.d.ts +29 -0
- package/dist/lib/runtime-triage.js +60 -0
- package/dist/lib/tool-review-scheduler.js +7 -7
- package/dist/lib/tools-inspect.js +91 -18
- package/dist/lib/tools-message.d.ts +1 -2
- package/dist/lib/tools-message.js +6 -6
- package/dist/lib/tools-response.d.ts +0 -1
- package/dist/lib/tools-response.js +0 -9
- package/dist/lib/tools.d.ts +1 -1
- package/dist/lib/tools.js +1 -1
- package/dist/lib/trace-projection.js +107 -41
- package/dist/scripts/conformance.mjs +5 -0
- package/dist/scripts/locker.mjs +40 -90
- package/dist/scripts/music-player.mjs +48 -142
- package/dist/scripts/release-gates.mjs +56 -3
- package/dist/scripts/validate-recipe.mjs +5 -4
- package/dist/skills/actors/SKILL.md +17 -11
- package/dist/skills/swarm/SKILL.md +2 -4
- package/docs/README.md +1 -4
- package/docs/actor-inspector.md +6 -5
- package/docs/async-runs.md +11 -9
- package/docs/command-templates.md +6 -116
- package/docs/recipe-library.md +4 -6
- package/docs/releasing.md +28 -0
- package/docs/template-recipes.md +1 -1
- package/docs/tool-registry.md +2 -2
- package/index.ts +1 -1
- package/lib/async-runs.ts +26 -50
- package/lib/automatic-review-runtime.ts +7 -7
- package/lib/command-templates.ts +44 -4
- package/lib/control-projection.ts +105 -0
- package/lib/control.ts +33 -18
- package/lib/draft-sleep.ts +3 -3
- package/lib/file-state.ts +91 -63
- package/lib/inspector-overlay.ts +108 -61
- package/lib/limits.ts +15 -3
- package/lib/observability.ts +55 -57
- package/lib/prompts.ts +1 -1
- package/lib/recipe-control.ts +9 -2
- package/lib/review-control.ts +4 -5
- package/lib/run-evidence-policy.ts +242 -0
- package/lib/run-ui-runtime.ts +2 -0
- package/lib/runs-control-delivery.ts +45 -17
- package/lib/runs-controls.ts +180 -102
- package/lib/runs-retention.ts +28 -20
- package/lib/runs-trace.ts +499 -20
- package/lib/runtime-identity.ts +39 -0
- package/lib/runtime-triage.ts +106 -0
- package/lib/tool-review-scheduler.ts +7 -7
- package/lib/tools-inspect.ts +94 -20
- package/lib/tools-message.ts +7 -8
- package/lib/tools-response.ts +0 -12
- package/lib/tools.ts +4 -4
- package/lib/trace-projection.ts +156 -71
- package/package.json +1 -1
- package/scripts/conformance.mjs +5 -0
- package/scripts/locker.mjs +40 -90
- package/scripts/music-player.mjs +48 -142
- package/scripts/release-gates.mjs +56 -3
- package/scripts/validate-recipe.mjs +5 -4
- package/skills/actors/SKILL.md +17 -11
- package/skills/swarm/SKILL.md +2 -4
- package/dist/lib/runtime-notifier.d.ts +0 -48
- package/dist/lib/runtime-notifier.js +0 -138
- package/docs/0.43-baseline.md +0 -44
- package/docs/actors-deep-reference.md +0 -108
- package/docs/component-recipes.md +0 -45
- package/docs/task-first-recipes.md +0 -261
- package/lib/runtime-notifier.ts +0 -211
package/lib/inspector-overlay.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Owns manual kernel navigation; evidence parsing and lifecycle mutation stay in ports.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import { readFileSync } from "node:fs";
|
|
7
|
+
import { readFileSync, statSync } from "node:fs";
|
|
8
8
|
import * as path from "node:path";
|
|
9
9
|
|
|
10
10
|
import type { Theme } from "@earendil-works/pi-coding-agent";
|
|
@@ -16,8 +16,13 @@ import {
|
|
|
16
16
|
} from "@earendil-works/pi-tui";
|
|
17
17
|
|
|
18
18
|
import * as ActorInspector from "./inspector.ts";
|
|
19
|
+
import * as ControlProjection from "./control-projection.ts";
|
|
19
20
|
import * as RunsControls from "./runs-controls.ts";
|
|
20
21
|
import * as RunControlDelivery from "./runs-control-delivery.ts";
|
|
22
|
+
import * as RunsTrace from "./runs-trace.ts";
|
|
23
|
+
import * as Limits from "./limits.ts";
|
|
24
|
+
import { computeRunControlCapacity } from "./run-evidence-policy.ts";
|
|
25
|
+
import * as RuntimeTriage from "./runtime-triage.ts";
|
|
21
26
|
import * as TraceProjection from "./trace-projection.ts";
|
|
22
27
|
|
|
23
28
|
export type ActorInspectorTab = "recipe" | "trace" | "control";
|
|
@@ -77,6 +82,7 @@ export class ActorInspectorOverlay {
|
|
|
77
82
|
private killDialogChoice: "cancel" | "kill" = "cancel";
|
|
78
83
|
private rowIndex = 0;
|
|
79
84
|
private runCache?: ActorInspector.ActorInspectorRunItem[];
|
|
85
|
+
private selectedTraceId?: string;
|
|
80
86
|
private runIndex = 0;
|
|
81
87
|
private selectorIndex = 0;
|
|
82
88
|
private selectorMode?: SelectorMode;
|
|
@@ -99,8 +105,7 @@ export class ActorInspectorOverlay {
|
|
|
99
105
|
this.theme = options.theme;
|
|
100
106
|
this.tui = options.tui;
|
|
101
107
|
this.refreshTimer = setInterval(() => {
|
|
102
|
-
this.
|
|
103
|
-
if (this.focus !== "list" && this.focus !== "detail") this.traceCache = undefined;
|
|
108
|
+
this.invalidate();
|
|
104
109
|
this.tui.requestRender();
|
|
105
110
|
}, 1_000);
|
|
106
111
|
this.refreshTimer.unref?.();
|
|
@@ -110,7 +115,10 @@ export class ActorInspectorOverlay {
|
|
|
110
115
|
clearInterval(this.refreshTimer);
|
|
111
116
|
}
|
|
112
117
|
|
|
113
|
-
invalidate(): void {
|
|
118
|
+
invalidate(): void {
|
|
119
|
+
this.runCache = undefined;
|
|
120
|
+
this.traceCache = undefined;
|
|
121
|
+
}
|
|
114
122
|
|
|
115
123
|
handleInput(data: string): void {
|
|
116
124
|
const runs = this.runs();
|
|
@@ -134,10 +142,12 @@ export class ActorInspectorOverlay {
|
|
|
134
142
|
this.selectorMode = undefined;
|
|
135
143
|
this.focus = this.tab === "trace" ? "tabs" : "runs";
|
|
136
144
|
} else if (this.focus === "detail") {
|
|
145
|
+
this.selectedTraceId = this.traceDetail?.id;
|
|
137
146
|
this.traceDetail = undefined;
|
|
138
147
|
this.detailScroll = 0;
|
|
139
148
|
this.focus = "list";
|
|
140
149
|
} else if (this.focus === "document" || this.focus === "list") {
|
|
150
|
+
if (this.focus === "list") this.selectedTraceId = undefined;
|
|
141
151
|
this.focus = "tabs";
|
|
142
152
|
} else {
|
|
143
153
|
this.done();
|
|
@@ -179,19 +189,27 @@ export class ActorInspectorOverlay {
|
|
|
179
189
|
} else if (matchesKey(data, "up")) {
|
|
180
190
|
this.focus = "runs";
|
|
181
191
|
} else if (matchesKey(data, "down")) {
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
192
|
+
const items = this.tab === "trace" ? this.traceItems(runs[this.runIndex]) : [];
|
|
193
|
+
this.focus = items.length > 0 ? "list" : "document";
|
|
194
|
+
if (this.focus === "list") {
|
|
195
|
+
this.rowIndex = 0;
|
|
196
|
+
this.selectedTraceId = items[0]?.id;
|
|
197
|
+
}
|
|
185
198
|
} else if (matchesKey(data, "return")) {
|
|
186
199
|
if (this.tab === "trace") this.openSourceSelector(runs[this.runIndex]);
|
|
187
200
|
else this.focus = "document";
|
|
188
201
|
}
|
|
189
202
|
} else if (this.focus === "list") {
|
|
190
|
-
const
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
203
|
+
const items = this.traceItems(runs[this.runIndex]);
|
|
204
|
+
const count = items.length;
|
|
205
|
+
if (matchesKey(data, "left")) {
|
|
206
|
+
this.selectedTraceId = undefined;
|
|
207
|
+
this.focus = "tabs";
|
|
208
|
+
} else if (matchesKey(data, "up")) {
|
|
209
|
+
if (this.rowIndex === 0) {
|
|
210
|
+
this.selectedTraceId = undefined;
|
|
211
|
+
this.focus = "tabs";
|
|
212
|
+
} else this.rowIndex -= 1;
|
|
195
213
|
} else if (matchesKey(data, "down")) {
|
|
196
214
|
this.rowIndex = Math.min(Math.max(0, count - 1), this.rowIndex + 1);
|
|
197
215
|
} else if (matchesKey(data, "pageUp")) {
|
|
@@ -202,13 +220,14 @@ export class ActorInspectorOverlay {
|
|
|
202
220
|
this.rowIndex + this.contentViewportRows(),
|
|
203
221
|
);
|
|
204
222
|
} else if (matchesKey(data, "return") || matchesKey(data, "right")) {
|
|
205
|
-
const item =
|
|
223
|
+
const item = items[this.rowIndex];
|
|
206
224
|
if (item) {
|
|
207
225
|
this.traceDetail = item;
|
|
208
226
|
this.detailScroll = 0;
|
|
209
227
|
this.focus = "detail";
|
|
210
228
|
}
|
|
211
229
|
}
|
|
230
|
+
if (this.focus === "list") this.selectedTraceId = items[this.rowIndex]?.id;
|
|
212
231
|
}
|
|
213
232
|
if (data.toLowerCase() === "f" && this.tab === "trace") {
|
|
214
233
|
this.cycleTraceSource(runs[this.runIndex]);
|
|
@@ -273,6 +292,7 @@ export class ActorInspectorOverlay {
|
|
|
273
292
|
this.documentScroll = 0;
|
|
274
293
|
this.detailScroll = 0;
|
|
275
294
|
this.rowIndex = 0;
|
|
295
|
+
this.selectedTraceId = undefined;
|
|
276
296
|
this.traceDetail = undefined;
|
|
277
297
|
}
|
|
278
298
|
|
|
@@ -291,6 +311,7 @@ export class ActorInspectorOverlay {
|
|
|
291
311
|
} else if (matchesKey(data, "pageDown")) {
|
|
292
312
|
this[key] += this.contentViewportRows();
|
|
293
313
|
} else if (matchesKey(data, "left") && target === "detail") {
|
|
314
|
+
this.selectedTraceId = this.traceDetail?.id;
|
|
294
315
|
this.traceDetail = undefined;
|
|
295
316
|
this.detailScroll = 0;
|
|
296
317
|
this.focus = "list";
|
|
@@ -392,7 +413,9 @@ export class ActorInspectorOverlay {
|
|
|
392
413
|
this.killDialogChoice = "cancel";
|
|
393
414
|
if (!confirmation || !this.killRun) return;
|
|
394
415
|
try {
|
|
395
|
-
|
|
416
|
+
const result = this.killRun(confirmation.run, confirmation.runInstanceId);
|
|
417
|
+
this.feedback = result.ok ? undefined : result;
|
|
418
|
+
if (result.ok) this.invalidate();
|
|
396
419
|
} catch (error) {
|
|
397
420
|
const message = error instanceof Error ? error.message : String(error);
|
|
398
421
|
this.feedback = {
|
|
@@ -417,8 +440,7 @@ export class ActorInspectorOverlay {
|
|
|
417
440
|
`${this.theme.fg("muted", "Run:")} ${this.theme.fg("accent", `run:${confirmation.run}`)}`,
|
|
418
441
|
`${this.theme.fg("muted", "Current status:")} ${this.theme.fg("warning", confirmation.status)}`,
|
|
419
442
|
"",
|
|
420
|
-
this.theme.fg("text", "This
|
|
421
|
-
this.theme.fg("error", "The action is destructive and cannot be undone."),
|
|
443
|
+
this.theme.fg("text", "This invokes generation-fenced runtime kill."),
|
|
422
444
|
"",
|
|
423
445
|
`${cancel} ${kill}`,
|
|
424
446
|
"",
|
|
@@ -520,15 +542,27 @@ export class ActorInspectorOverlay {
|
|
|
520
542
|
|
|
521
543
|
private renderTraceList(run: ActorInspector.ActorInspectorRunItem): string[] {
|
|
522
544
|
const items = this.traceItems(run);
|
|
523
|
-
|
|
545
|
+
const summary = RunsTrace.summarizeRunTraceJournal(
|
|
546
|
+
RunsTrace.readRunTraceJournal(path.join(this.stateRoot, run.run)));
|
|
547
|
+
const banner = summary.history_complete ? [] : [this.theme.fg("warning",
|
|
548
|
+
` ! Trace history incomplete: ${summary.compacted ? `${summary.compactions_total} compaction(s), ` : ""}${summary.dropped_event_count_exact ? summary.dropped_events : "unknown"} dropped event(s), ${summary.dropped_bytes} bytes`)];
|
|
549
|
+
if (items.length === 0) return [...banner, this.theme.fg("muted", " No Trace evidence")];
|
|
550
|
+
if (this.selectedTraceId) {
|
|
551
|
+
const selectedIndex = items.findIndex((item) => item.id === this.selectedTraceId);
|
|
552
|
+
if (selectedIndex >= 0) this.rowIndex = selectedIndex;
|
|
553
|
+
else this.selectedTraceId = undefined;
|
|
554
|
+
}
|
|
524
555
|
this.rowIndex = Math.min(this.rowIndex, items.length - 1);
|
|
556
|
+
if (this.focus === "list" && !this.selectedTraceId) {
|
|
557
|
+
this.selectedTraceId = items[this.rowIndex]?.id;
|
|
558
|
+
}
|
|
525
559
|
const viewportRows = this.contentViewportRows();
|
|
526
560
|
const maxStart = Math.max(0, items.length - viewportRows);
|
|
527
561
|
const start = Math.max(0, Math.min(this.rowIndex - Math.floor(viewportRows / 2), maxStart));
|
|
528
562
|
this.contentStripeIndices = items
|
|
529
563
|
.slice(start, start + viewportRows)
|
|
530
564
|
.map((_item, offset) => start + offset);
|
|
531
|
-
return items.slice(start, start + viewportRows).map((item, offset) => {
|
|
565
|
+
return [...banner, ...items.slice(start, start + viewportRows - banner.length).map((item, offset) => {
|
|
532
566
|
const index = start + offset;
|
|
533
567
|
const detail = item.detail && typeof item.detail === "object"
|
|
534
568
|
? item.detail as Record<string, unknown>
|
|
@@ -536,16 +570,16 @@ export class ActorInspectorOverlay {
|
|
|
536
570
|
const marker = item.level === "error"
|
|
537
571
|
? this.theme.fg("error", "!")
|
|
538
572
|
: detail.attention
|
|
539
|
-
? this.theme.fg("warning", "
|
|
540
|
-
:
|
|
573
|
+
? this.theme.fg("warning", "A")
|
|
574
|
+
: " ";
|
|
541
575
|
const prefix = this.focus === "list" && index === this.rowIndex
|
|
542
576
|
? this.theme.fg("accent", " ▶ ")
|
|
543
577
|
: " ";
|
|
544
|
-
const row = `${prefix}${this.theme.fg("text", `#${
|
|
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)}`;
|
|
545
579
|
return this.focus === "list" && index === this.rowIndex
|
|
546
580
|
? this.theme.fg("accent", row)
|
|
547
581
|
: row;
|
|
548
|
-
});
|
|
582
|
+
})];
|
|
549
583
|
}
|
|
550
584
|
|
|
551
585
|
private renderTraceDetail(width: number): string[] {
|
|
@@ -571,13 +605,26 @@ export class ActorInspectorOverlay {
|
|
|
571
605
|
? RunControlDelivery.readRunControlEndpoint(stateDir, run.runInstanceId)
|
|
572
606
|
: undefined;
|
|
573
607
|
const meta = this.readRunMeta(stateDir);
|
|
608
|
+
const journal = RunsControls.readRunControlJournalFromStateDir(stateDir);
|
|
609
|
+
const capacity = computeRunControlCapacity(journal.records);
|
|
610
|
+
const stalePending = journal.records.reduce<number>((count, control) => count + Number(Boolean(
|
|
611
|
+
RuntimeTriage.classifyRuntimeControl({ run: run.run,
|
|
612
|
+
runInstanceId: run.runInstanceId, status: run.status }, control, Date.now()).stale)), 0);
|
|
574
613
|
return {
|
|
575
614
|
status: run.status,
|
|
576
615
|
run_instance_id: run.runInstanceId,
|
|
616
|
+
pending: capacity.pending, pending_limit: capacity.limit,
|
|
617
|
+
available: capacity.available, backpressured: capacity.backpressured,
|
|
618
|
+
journal_bytes: (() => { try { return statSync(RunsControls.runControlsFile(stateDir)).size; } catch { return 0; } })(),
|
|
619
|
+
journal_limit: Limits.RUN_CONTROL_JOURNAL_MAX_BYTES,
|
|
620
|
+
stale_pending: stalePending,
|
|
621
|
+
diagnostics: journal.diagnostics.map((item) => item.line === undefined
|
|
622
|
+
? { reason: "unreadable_control_journal" } : { line: item.line, reason: "invalid_control_json" }),
|
|
577
623
|
actor_actions: Array.isArray(meta.control) ? meta.control : [],
|
|
578
624
|
runtime_actions: run.status === "running" ? ["kill"] : ["archive", "prune"],
|
|
579
625
|
endpoint,
|
|
580
|
-
recent_controls:
|
|
626
|
+
recent_controls: journal.records.slice(-20).reverse().map((control) =>
|
|
627
|
+
ControlProjection.projectRunControl(control as RunsControls.RunControlRecord)),
|
|
581
628
|
};
|
|
582
629
|
}
|
|
583
630
|
|
|
@@ -627,14 +674,9 @@ export class ActorInspectorOverlay {
|
|
|
627
674
|
width: number,
|
|
628
675
|
): { lines: string[]; stripes: number[] } {
|
|
629
676
|
const sections = value && typeof value === "object" && !Array.isArray(value)
|
|
630
|
-
? Object.entries(value as Record<string, unknown>).map(([key, item]) =>
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
? [`${key}: ${inline}`]
|
|
634
|
-
: item && typeof item === "object"
|
|
635
|
-
? [`${key}:`, ...this.document(item, 1)]
|
|
636
|
-
: this.labeledScalarLines(key, item);
|
|
637
|
-
})
|
|
677
|
+
? Object.entries(value as Record<string, unknown>).map(([key, item]) =>
|
|
678
|
+
this.labeledDocumentLines(key, item)
|
|
679
|
+
)
|
|
638
680
|
: [this.document(value)];
|
|
639
681
|
const lines: string[] = [];
|
|
640
682
|
const stripes: number[] = [];
|
|
@@ -658,23 +700,22 @@ export class ActorInspectorOverlay {
|
|
|
658
700
|
return undefined;
|
|
659
701
|
}
|
|
660
702
|
if (value && typeof value === "object") {
|
|
661
|
-
|
|
662
|
-
if (entries.length === 0) return "{}";
|
|
663
|
-
if (
|
|
664
|
-
entries.length <= 3 &&
|
|
665
|
-
entries.every(([, item]) =>
|
|
666
|
-
(item === null || typeof item !== "object") &&
|
|
667
|
-
!(typeof item === "string" && /[\r\n]/u.test(item))
|
|
668
|
-
)
|
|
669
|
-
) {
|
|
670
|
-
return `{ ${entries
|
|
671
|
-
.map(([key, item]) => `${key}: ${String(item ?? "none")}`)
|
|
672
|
-
.join(", ")} }`;
|
|
673
|
-
}
|
|
703
|
+
if (Object.keys(value as Record<string, unknown>).length === 0) return "{}";
|
|
674
704
|
}
|
|
675
705
|
return undefined;
|
|
676
706
|
}
|
|
677
707
|
|
|
708
|
+
private labeledDocumentLines(key: string, value: unknown, depth = 0): string[] {
|
|
709
|
+
const inline = this.inlineDocumentValue(value);
|
|
710
|
+
if (inline !== undefined) return [`${" ".repeat(depth)}${key}: ${inline}`];
|
|
711
|
+
if (value && typeof value === "object") {
|
|
712
|
+
const indent = " ".repeat(depth);
|
|
713
|
+
const nested = this.document(value, depth);
|
|
714
|
+
return [`${indent}${key}: ${nested[0]!.slice(indent.length)}`, ...nested.slice(1)];
|
|
715
|
+
}
|
|
716
|
+
return this.labeledScalarLines(key, value, depth);
|
|
717
|
+
}
|
|
718
|
+
|
|
678
719
|
private labeledScalarLines(key: string, value: unknown, depth = 0): string[] {
|
|
679
720
|
const indent = " ".repeat(depth);
|
|
680
721
|
const [first = "", ...rest] = String(value ?? "none").split(/\r?\n/u);
|
|
@@ -689,25 +730,31 @@ export class ActorInspectorOverlay {
|
|
|
689
730
|
const inline = this.inlineDocumentValue(value);
|
|
690
731
|
if (inline !== undefined) return [`${" ".repeat(depth)}${inline}`];
|
|
691
732
|
if (Array.isArray(value)) {
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
733
|
+
const indent = " ".repeat(depth);
|
|
734
|
+
return [
|
|
735
|
+
`${indent}[`,
|
|
736
|
+
...value.flatMap((item, index) => {
|
|
737
|
+
const marker = `${indent} - #${index + 1}`;
|
|
738
|
+
const itemInline = this.inlineDocumentValue(item);
|
|
739
|
+
return itemInline !== undefined
|
|
740
|
+
? [`${marker}: ${itemInline}`]
|
|
741
|
+
: [marker, ...this.document(item, depth + 2)];
|
|
742
|
+
}),
|
|
743
|
+
`${indent}]`,
|
|
744
|
+
];
|
|
699
745
|
}
|
|
700
746
|
if (typeof value === "object") {
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
747
|
+
const indent = " ".repeat(depth);
|
|
748
|
+
const entries = Object.entries(value as Record<string, unknown>);
|
|
749
|
+
return [
|
|
750
|
+
`${indent}{`,
|
|
751
|
+
...entries.flatMap(([key, item], index) => {
|
|
752
|
+
const lines = this.labeledDocumentLines(key, item, depth + 1);
|
|
753
|
+
if (index < entries.length - 1) lines[lines.length - 1] += ",";
|
|
754
|
+
return lines;
|
|
755
|
+
}),
|
|
756
|
+
`${indent}}`,
|
|
757
|
+
];
|
|
711
758
|
}
|
|
712
759
|
return String(value)
|
|
713
760
|
.split(/\r?\n/u)
|
package/lib/limits.ts
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Shared output and
|
|
3
|
-
* Zones: output governance, Trace previews,
|
|
2
|
+
* Shared output, preview, and Control envelope limits.
|
|
3
|
+
* Zones: output governance, Trace previews, Inspect defaults, Control portability
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
export const DEFAULT_INSPECT_LINES = 40;
|
|
7
7
|
export const TOOL_OUTPUT_MAX_BYTES = 50 * 1024;
|
|
8
8
|
export const TOOL_OUTPUT_MAX_LINES = 2_000;
|
|
9
9
|
export const COMPACT_PREVIEW_CHARS = 160;
|
|
10
|
-
export const
|
|
10
|
+
export const CONTROL_ACTION_MAX_LENGTH = 64;
|
|
11
|
+
export const CONTROL_INPUT_MAX_BYTES = 380;
|
|
12
|
+
export const CONTROL_WIRE_MAX_BYTES = 512;
|
|
11
13
|
export const INSPECTOR_BODY_PREVIEW_CHARS = 320;
|
|
12
14
|
export const DOCTOR_ACTION_PREVIEW_CHARS = 72;
|
|
13
15
|
export const SESSION_EVIDENCE_MAX_TURNS = 100;
|
|
@@ -15,3 +17,13 @@ export const SESSION_EVIDENCE_TEXT_CHARS = 4_000;
|
|
|
15
17
|
export const SESSION_EVIDENCE_MAX_TOOL_CALLS = 100;
|
|
16
18
|
export const TRACE_EVENT_MAX_BYTES = 64 * 1024;
|
|
17
19
|
export const TRACE_EVENT_MAX_READ = 200;
|
|
20
|
+
export const TRACE_JOURNAL_MAX_EVENTS = 2_048;
|
|
21
|
+
export const TRACE_JOURNAL_TARGET_EVENTS = 1_536;
|
|
22
|
+
export const TRACE_JOURNAL_MAX_BYTES = 4 * 1024 * 1024;
|
|
23
|
+
export const TRACE_JOURNAL_TARGET_BYTES = 3 * 1024 * 1024;
|
|
24
|
+
export const RUN_CONTROL_PENDING_LIMIT = 64;
|
|
25
|
+
export const RUN_CONTROL_TERMINAL_LIMIT = 128;
|
|
26
|
+
export const RUN_CONTROL_ERROR_MAX_BYTES = 4 * 1024;
|
|
27
|
+
export const RUN_CONTROL_JOURNAL_MAX_BYTES = 1024 * 1024;
|
|
28
|
+
export const RUN_RETENTION_MAX_RECORDS = 256;
|
|
29
|
+
export const RUN_RETENTION_MAX_BYTES = 1024 * 1024;
|
package/lib/observability.ts
CHANGED
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
|
|
26
26
|
import * as AsyncRuns from "./async-runs.ts";
|
|
27
27
|
import * as Paths from "./paths.ts";
|
|
28
|
+
import * as RunsTrace from "./runs-trace.ts";
|
|
28
29
|
import { readJsonlFileResilient } from "./state-readers.ts";
|
|
29
30
|
|
|
30
31
|
export type RunObservedStatus =
|
|
@@ -73,7 +74,7 @@ export interface RunSummary {
|
|
|
73
74
|
|
|
74
75
|
export interface RunUiObservationState {
|
|
75
76
|
attentionEventIds: Map<string, Set<string>>;
|
|
76
|
-
|
|
77
|
+
legacyEventLines: Map<string, number>;
|
|
77
78
|
frame: number;
|
|
78
79
|
observed: Map<string, RunObservedStatus>;
|
|
79
80
|
}
|
|
@@ -98,12 +99,19 @@ export interface RunUiNotificationSink {
|
|
|
98
99
|
export function createRunUiObservationState(): RunUiObservationState {
|
|
99
100
|
return {
|
|
100
101
|
attentionEventIds: new Map<string, Set<string>>(),
|
|
101
|
-
|
|
102
|
+
legacyEventLines: new Map<string, number>(),
|
|
102
103
|
frame: 0,
|
|
103
104
|
observed: new Map<string, RunObservedStatus>(),
|
|
104
105
|
};
|
|
105
106
|
}
|
|
106
107
|
|
|
108
|
+
export function primeRunAttentionState(
|
|
109
|
+
state: RunUiObservationState, ownerId: string, stateRoot?: string,
|
|
110
|
+
): void {
|
|
111
|
+
detectRunAttentionEvents(state.legacyEventLines, summarizeRuns(stateRoot, ownerId),
|
|
112
|
+
state.attentionEventIds, true);
|
|
113
|
+
}
|
|
114
|
+
|
|
107
115
|
export function readRunUiSnapshot(
|
|
108
116
|
state: RunUiObservationState,
|
|
109
117
|
ownerId: string,
|
|
@@ -116,7 +124,7 @@ export function readRunUiSnapshot(
|
|
|
116
124
|
options.includeAttention === false
|
|
117
125
|
? []
|
|
118
126
|
: detectRunAttentionEvents(
|
|
119
|
-
state.
|
|
127
|
+
state.legacyEventLines,
|
|
120
128
|
summary,
|
|
121
129
|
state.attentionEventIds,
|
|
122
130
|
),
|
|
@@ -132,7 +140,7 @@ export function pruneRunUiObservationState(
|
|
|
132
140
|
): void {
|
|
133
141
|
pruneRunObservationState(
|
|
134
142
|
state.observed,
|
|
135
|
-
state.
|
|
143
|
+
state.legacyEventLines,
|
|
136
144
|
snapshot.summary,
|
|
137
145
|
snapshot.transitions.map(
|
|
138
146
|
(transition) => transition.stateDir ?? transition.run,
|
|
@@ -192,16 +200,15 @@ export function reconcileRunTerminalNotifications(input: {
|
|
|
192
200
|
sink: RunUiNotificationSink;
|
|
193
201
|
state: RunUiObservationState;
|
|
194
202
|
stateRoot?: string;
|
|
203
|
+
includeAttention?: boolean;
|
|
195
204
|
}): RunUiSnapshot {
|
|
196
205
|
const snapshot = readRunUiSnapshot(input.state, input.ownerId, {
|
|
197
|
-
includeAttention:
|
|
206
|
+
includeAttention: input.includeAttention,
|
|
198
207
|
stateRoot: input.stateRoot,
|
|
199
208
|
});
|
|
200
|
-
|
|
201
|
-
snapshot.
|
|
202
|
-
|
|
203
|
-
input.inFlight,
|
|
204
|
-
);
|
|
209
|
+
if (input.includeAttention)
|
|
210
|
+
deliverRunAttentionNotifications(snapshot.attentionEvents, input.sink);
|
|
211
|
+
deliverRunTransitionNotifications(snapshot.transitions, input.sink, input.inFlight);
|
|
205
212
|
pruneRunUiObservationState(input.state, snapshot);
|
|
206
213
|
return snapshot;
|
|
207
214
|
}
|
|
@@ -1114,15 +1121,17 @@ function parseAttentionRecord(
|
|
|
1114
1121
|
};
|
|
1115
1122
|
}
|
|
1116
1123
|
|
|
1117
|
-
function readTraceAttentionRecords(run: RunObservation):
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
if (
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1124
|
+
function readTraceAttentionRecords(run: RunObservation): {
|
|
1125
|
+
canonical: boolean; records: Record<string, unknown>[];
|
|
1126
|
+
} {
|
|
1127
|
+
if (!run.stateDir) return { canonical: true, records: [] };
|
|
1128
|
+
if (existsSync(join(run.stateDir, "trace.jsonl"))) return {
|
|
1129
|
+
canonical: true,
|
|
1130
|
+
records: RunsTrace.readRunTraceJournal(run.stateDir).events.map(
|
|
1131
|
+
({ event }) => event as unknown as Record<string, unknown>),
|
|
1132
|
+
};
|
|
1133
|
+
return { canonical: false, records: readJsonlFileResilient<Record<string, unknown>>(
|
|
1134
|
+
join(run.stateDir, "outbox.jsonl")).records };
|
|
1126
1135
|
}
|
|
1127
1136
|
|
|
1128
1137
|
export function pruneRunObservationState(
|
|
@@ -1134,55 +1143,44 @@ export function pruneRunObservationState(
|
|
|
1134
1143
|
): void {
|
|
1135
1144
|
const activeRuns = new Set(summary.runs.map((run) => runObservationKey(run)));
|
|
1136
1145
|
const terminalRunSet = new Set(terminalRuns);
|
|
1137
|
-
const terminalLineKeys = new Set(
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
);
|
|
1142
|
-
const activeLineKeys = new Set(
|
|
1143
|
-
summary.runs.map((run) => run.stateDir ?? run.run),
|
|
1144
|
-
);
|
|
1146
|
+
const terminalLineKeys = new Set(summary.runs
|
|
1147
|
+
.filter((run) => terminalRunSet.has(runObservationKey(run)))
|
|
1148
|
+
.map((run) => runObservationKey(run)));
|
|
1149
|
+
const activeLineKeys = new Set(summary.runs.map((run) => run.stateDir ?? run.run));
|
|
1145
1150
|
for (const run of terminalRunSet) previousStatuses.delete(run);
|
|
1146
|
-
for (const run of previousStatuses.keys())
|
|
1151
|
+
for (const run of previousStatuses.keys())
|
|
1147
1152
|
if (!activeRuns.has(run)) previousStatuses.delete(run);
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
}
|
|
1153
|
-
}
|
|
1154
|
-
for (const key of seenEventIds.keys()) {
|
|
1155
|
-
if (terminalLineKeys.has(key) || !activeLineKeys.has(key)) {
|
|
1156
|
-
seenEventIds.delete(key);
|
|
1157
|
-
}
|
|
1158
|
-
}
|
|
1153
|
+
for (const key of previousLineCounts.keys())
|
|
1154
|
+
if (terminalLineKeys.has(key) || !activeLineKeys.has(key)) previousLineCounts.delete(key);
|
|
1155
|
+
for (const key of seenEventIds.keys())
|
|
1156
|
+
if (terminalLineKeys.has(key) || !activeLineKeys.has(key)) seenEventIds.delete(key);
|
|
1159
1157
|
}
|
|
1160
1158
|
|
|
1161
1159
|
export function detectRunAttentionEvents(
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
seenEventIds: Map<string, Set<string>> = new Map(),
|
|
1160
|
+
legacyLineCounts: Map<string, number>, summary: RunSummary,
|
|
1161
|
+
seenEventIds: Map<string, Set<string>> = new Map(), prime = false,
|
|
1165
1162
|
): RunAttentionEvent[] {
|
|
1166
1163
|
const events: RunAttentionEvent[] = [];
|
|
1167
1164
|
for (const run of summary.runs) {
|
|
1168
1165
|
const key = run.stateDir ?? run.run;
|
|
1169
|
-
const
|
|
1170
|
-
const
|
|
1171
|
-
if (run.notificationPolicy === "silent") {
|
|
1172
|
-
previousLineCounts.set(key, records.length);
|
|
1173
|
-
seenEventIds.set(key, seenEventIds.get(key) ?? new Set<string>());
|
|
1174
|
-
continue;
|
|
1175
|
-
}
|
|
1176
|
-
const start = Math.min(previousCount, records.length);
|
|
1166
|
+
const read = readTraceAttentionRecords(run);
|
|
1167
|
+
const retained = new Set<string>();
|
|
1177
1168
|
const seen = seenEventIds.get(key) ?? new Set<string>();
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1169
|
+
const start = read.canonical ? 0
|
|
1170
|
+
: Math.min(legacyLineCounts.get(key) ?? 0, read.records.length);
|
|
1171
|
+
for (const [index, record] of read.records.entries()) {
|
|
1172
|
+
const event = parseAttentionRecord(record, run, index);
|
|
1173
|
+
if (!event || !shouldNotifyRunAttentionEvent(event) ||
|
|
1174
|
+
event.kind === "runtime.trace_compacted") continue;
|
|
1175
|
+
retained.add(event.id);
|
|
1176
|
+
if (prime || run.notificationPolicy === "silent") seen.add(event.id);
|
|
1177
|
+
else if (index >= start && !seen.has(event.id)) {
|
|
1178
|
+
events.push(event); seen.add(event.id);
|
|
1179
|
+
}
|
|
1183
1180
|
}
|
|
1184
|
-
|
|
1185
|
-
|
|
1181
|
+
seenEventIds.set(key, new Set([...retained].filter((id) => seen.has(id))));
|
|
1182
|
+
if (read.canonical) legacyLineCounts.delete(key);
|
|
1183
|
+
else legacyLineCounts.set(key, read.records.length);
|
|
1186
1184
|
}
|
|
1187
1185
|
return events;
|
|
1188
1186
|
}
|
package/lib/prompts.ts
CHANGED
|
@@ -22,7 +22,7 @@ export const REGISTER_TOOL_GUIDELINES = [
|
|
|
22
22
|
export const ONBOARDING_SYSTEM_PROMPT = `pi-actors quick model:
|
|
23
23
|
- Local-first actor memory: persist trusted local capabilities instead of rebuilding shell recipes.
|
|
24
24
|
- Layers: task -> command template -> recipe/tool -> spawn -> run:<id>; tool:<name> wraps registered capabilities.
|
|
25
|
-
- Command templates stay sync and shell-free: string leaves split into executable + argv,
|
|
25
|
+
- Command templates stay sync and shell-free: string leaves split into executable + argv, infer .js/.mjs through node→bun→deno run and .sh through bash, and treat operators such as && as literal arguments; use template arrays for sequencing or an explicit trusted shell/script when shell semantics are required. Flags include args/defaults, parallel, concurrency, min_successful, when, timeout, delay, retry, failure, recover, repeat, accept_output, output.
|
|
26
26
|
- Placeholders support typed/default args plus {value??fallback} and {flag?yes:no}.
|
|
27
27
|
- ~/.pi/agent/recipes/*.json is actor muscle memory: every recipe there is auto-registered as an agent tool across sessions; register_tool writes there.
|
|
28
28
|
- Recipes own template directly and may declare metadata/defaults/imports/control/artifacts; files >1 MiB or import depth >32 fail closed.
|
package/lib/recipe-control.ts
CHANGED
|
@@ -4,7 +4,9 @@
|
|
|
4
4
|
* Owns pure Recipe control validation; Recipe loading and Run capture stay in recipe/run domains.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
import * as Control from "./control.ts";
|
|
8
|
+
import * as Limits from "./limits.ts";
|
|
9
|
+
|
|
8
10
|
const RUNTIME_RUN_ACTIONS = new Set(["archive", "kill", "prune"]);
|
|
9
11
|
|
|
10
12
|
export function normalizeRecipeControl(value: unknown): string[] | undefined {
|
|
@@ -19,9 +21,14 @@ export function normalizeRecipeControl(value: unknown): string[] | undefined {
|
|
|
19
21
|
throw new Error("recipe.control actions must be non-empty strings");
|
|
20
22
|
}
|
|
21
23
|
const action = raw.trim();
|
|
22
|
-
if (!
|
|
24
|
+
if (!Control.isControlAction(action)) {
|
|
23
25
|
throw new Error(`invalid recipe.control action: ${action}`);
|
|
24
26
|
}
|
|
27
|
+
if (action.length > Limits.CONTROL_ACTION_MAX_LENGTH) {
|
|
28
|
+
throw new Error(
|
|
29
|
+
`recipe.control action exceeds ${Limits.CONTROL_ACTION_MAX_LENGTH} ASCII characters: ${action}`,
|
|
30
|
+
);
|
|
31
|
+
}
|
|
25
32
|
if (RUNTIME_RUN_ACTIONS.has(action)) {
|
|
26
33
|
throw new Error(
|
|
27
34
|
`recipe.control action is runtime-reserved and must not be declared: ${action}`,
|
package/lib/review-control.ts
CHANGED
|
@@ -124,14 +124,13 @@ export function controlAutomaticReview(
|
|
|
124
124
|
return {
|
|
125
125
|
...result,
|
|
126
126
|
next_actions: nextActions,
|
|
127
|
-
sent: result.changed === true,
|
|
128
127
|
};
|
|
129
128
|
}
|
|
130
129
|
|
|
131
|
-
export function parseAutomaticReviewScope(
|
|
132
|
-
const record =
|
|
133
|
-
?
|
|
130
|
+
export function parseAutomaticReviewScope(input: unknown): AutomaticReviewScope {
|
|
131
|
+
const record = input && typeof input === "object" && !Array.isArray(input)
|
|
132
|
+
? input as Record<string, unknown>
|
|
134
133
|
: {};
|
|
135
134
|
if (record.scope === "draft" || record.scope === "tool") return record.scope;
|
|
136
|
-
throw new Error("review
|
|
135
|
+
throw new Error("review Control requires input.scope=draft or input.scope=tool.");
|
|
137
136
|
}
|