@llblab/pi-actors 0.43.0 → 0.43.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 +12 -6
- package/BACKLOG.md +189 -1
- package/CHANGELOG.md +180 -301
- package/README.md +9 -7
- package/dist/index.js +1 -1
- package/dist/lib/async-runs.d.ts +2 -1
- package/dist/lib/async-runs.js +16 -4
- 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 +1 -0
- package/dist/lib/file-state.js +98 -42
- package/dist/lib/inspector-overlay.d.ts +2 -0
- package/dist/lib/inspector-overlay.js +92 -53
- package/dist/lib/limits.d.ts +5 -3
- package/dist/lib/limits.js +5 -3
- 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/runs-control-delivery.d.ts +8 -1
- package/dist/lib/runs-control-delivery.js +38 -15
- package/dist/lib/runs-controls.d.ts +2 -0
- package/dist/lib/runs-controls.js +5 -3
- package/dist/lib/runs-trace.d.ts +2 -2
- package/dist/lib/runs-trace.js +24 -20
- 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 +76 -0
- package/dist/lib/tool-review-scheduler.js +7 -7
- package/dist/lib/tools-inspect.js +53 -14
- 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 +30 -10
- package/dist/scripts/locker.mjs +9 -16
- package/dist/scripts/music-player.mjs +7 -13
- package/dist/scripts/release-gates.mjs +33 -2
- package/dist/scripts/validate-recipe.mjs +5 -4
- package/dist/skills/actors/SKILL.md +12 -7
- package/dist/skills/swarm/SKILL.md +0 -2
- package/docs/0.43-baseline.md +18 -23
- package/docs/README.md +2 -4
- package/docs/actor-inspector.md +5 -5
- package/docs/async-runs.md +2 -2
- package/docs/command-templates.md +6 -116
- package/docs/recipe-library.md +2 -4
- 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 +18 -5
- 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 +68 -61
- package/lib/inspector-overlay.ts +93 -58
- package/lib/limits.ts +5 -3
- package/lib/prompts.ts +1 -1
- package/lib/recipe-control.ts +9 -2
- package/lib/review-control.ts +4 -5
- package/lib/runs-control-delivery.ts +45 -17
- package/lib/runs-controls.ts +12 -3
- package/lib/runs-trace.ts +23 -19
- package/lib/runtime-identity.ts +39 -0
- package/lib/runtime-triage.ts +120 -0
- package/lib/tool-review-scheduler.ts +7 -7
- package/lib/tools-inspect.ts +60 -16
- 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 +33 -10
- package/package.json +1 -1
- package/scripts/locker.mjs +9 -16
- package/scripts/music-player.mjs +7 -13
- package/scripts/release-gates.mjs +33 -2
- package/scripts/validate-recipe.mjs +5 -4
- package/skills/actors/SKILL.md +12 -7
- package/skills/swarm/SKILL.md +0 -2
- 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/inspector-overlay.ts
CHANGED
|
@@ -16,6 +16,7 @@ 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";
|
|
21
22
|
import * as TraceProjection from "./trace-projection.ts";
|
|
@@ -58,6 +59,12 @@ const TRACE_SOURCES: TraceProjection.TraceSourceFilter[] = [
|
|
|
58
59
|
"runtime",
|
|
59
60
|
];
|
|
60
61
|
|
|
62
|
+
function newestTraceItems(items: readonly TraceProjection.TraceItem[]): TraceProjection.TraceItem[] {
|
|
63
|
+
return [...items].sort((left, right) =>
|
|
64
|
+
right.ts.localeCompare(left.ts) || right.id.localeCompare(left.id),
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
61
68
|
export class ActorInspectorOverlay {
|
|
62
69
|
private readonly done: () => void;
|
|
63
70
|
private readonly killRun?: (run: string, runInstanceId: string) => ActorInspectorActionResult;
|
|
@@ -77,6 +84,7 @@ export class ActorInspectorOverlay {
|
|
|
77
84
|
private killDialogChoice: "cancel" | "kill" = "cancel";
|
|
78
85
|
private rowIndex = 0;
|
|
79
86
|
private runCache?: ActorInspector.ActorInspectorRunItem[];
|
|
87
|
+
private selectedTraceId?: string;
|
|
80
88
|
private runIndex = 0;
|
|
81
89
|
private selectorIndex = 0;
|
|
82
90
|
private selectorMode?: SelectorMode;
|
|
@@ -99,8 +107,7 @@ export class ActorInspectorOverlay {
|
|
|
99
107
|
this.theme = options.theme;
|
|
100
108
|
this.tui = options.tui;
|
|
101
109
|
this.refreshTimer = setInterval(() => {
|
|
102
|
-
this.
|
|
103
|
-
if (this.focus !== "list" && this.focus !== "detail") this.traceCache = undefined;
|
|
110
|
+
this.invalidate();
|
|
104
111
|
this.tui.requestRender();
|
|
105
112
|
}, 1_000);
|
|
106
113
|
this.refreshTimer.unref?.();
|
|
@@ -110,7 +117,10 @@ export class ActorInspectorOverlay {
|
|
|
110
117
|
clearInterval(this.refreshTimer);
|
|
111
118
|
}
|
|
112
119
|
|
|
113
|
-
invalidate(): void {
|
|
120
|
+
invalidate(): void {
|
|
121
|
+
this.runCache = undefined;
|
|
122
|
+
this.traceCache = undefined;
|
|
123
|
+
}
|
|
114
124
|
|
|
115
125
|
handleInput(data: string): void {
|
|
116
126
|
const runs = this.runs();
|
|
@@ -134,10 +144,12 @@ export class ActorInspectorOverlay {
|
|
|
134
144
|
this.selectorMode = undefined;
|
|
135
145
|
this.focus = this.tab === "trace" ? "tabs" : "runs";
|
|
136
146
|
} else if (this.focus === "detail") {
|
|
147
|
+
this.selectedTraceId = this.traceDetail?.id;
|
|
137
148
|
this.traceDetail = undefined;
|
|
138
149
|
this.detailScroll = 0;
|
|
139
150
|
this.focus = "list";
|
|
140
151
|
} else if (this.focus === "document" || this.focus === "list") {
|
|
152
|
+
if (this.focus === "list") this.selectedTraceId = undefined;
|
|
141
153
|
this.focus = "tabs";
|
|
142
154
|
} else {
|
|
143
155
|
this.done();
|
|
@@ -179,19 +191,27 @@ export class ActorInspectorOverlay {
|
|
|
179
191
|
} else if (matchesKey(data, "up")) {
|
|
180
192
|
this.focus = "runs";
|
|
181
193
|
} else if (matchesKey(data, "down")) {
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
194
|
+
const items = this.tab === "trace" ? this.traceItems(runs[this.runIndex]) : [];
|
|
195
|
+
this.focus = items.length > 0 ? "list" : "document";
|
|
196
|
+
if (this.focus === "list") {
|
|
197
|
+
this.rowIndex = 0;
|
|
198
|
+
this.selectedTraceId = items[0]?.id;
|
|
199
|
+
}
|
|
185
200
|
} else if (matchesKey(data, "return")) {
|
|
186
201
|
if (this.tab === "trace") this.openSourceSelector(runs[this.runIndex]);
|
|
187
202
|
else this.focus = "document";
|
|
188
203
|
}
|
|
189
204
|
} else if (this.focus === "list") {
|
|
190
|
-
const
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
205
|
+
const items = this.traceItems(runs[this.runIndex]);
|
|
206
|
+
const count = items.length;
|
|
207
|
+
if (matchesKey(data, "left")) {
|
|
208
|
+
this.selectedTraceId = undefined;
|
|
209
|
+
this.focus = "tabs";
|
|
210
|
+
} else if (matchesKey(data, "up")) {
|
|
211
|
+
if (this.rowIndex === 0) {
|
|
212
|
+
this.selectedTraceId = undefined;
|
|
213
|
+
this.focus = "tabs";
|
|
214
|
+
} else this.rowIndex -= 1;
|
|
195
215
|
} else if (matchesKey(data, "down")) {
|
|
196
216
|
this.rowIndex = Math.min(Math.max(0, count - 1), this.rowIndex + 1);
|
|
197
217
|
} else if (matchesKey(data, "pageUp")) {
|
|
@@ -202,13 +222,14 @@ export class ActorInspectorOverlay {
|
|
|
202
222
|
this.rowIndex + this.contentViewportRows(),
|
|
203
223
|
);
|
|
204
224
|
} else if (matchesKey(data, "return") || matchesKey(data, "right")) {
|
|
205
|
-
const item =
|
|
225
|
+
const item = items[this.rowIndex];
|
|
206
226
|
if (item) {
|
|
207
227
|
this.traceDetail = item;
|
|
208
228
|
this.detailScroll = 0;
|
|
209
229
|
this.focus = "detail";
|
|
210
230
|
}
|
|
211
231
|
}
|
|
232
|
+
if (this.focus === "list") this.selectedTraceId = items[this.rowIndex]?.id;
|
|
212
233
|
}
|
|
213
234
|
if (data.toLowerCase() === "f" && this.tab === "trace") {
|
|
214
235
|
this.cycleTraceSource(runs[this.runIndex]);
|
|
@@ -273,6 +294,7 @@ export class ActorInspectorOverlay {
|
|
|
273
294
|
this.documentScroll = 0;
|
|
274
295
|
this.detailScroll = 0;
|
|
275
296
|
this.rowIndex = 0;
|
|
297
|
+
this.selectedTraceId = undefined;
|
|
276
298
|
this.traceDetail = undefined;
|
|
277
299
|
}
|
|
278
300
|
|
|
@@ -291,6 +313,7 @@ export class ActorInspectorOverlay {
|
|
|
291
313
|
} else if (matchesKey(data, "pageDown")) {
|
|
292
314
|
this[key] += this.contentViewportRows();
|
|
293
315
|
} else if (matchesKey(data, "left") && target === "detail") {
|
|
316
|
+
this.selectedTraceId = this.traceDetail?.id;
|
|
294
317
|
this.traceDetail = undefined;
|
|
295
318
|
this.detailScroll = 0;
|
|
296
319
|
this.focus = "list";
|
|
@@ -392,7 +415,9 @@ export class ActorInspectorOverlay {
|
|
|
392
415
|
this.killDialogChoice = "cancel";
|
|
393
416
|
if (!confirmation || !this.killRun) return;
|
|
394
417
|
try {
|
|
395
|
-
|
|
418
|
+
const result = this.killRun(confirmation.run, confirmation.runInstanceId);
|
|
419
|
+
this.feedback = result.ok ? undefined : result;
|
|
420
|
+
if (result.ok) this.invalidate();
|
|
396
421
|
} catch (error) {
|
|
397
422
|
const message = error instanceof Error ? error.message : String(error);
|
|
398
423
|
this.feedback = {
|
|
@@ -418,7 +443,6 @@ export class ActorInspectorOverlay {
|
|
|
418
443
|
`${this.theme.fg("muted", "Current status:")} ${this.theme.fg("warning", confirmation.status)}`,
|
|
419
444
|
"",
|
|
420
445
|
this.theme.fg("text", "This sends canonical kill Control."),
|
|
421
|
-
this.theme.fg("error", "The action is destructive and cannot be undone."),
|
|
422
446
|
"",
|
|
423
447
|
`${cancel} ${kill}`,
|
|
424
448
|
"",
|
|
@@ -521,7 +545,15 @@ export class ActorInspectorOverlay {
|
|
|
521
545
|
private renderTraceList(run: ActorInspector.ActorInspectorRunItem): string[] {
|
|
522
546
|
const items = this.traceItems(run);
|
|
523
547
|
if (items.length === 0) return [this.theme.fg("muted", " No Trace evidence")];
|
|
548
|
+
if (this.selectedTraceId) {
|
|
549
|
+
const selectedIndex = items.findIndex((item) => item.id === this.selectedTraceId);
|
|
550
|
+
if (selectedIndex >= 0) this.rowIndex = selectedIndex;
|
|
551
|
+
else this.selectedTraceId = undefined;
|
|
552
|
+
}
|
|
524
553
|
this.rowIndex = Math.min(this.rowIndex, items.length - 1);
|
|
554
|
+
if (this.focus === "list" && !this.selectedTraceId) {
|
|
555
|
+
this.selectedTraceId = items[this.rowIndex]?.id;
|
|
556
|
+
}
|
|
525
557
|
const viewportRows = this.contentViewportRows();
|
|
526
558
|
const maxStart = Math.max(0, items.length - viewportRows);
|
|
527
559
|
const start = Math.max(0, Math.min(this.rowIndex - Math.floor(viewportRows / 2), maxStart));
|
|
@@ -536,12 +568,12 @@ export class ActorInspectorOverlay {
|
|
|
536
568
|
const marker = item.level === "error"
|
|
537
569
|
? this.theme.fg("error", "!")
|
|
538
570
|
: detail.attention
|
|
539
|
-
? this.theme.fg("warning", "
|
|
540
|
-
:
|
|
571
|
+
? this.theme.fg("warning", "A")
|
|
572
|
+
: " ";
|
|
541
573
|
const prefix = this.focus === "list" && index === this.rowIndex
|
|
542
574
|
? this.theme.fg("accent", " ▶ ")
|
|
543
575
|
: " ";
|
|
544
|
-
const row = `${prefix}${this.theme.fg("text", `#${
|
|
576
|
+
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
577
|
return this.focus === "list" && index === this.rowIndex
|
|
546
578
|
? this.theme.fg("accent", row)
|
|
547
579
|
: row;
|
|
@@ -577,7 +609,10 @@ export class ActorInspectorOverlay {
|
|
|
577
609
|
actor_actions: Array.isArray(meta.control) ? meta.control : [],
|
|
578
610
|
runtime_actions: run.status === "running" ? ["kill"] : ["archive", "prune"],
|
|
579
611
|
endpoint,
|
|
580
|
-
recent_controls: RunsControls.readRunControlsFromStateDir(stateDir)
|
|
612
|
+
recent_controls: RunsControls.readRunControlsFromStateDir(stateDir)
|
|
613
|
+
.slice(-20)
|
|
614
|
+
.reverse()
|
|
615
|
+
.map(ControlProjection.projectRunControl),
|
|
581
616
|
};
|
|
582
617
|
}
|
|
583
618
|
|
|
@@ -589,10 +624,10 @@ export class ActorInspectorOverlay {
|
|
|
589
624
|
) {
|
|
590
625
|
return this.traceCache.items;
|
|
591
626
|
}
|
|
592
|
-
const items = this.readTrace(path.join(this.stateRoot, run.run), {
|
|
627
|
+
const items = newestTraceItems(this.readTrace(path.join(this.stateRoot, run.run), {
|
|
593
628
|
limit: 100,
|
|
594
629
|
source: "all",
|
|
595
|
-
});
|
|
630
|
+
}));
|
|
596
631
|
this.traceCache = { run: run.run, runInstanceId: run.runInstanceId, items };
|
|
597
632
|
return items;
|
|
598
633
|
}
|
|
@@ -627,14 +662,9 @@ export class ActorInspectorOverlay {
|
|
|
627
662
|
width: number,
|
|
628
663
|
): { lines: string[]; stripes: number[] } {
|
|
629
664
|
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
|
-
})
|
|
665
|
+
? Object.entries(value as Record<string, unknown>).map(([key, item]) =>
|
|
666
|
+
this.labeledDocumentLines(key, item)
|
|
667
|
+
)
|
|
638
668
|
: [this.document(value)];
|
|
639
669
|
const lines: string[] = [];
|
|
640
670
|
const stripes: number[] = [];
|
|
@@ -658,23 +688,22 @@ export class ActorInspectorOverlay {
|
|
|
658
688
|
return undefined;
|
|
659
689
|
}
|
|
660
690
|
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
|
-
}
|
|
691
|
+
if (Object.keys(value as Record<string, unknown>).length === 0) return "{}";
|
|
674
692
|
}
|
|
675
693
|
return undefined;
|
|
676
694
|
}
|
|
677
695
|
|
|
696
|
+
private labeledDocumentLines(key: string, value: unknown, depth = 0): string[] {
|
|
697
|
+
const inline = this.inlineDocumentValue(value);
|
|
698
|
+
if (inline !== undefined) return [`${" ".repeat(depth)}${key}: ${inline}`];
|
|
699
|
+
if (value && typeof value === "object") {
|
|
700
|
+
const indent = " ".repeat(depth);
|
|
701
|
+
const nested = this.document(value, depth);
|
|
702
|
+
return [`${indent}${key}: ${nested[0]!.slice(indent.length)}`, ...nested.slice(1)];
|
|
703
|
+
}
|
|
704
|
+
return this.labeledScalarLines(key, value, depth);
|
|
705
|
+
}
|
|
706
|
+
|
|
678
707
|
private labeledScalarLines(key: string, value: unknown, depth = 0): string[] {
|
|
679
708
|
const indent = " ".repeat(depth);
|
|
680
709
|
const [first = "", ...rest] = String(value ?? "none").split(/\r?\n/u);
|
|
@@ -689,25 +718,31 @@ export class ActorInspectorOverlay {
|
|
|
689
718
|
const inline = this.inlineDocumentValue(value);
|
|
690
719
|
if (inline !== undefined) return [`${" ".repeat(depth)}${inline}`];
|
|
691
720
|
if (Array.isArray(value)) {
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
721
|
+
const indent = " ".repeat(depth);
|
|
722
|
+
return [
|
|
723
|
+
`${indent}[`,
|
|
724
|
+
...value.flatMap((item, index) => {
|
|
725
|
+
const marker = `${indent} - #${index + 1}`;
|
|
726
|
+
const itemInline = this.inlineDocumentValue(item);
|
|
727
|
+
return itemInline !== undefined
|
|
728
|
+
? [`${marker}: ${itemInline}`]
|
|
729
|
+
: [marker, ...this.document(item, depth + 2)];
|
|
730
|
+
}),
|
|
731
|
+
`${indent}]`,
|
|
732
|
+
];
|
|
699
733
|
}
|
|
700
734
|
if (typeof value === "object") {
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
735
|
+
const indent = " ".repeat(depth);
|
|
736
|
+
const entries = Object.entries(value as Record<string, unknown>);
|
|
737
|
+
return [
|
|
738
|
+
`${indent}{`,
|
|
739
|
+
...entries.flatMap(([key, item], index) => {
|
|
740
|
+
const lines = this.labeledDocumentLines(key, item, depth + 1);
|
|
741
|
+
if (index < entries.length - 1) lines[lines.length - 1] += ",";
|
|
742
|
+
return lines;
|
|
743
|
+
}),
|
|
744
|
+
`${indent}}`,
|
|
745
|
+
];
|
|
711
746
|
}
|
|
712
747
|
return String(value)
|
|
713
748
|
.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;
|
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
|
}
|
|
@@ -15,6 +15,8 @@ import {
|
|
|
15
15
|
import { createConnection } from "node:net";
|
|
16
16
|
import { join } from "node:path";
|
|
17
17
|
|
|
18
|
+
import * as Control from "./control.ts";
|
|
19
|
+
import * as Limits from "./limits.ts";
|
|
18
20
|
import {
|
|
19
21
|
appendRunControlInStateDir,
|
|
20
22
|
updateRunControlStatusInStateDir,
|
|
@@ -37,7 +39,26 @@ export interface DeliverRunControlRequest {
|
|
|
37
39
|
run_instance_id: string;
|
|
38
40
|
}
|
|
39
41
|
|
|
40
|
-
|
|
42
|
+
const PREFLIGHT_CONTROL_ID = "00000000-0000-4000-8000-000000000000";
|
|
43
|
+
|
|
44
|
+
export function encodeRunControlWire(wire: {
|
|
45
|
+
action: string;
|
|
46
|
+
id: string;
|
|
47
|
+
input?: unknown;
|
|
48
|
+
}): { bytes: number; payload: string } {
|
|
49
|
+
const payload = `${JSON.stringify({
|
|
50
|
+
id: wire.id,
|
|
51
|
+
action: wire.action,
|
|
52
|
+
...(wire.input !== undefined ? { input: wire.input } : {}),
|
|
53
|
+
})}\n`;
|
|
54
|
+
const bytes = Buffer.byteLength(payload);
|
|
55
|
+
if (bytes > Limits.CONTROL_WIRE_MAX_BYTES) {
|
|
56
|
+
throw new Error(
|
|
57
|
+
`Run Control wire record exceeds the ${Limits.CONTROL_WIRE_MAX_BYTES}-byte portable bound`,
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
return { bytes, payload };
|
|
61
|
+
}
|
|
41
62
|
|
|
42
63
|
export function readRunControlEndpoint(
|
|
43
64
|
stateDir: string,
|
|
@@ -107,7 +128,21 @@ export async function deliverRunControl(
|
|
|
107
128
|
request: DeliverRunControlRequest,
|
|
108
129
|
options: DeliverRunControlOptions = {},
|
|
109
130
|
): Promise<Record<string, unknown>> {
|
|
110
|
-
const
|
|
131
|
+
const action = Control.normalizeControlAction(request.action);
|
|
132
|
+
const input = request.input === undefined
|
|
133
|
+
? undefined
|
|
134
|
+
: Control.normalizeControlInput(request.input);
|
|
135
|
+
const normalizedRequest = {
|
|
136
|
+
action,
|
|
137
|
+
...(input !== undefined ? { input } : {}),
|
|
138
|
+
run_instance_id: request.run_instance_id,
|
|
139
|
+
};
|
|
140
|
+
encodeRunControlWire({
|
|
141
|
+
id: PREFLIGHT_CONTROL_ID,
|
|
142
|
+
action,
|
|
143
|
+
...(input !== undefined ? { input } : {}),
|
|
144
|
+
});
|
|
145
|
+
const control = appendRunControlInStateDir(stateDir, normalizedRequest);
|
|
111
146
|
const endpoint = readRunControlEndpoint(stateDir, request.run_instance_id);
|
|
112
147
|
if (!endpoint) {
|
|
113
148
|
updateRunControlStatusInStateDir(
|
|
@@ -118,31 +153,24 @@ export async function deliverRunControl(
|
|
|
118
153
|
["queued", "delivered"],
|
|
119
154
|
);
|
|
120
155
|
throw Object.assign(new Error("Run Control endpoint is not ready."), {
|
|
121
|
-
action
|
|
156
|
+
action,
|
|
122
157
|
control_id: control.id,
|
|
123
158
|
reason: "endpoint_not_ready",
|
|
124
159
|
run,
|
|
125
160
|
run_instance_id: request.run_instance_id,
|
|
126
161
|
});
|
|
127
162
|
}
|
|
128
|
-
const wire = {
|
|
129
|
-
id: control.id,
|
|
130
|
-
action: request.action,
|
|
131
|
-
...(request.input !== undefined ? { input: request.input } : {}),
|
|
132
|
-
};
|
|
133
|
-
const payload = `${JSON.stringify(wire)}\n`;
|
|
134
|
-
const bytes = Buffer.byteLength(payload);
|
|
135
163
|
try {
|
|
164
|
+
const { bytes, payload } = encodeRunControlWire({
|
|
165
|
+
id: control.id,
|
|
166
|
+
action,
|
|
167
|
+
...(input !== undefined ? { input } : {}),
|
|
168
|
+
});
|
|
136
169
|
let written: number;
|
|
137
170
|
if (endpoint.type === "fifo") {
|
|
138
171
|
if ((options.platform ?? process.platform) === "win32") {
|
|
139
172
|
throw new Error("FIFO Control delivery is unsupported on native Windows");
|
|
140
173
|
}
|
|
141
|
-
if (bytes > FIFO_ATOMIC_CONTROL_MAX_BYTES) {
|
|
142
|
-
throw new Error(
|
|
143
|
-
`FIFO Control payload exceeds the ${FIFO_ATOMIC_CONTROL_MAX_BYTES}-byte portable atomic-write bound`,
|
|
144
|
-
);
|
|
145
|
-
}
|
|
146
174
|
written = sendToFifo(endpoint, payload);
|
|
147
175
|
} else {
|
|
148
176
|
written = await sendToNamedPipe(endpoint, payload, options.namedPipeSend);
|
|
@@ -152,7 +180,7 @@ export async function deliverRunControl(
|
|
|
152
180
|
}
|
|
153
181
|
updateRunControlStatusInStateDir(stateDir, control.id, "delivered");
|
|
154
182
|
return {
|
|
155
|
-
action
|
|
183
|
+
action,
|
|
156
184
|
bytes,
|
|
157
185
|
control_id: control.id,
|
|
158
186
|
delivery: "delivered",
|
|
@@ -170,7 +198,7 @@ export async function deliverRunControl(
|
|
|
170
198
|
["queued", "delivered"],
|
|
171
199
|
);
|
|
172
200
|
throw Object.assign(new Error(`Run Control delivery failed: ${reason}`), {
|
|
173
|
-
action
|
|
201
|
+
action,
|
|
174
202
|
control_id: control.id,
|
|
175
203
|
endpoint_type: endpoint.type,
|
|
176
204
|
reason: "delivery_failed",
|
package/lib/runs-controls.ts
CHANGED
|
@@ -9,7 +9,11 @@ import { writeFileSync } from "node:fs";
|
|
|
9
9
|
import { join } from "node:path";
|
|
10
10
|
|
|
11
11
|
import { acquireFileMutationLock, writeTextAtomic } from "./file-state.ts";
|
|
12
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
readJsonlFileResilient,
|
|
14
|
+
type JsonlReadResult,
|
|
15
|
+
} from "./state-readers.ts";
|
|
16
|
+
|
|
13
17
|
export const RUN_CONTROL_TERMINAL_LIMIT = 128;
|
|
14
18
|
|
|
15
19
|
export type RunControlStatus =
|
|
@@ -45,9 +49,14 @@ export function runControlsFile(stateDir: string): string {
|
|
|
45
49
|
return join(stateDir, "controls.jsonl");
|
|
46
50
|
}
|
|
47
51
|
|
|
52
|
+
export function readRunControlJournalFromStateDir(
|
|
53
|
+
stateDir: string,
|
|
54
|
+
): JsonlReadResult<unknown> {
|
|
55
|
+
return readJsonlFileResilient<unknown>(runControlsFile(stateDir));
|
|
56
|
+
}
|
|
57
|
+
|
|
48
58
|
export function readRunControlsFromStateDir(stateDir: string): RunControlRecord[] {
|
|
49
|
-
return
|
|
50
|
-
.records;
|
|
59
|
+
return readRunControlJournalFromStateDir(stateDir).records as RunControlRecord[];
|
|
51
60
|
}
|
|
52
61
|
|
|
53
62
|
function acquireRunControlsLock(stateDir: string): () => void {
|
package/lib/runs-trace.ts
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Run Trace event journal.
|
|
3
|
-
* Zones: structured event validation,
|
|
4
|
-
* Owns Run-local
|
|
3
|
+
* Zones: structured event validation, token-locked append-only writes, resilient bounded reads
|
|
4
|
+
* Owns canonical Run-local Trace persistence; lifecycle projection and owner attention delivery stay in adapters.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { randomUUID } from "node:crypto";
|
|
8
8
|
import { writeFileSync } from "node:fs";
|
|
9
9
|
import { join } from "node:path";
|
|
10
10
|
|
|
11
|
+
import { withFileMutationLock } from "./file-state.ts";
|
|
11
12
|
import * as Limits from "./limits.ts";
|
|
12
13
|
import { readJsonlFileResilient } from "./state-readers.ts";
|
|
13
14
|
|
|
@@ -104,23 +105,26 @@ export function appendRunTraceEvent(
|
|
|
104
105
|
stateDir: string,
|
|
105
106
|
input: AppendTraceEventInput,
|
|
106
107
|
): TraceEvent {
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
encoded
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
108
|
+
const path = runTraceFile(stateDir);
|
|
109
|
+
return withFileMutationLock(path, () => {
|
|
110
|
+
const normalized = normalizeTraceEventInput(input);
|
|
111
|
+
const event: TraceEvent = {
|
|
112
|
+
id: randomUUID(),
|
|
113
|
+
ts: new Date().toISOString(),
|
|
114
|
+
...normalized,
|
|
115
|
+
};
|
|
116
|
+
let encoded: string;
|
|
117
|
+
try {
|
|
118
|
+
encoded = JSON.stringify(event);
|
|
119
|
+
} catch {
|
|
120
|
+
throw new Error("Trace event data must be JSON-serializable");
|
|
121
|
+
}
|
|
122
|
+
if (Buffer.byteLength(encoded) > Limits.TRACE_EVENT_MAX_BYTES) {
|
|
123
|
+
throw new Error(`Trace event exceeds ${Limits.TRACE_EVENT_MAX_BYTES} bytes`);
|
|
124
|
+
}
|
|
125
|
+
writeFileSync(path, `${encoded}\n`, { flag: "a" });
|
|
126
|
+
return event;
|
|
127
|
+
});
|
|
124
128
|
}
|
|
125
129
|
|
|
126
130
|
export function readRunTraceEvents(
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Immutable runtime package identity.
|
|
3
|
+
* Zones: source/installed package version resolution and Run-state schema marker
|
|
4
|
+
* Owns package-local identity reads; runtime status and Run persistence remain in their domains.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { readFileSync } from "node:fs";
|
|
8
|
+
import { dirname, join } from "node:path";
|
|
9
|
+
import { fileURLToPath } from "node:url";
|
|
10
|
+
|
|
11
|
+
export const RUN_STATE_SCHEMA = "run-kernel-v1" as const;
|
|
12
|
+
|
|
13
|
+
let cachedVersion: string | undefined;
|
|
14
|
+
|
|
15
|
+
export function getPackageVersion(): string {
|
|
16
|
+
if (cachedVersion) return cachedVersion;
|
|
17
|
+
const moduleDir = dirname(fileURLToPath(import.meta.url));
|
|
18
|
+
for (const path of [
|
|
19
|
+
join(moduleDir, "..", "package.json"),
|
|
20
|
+
join(moduleDir, "..", "..", "package.json"),
|
|
21
|
+
]) {
|
|
22
|
+
try {
|
|
23
|
+
const pkg = JSON.parse(readFileSync(path, "utf8")) as Record<string, unknown>;
|
|
24
|
+
if (
|
|
25
|
+
pkg.name === "@llblab/pi-actors" &&
|
|
26
|
+
typeof pkg.version === "string" &&
|
|
27
|
+
pkg.version.length > 0 &&
|
|
28
|
+
pkg.version.length <= 128 &&
|
|
29
|
+
pkg.version.trim() === pkg.version
|
|
30
|
+
) {
|
|
31
|
+
cachedVersion = pkg.version;
|
|
32
|
+
return cachedVersion;
|
|
33
|
+
}
|
|
34
|
+
} catch {
|
|
35
|
+
// Source and installed-dist modes resolve through different package ancestors.
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
throw new Error("pi-actors package identity is unavailable");
|
|
39
|
+
}
|