@llblab/pi-actors 0.45.0 → 0.45.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +2 -0
- package/CHANGELOG.md +6 -0
- package/README.md +2 -0
- package/dist/index.d.ts +1 -2
- package/dist/index.js +10 -110
- package/dist/lib/extension-runtime.d.ts +21 -0
- package/dist/lib/extension-runtime.js +116 -0
- package/dist/scripts/async-runner.mjs +0 -16
- package/dist/scripts/executable-block-style.d.mts +1 -0
- package/dist/scripts/executable-block-style.mjs +73 -0
- package/dist/scripts/locker.mjs +0 -21
- package/dist/scripts/recipe-utils.mjs +0 -1
- package/dist/scripts/release-gates.mjs +14 -11
- package/dist/scripts/validate-recipe.mjs +0 -1
- package/index.ts +17 -112
- package/lib/extension-runtime.ts +139 -0
- package/package.json +2 -2
- package/scripts/async-runner.mjs +0 -16
- package/scripts/executable-block-style.d.mts +1 -0
- package/scripts/executable-block-style.mjs +73 -0
- package/scripts/locker.mjs +0 -21
- package/scripts/recipe-utils.mjs +0 -1
- package/scripts/release-gates.mjs +14 -11
- package/scripts/validate-recipe.mjs +0 -1
package/scripts/locker.mjs
CHANGED
|
@@ -65,7 +65,6 @@ async function runLocker(argv = process.argv.slice(2)) {
|
|
|
65
65
|
const controlPath = join(stateDir, "control.fifo");
|
|
66
66
|
let runInstanceId;
|
|
67
67
|
mkdirSync(stateDir, { recursive: true });
|
|
68
|
-
|
|
69
68
|
function readJson(path, fallback) {
|
|
70
69
|
if (!existsSync(path)) return fallback;
|
|
71
70
|
try {
|
|
@@ -74,11 +73,9 @@ async function runLocker(argv = process.argv.slice(2)) {
|
|
|
74
73
|
return fallback;
|
|
75
74
|
}
|
|
76
75
|
}
|
|
77
|
-
|
|
78
76
|
function writeJson(path, value) {
|
|
79
77
|
writeJsonAtomic(path, value);
|
|
80
78
|
}
|
|
81
|
-
|
|
82
79
|
function getControlEndpoint() {
|
|
83
80
|
if (process.platform !== "win32") return { path: controlPath, type: "fifo" };
|
|
84
81
|
const hash = createHash("sha256")
|
|
@@ -87,7 +84,6 @@ async function runLocker(argv = process.argv.slice(2)) {
|
|
|
87
84
|
.slice(0, 20);
|
|
88
85
|
return { path: `\\\\.\\pipe\\pi-actors-locker-${hash}`, type: "named-pipe" };
|
|
89
86
|
}
|
|
90
|
-
|
|
91
87
|
function writeControlEndpoint(endpoint) {
|
|
92
88
|
if (!runInstanceId) throw new Error("Run generation unavailable for Control endpoint");
|
|
93
89
|
writeJson(join(stateDir, "control-endpoint.json"), {
|
|
@@ -96,7 +92,6 @@ async function runLocker(argv = process.argv.slice(2)) {
|
|
|
96
92
|
run_instance_id: runInstanceId,
|
|
97
93
|
});
|
|
98
94
|
}
|
|
99
|
-
|
|
100
95
|
function journal(event, data = {}) {
|
|
101
96
|
const release = acquireFileMutationLock(journalPath);
|
|
102
97
|
try {
|
|
@@ -111,11 +106,9 @@ async function runLocker(argv = process.argv.slice(2)) {
|
|
|
111
106
|
writeTextAtomic(journalPath, content);
|
|
112
107
|
} finally { release(); }
|
|
113
108
|
}
|
|
114
|
-
|
|
115
109
|
function encodeJournal(records) {
|
|
116
110
|
return records.length ? `${records.map((record) => JSON.stringify(record)).join("\n")}\n` : "";
|
|
117
111
|
}
|
|
118
|
-
|
|
119
112
|
function emitTrace(kind, summary, data = {}, level = "info") {
|
|
120
113
|
appendRunTraceEvent(stateDir, {
|
|
121
114
|
attention: "followup",
|
|
@@ -125,11 +118,9 @@ async function runLocker(argv = process.argv.slice(2)) {
|
|
|
125
118
|
summary,
|
|
126
119
|
});
|
|
127
120
|
}
|
|
128
|
-
|
|
129
121
|
function now() {
|
|
130
122
|
return Date.now();
|
|
131
123
|
}
|
|
132
|
-
|
|
133
124
|
function cleanExpiredLocks(locks) {
|
|
134
125
|
const current = now();
|
|
135
126
|
const kept = {};
|
|
@@ -139,7 +130,6 @@ async function runLocker(argv = process.argv.slice(2)) {
|
|
|
139
130
|
}
|
|
140
131
|
return kept;
|
|
141
132
|
}
|
|
142
|
-
|
|
143
133
|
function normalizeControl(line) {
|
|
144
134
|
const trimmed = line.trim();
|
|
145
135
|
if (!trimmed) return undefined;
|
|
@@ -160,8 +150,6 @@ async function runLocker(argv = process.argv.slice(2)) {
|
|
|
160
150
|
);
|
|
161
151
|
}
|
|
162
152
|
}
|
|
163
|
-
|
|
164
|
-
|
|
165
153
|
function tailJournal(count) {
|
|
166
154
|
if (!existsSync(journalPath)) return [];
|
|
167
155
|
return readFileSync(journalPath, "utf8")
|
|
@@ -177,7 +165,6 @@ async function runLocker(argv = process.argv.slice(2)) {
|
|
|
177
165
|
}
|
|
178
166
|
});
|
|
179
167
|
}
|
|
180
|
-
|
|
181
168
|
function printSnapshot() {
|
|
182
169
|
const locks = cleanExpiredLocks(readJson(locksPath, {}));
|
|
183
170
|
writeJson(locksPath, locks);
|
|
@@ -195,7 +182,6 @@ async function runLocker(argv = process.argv.slice(2)) {
|
|
|
195
182
|
),
|
|
196
183
|
);
|
|
197
184
|
}
|
|
198
|
-
|
|
199
185
|
function nextTask(queue, locks) {
|
|
200
186
|
const items = Array.isArray(queue.items) ? queue.items : [];
|
|
201
187
|
const index = items.findIndex((item) => {
|
|
@@ -205,7 +191,6 @@ async function runLocker(argv = process.argv.slice(2)) {
|
|
|
205
191
|
if (index < 0) return undefined;
|
|
206
192
|
return items.splice(index, 1)[0];
|
|
207
193
|
}
|
|
208
|
-
|
|
209
194
|
function handle(control) {
|
|
210
195
|
const action = control.action;
|
|
211
196
|
const body =
|
|
@@ -333,18 +318,15 @@ async function runLocker(argv = process.argv.slice(2)) {
|
|
|
333
318
|
journal("lock.unknown", { action, input: body });
|
|
334
319
|
emitTrace("lock.unknown", `Unknown Control ${action}`, { action, input: body }, "warning");
|
|
335
320
|
}
|
|
336
|
-
|
|
337
321
|
if (mode === "snapshot") {
|
|
338
322
|
printSnapshot();
|
|
339
323
|
process.exit(0);
|
|
340
324
|
}
|
|
341
|
-
|
|
342
325
|
const startupRun = readJson(join(stateDir, "run.json"), undefined);
|
|
343
326
|
if (!startupRun || typeof startupRun.run_instance_id !== "string") {
|
|
344
327
|
throw new Error("Run generation unavailable for Control service");
|
|
345
328
|
}
|
|
346
329
|
runInstanceId = startupRun.run_instance_id;
|
|
347
|
-
|
|
348
330
|
function handleLine(line) {
|
|
349
331
|
const control = normalizeControl(line);
|
|
350
332
|
if (!control) return false;
|
|
@@ -376,7 +358,6 @@ async function runLocker(argv = process.argv.slice(2)) {
|
|
|
376
358
|
return false;
|
|
377
359
|
}
|
|
378
360
|
}
|
|
379
|
-
|
|
380
361
|
async function serveFifo(endpoint) {
|
|
381
362
|
if (!existsSync(endpoint.path)) {
|
|
382
363
|
const result = spawnSync("mkfifo", [endpoint.path]);
|
|
@@ -403,7 +384,6 @@ async function runLocker(argv = process.argv.slice(2)) {
|
|
|
403
384
|
fs.closeSync(fd);
|
|
404
385
|
}
|
|
405
386
|
}
|
|
406
|
-
|
|
407
387
|
async function serveNamedPipe(endpoint) {
|
|
408
388
|
let resolveStopped;
|
|
409
389
|
const stopped = new Promise((resolve) => {
|
|
@@ -428,7 +408,6 @@ async function runLocker(argv = process.argv.slice(2)) {
|
|
|
428
408
|
});
|
|
429
409
|
await stopped;
|
|
430
410
|
}
|
|
431
|
-
|
|
432
411
|
const endpoint = getControlEndpoint();
|
|
433
412
|
writeJson(queuePath, readJson(queuePath, { items: [] }));
|
|
434
413
|
writeJson(locksPath, cleanExpiredLocks(readJson(locksPath, {})));
|
package/scripts/recipe-utils.mjs
CHANGED
|
@@ -7,6 +7,8 @@ import { dirname, extname, join, normalize, relative, resolve } from "node:path"
|
|
|
7
7
|
import { spawnSync } from "node:child_process";
|
|
8
8
|
import { fileURLToPath } from "node:url";
|
|
9
9
|
|
|
10
|
+
import { executableBlockBlankLines } from "./executable-block-style.mjs";
|
|
11
|
+
|
|
10
12
|
const root = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
11
13
|
const temp = mkdtempSync(join(tmpdir(), "pi-actors-release-index-"));
|
|
12
14
|
const indexPath = join(temp, "index");
|
|
@@ -71,7 +73,6 @@ try {
|
|
|
71
73
|
.filter(Boolean);
|
|
72
74
|
const fileSet = new Set(files);
|
|
73
75
|
console.log(`[release] exact temporary index: ${files.length} files`);
|
|
74
|
-
|
|
75
76
|
const secretPatterns = [
|
|
76
77
|
[/-----BEGIN(?: [A-Z0-9]+)? PRIVATE KEY-----[\s\S]{80,}?-----END(?: [A-Z0-9]+)? PRIVATE KEY-----/u, "private-key block"],
|
|
77
78
|
[/\b(?:AKIA|ASIA)[A-Z0-9]{16}\b/u, "AWS access key"],
|
|
@@ -92,7 +93,17 @@ try {
|
|
|
92
93
|
}
|
|
93
94
|
}
|
|
94
95
|
console.log("[release] bounded secret and public-path hygiene checked");
|
|
95
|
-
|
|
96
|
+
const executableSources = files.filter((path) =>
|
|
97
|
+
/\.(?:[cm]?js|ts)$/u.test(path) && !path.startsWith("dist/"),
|
|
98
|
+
);
|
|
99
|
+
for (const path of executableSources) {
|
|
100
|
+
const blankLines = executableBlockBlankLines(stagedText(path) ?? "");
|
|
101
|
+
check(
|
|
102
|
+
blankLines.length === 0,
|
|
103
|
+
`blank lines inside executable blocks: ${path}:${blankLines.join(",")}`,
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
console.log("[release] executable blocks contain no blank lines");
|
|
96
107
|
const removedPaths = [
|
|
97
108
|
"lib/messages.ts",
|
|
98
109
|
"lib/rooms.ts",
|
|
@@ -113,7 +124,6 @@ try {
|
|
|
113
124
|
"lib/run-inspector-overlay.ts",
|
|
114
125
|
];
|
|
115
126
|
for (const path of removedPaths) check(!fileSet.has(path), `removed surface returned: ${path}`);
|
|
116
|
-
|
|
117
127
|
const currentSurface = /^(?:lib\/|scripts\/|recipes\/|fixtures\/|skills\/|docs\/|README\.md|AGENTS\.md)/u;
|
|
118
128
|
const forbiddenPatterns = [
|
|
119
129
|
[/room:</u, "room address"],
|
|
@@ -144,7 +154,6 @@ try {
|
|
|
144
154
|
}
|
|
145
155
|
}
|
|
146
156
|
console.log("[release] removed-surface and legacy-fallback allowlists checked");
|
|
147
|
-
|
|
148
157
|
const directTraceWrite = /(?:appendFileSync|writeFileSync|writeText(?:Atomic)?)\s*\(\s*[A-Za-z0-9_.]*?(?:trace|event)(?:Path|File)/iu;
|
|
149
158
|
for (const path of files.filter((candidate) =>
|
|
150
159
|
(candidate.startsWith("lib/") && candidate.endsWith(".ts")) ||
|
|
@@ -172,7 +181,6 @@ try {
|
|
|
172
181
|
);
|
|
173
182
|
}
|
|
174
183
|
console.log("[release] canonical Trace and Control writer residue checked");
|
|
175
|
-
|
|
176
184
|
// owner | class | lock | record bound | byte bound | recovery
|
|
177
185
|
const appendOnlyOwners = new Set([
|
|
178
186
|
"lib/runs-trace.ts", // canonical Run evidence | token lock | 2048 | 4 MiB | compact valid suffix
|
|
@@ -189,7 +197,6 @@ try {
|
|
|
189
197
|
check(appendOnlyOwners.has(path), `unregistered shipped append-only writer: ${path}`);
|
|
190
198
|
for (const path of appendOnlyOwners) check(fileSet.has(path), `stale append-only owner inventory: ${path}`);
|
|
191
199
|
console.log(`[release] append-only owner inventory checked: ${appendOnlyOwners.size} source owners`);
|
|
192
|
-
|
|
193
200
|
check(
|
|
194
201
|
(stagedText("scripts/validate-recipe.mjs") ?? "").includes(
|
|
195
202
|
"qaReport.diagnostics.length === 0 && qaReport.warnings.length === 0",
|
|
@@ -197,15 +204,13 @@ try {
|
|
|
197
204
|
"Recipe QA warnings are not release-blocking",
|
|
198
205
|
);
|
|
199
206
|
console.log("[release] zero-warning Recipe QA gate checked");
|
|
200
|
-
|
|
201
|
-
const maximumShippedLines = 29_767;
|
|
207
|
+
const maximumShippedLines = 32_000;
|
|
202
208
|
const shippedPath = /^(?:lib\/|scripts\/|recipes\/|docs\/|skills\/)/u;
|
|
203
209
|
const shippedLines = files
|
|
204
210
|
.filter((path) => shippedPath.test(path))
|
|
205
211
|
.reduce((total, path) => total + (((stagedText(path) ?? "").match(/\n/gu) ?? []).length + 1), 0);
|
|
206
212
|
check(shippedLines <= maximumShippedLines, `shipped lines ${shippedLines} exceed release maximum ${maximumShippedLines}`);
|
|
207
213
|
console.log(`[release] shipped lines ${shippedLines} <= release maximum ${maximumShippedLines}`);
|
|
208
|
-
|
|
209
214
|
const sources = files.filter((path) => path === "index.ts" || (path.startsWith("lib/") && path.endsWith(".ts")));
|
|
210
215
|
const sourceSet = new Set(sources);
|
|
211
216
|
const graph = new Map();
|
|
@@ -240,7 +245,6 @@ try {
|
|
|
240
245
|
}
|
|
241
246
|
for (const path of sources) visit(path);
|
|
242
247
|
console.log(`[release] strict Domain DAG: ${sources.length} sources, acyclic`);
|
|
243
|
-
|
|
244
248
|
for (const path of ["README.md", "AGENTS.md", "BACKLOG.md", "CHANGELOG.md", "docs/README.md"]) {
|
|
245
249
|
check(fileSet.has(path), `missing ABCd root/context file: ${path}`);
|
|
246
250
|
}
|
|
@@ -263,7 +267,6 @@ try {
|
|
|
263
267
|
check(docsIndex.includes(relative("docs", path).replaceAll("\\", "/")), `docs/README.md omits ${path}`);
|
|
264
268
|
}
|
|
265
269
|
console.log("[release] ABCd context roots, routing, and Markdown links checked");
|
|
266
|
-
|
|
267
270
|
if (failures.length > 0) {
|
|
268
271
|
for (const failure of failures) console.error(`[FAIL] ${failure}`);
|
|
269
272
|
process.exitCode = 1;
|
|
@@ -187,7 +187,6 @@ export function validateRecipes(argv) {
|
|
|
187
187
|
if (!targetArg || argv.includes("--help") || argv.includes("-h")) {
|
|
188
188
|
return { help: true, ok: Boolean(targetArg), usage: validateRecipeUsage() };
|
|
189
189
|
}
|
|
190
|
-
|
|
191
190
|
const files = recipeFiles(expandPath(targetArg), all);
|
|
192
191
|
const results = files.map((file) => validateFile(file, qa));
|
|
193
192
|
const failed = results.filter((result) => !result.ok).length;
|