@llblab/pi-actors 0.41.1 → 0.42.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 +11 -6
- package/BACKLOG.md +2 -62
- package/CHANGELOG.md +15 -0
- package/README.md +22 -4
- package/dist/index.js +25 -121
- package/dist/lib/async-runs.d.ts +25 -5
- package/dist/lib/async-runs.js +136 -47
- package/dist/lib/automatic-review-runtime.d.ts +18 -0
- package/dist/lib/automatic-review-runtime.js +96 -0
- package/dist/lib/draft-consolidation-transaction.d.ts +65 -0
- package/dist/lib/draft-consolidation-transaction.js +610 -0
- package/dist/lib/draft-consolidation.d.ts +35 -0
- package/dist/lib/draft-consolidation.js +126 -0
- package/dist/lib/draft-review.d.ts +56 -0
- package/dist/lib/draft-review.js +254 -0
- package/dist/lib/draft-sleep.d.ts +65 -0
- package/dist/lib/draft-sleep.js +468 -0
- package/dist/lib/file-state.d.ts +8 -1
- package/dist/lib/file-state.js +115 -18
- package/dist/lib/inspector-actions.d.ts +16 -0
- package/dist/lib/inspector-actions.js +57 -0
- package/dist/lib/inspector-command.d.ts +7 -0
- package/dist/lib/inspector-command.js +37 -0
- package/dist/lib/inspector-overlay.d.ts +20 -3
- package/dist/lib/inspector-overlay.js +275 -74
- package/dist/lib/inspector.d.ts +9 -0
- package/dist/lib/inspector.js +48 -0
- package/dist/lib/observability.d.ts +1 -0
- package/dist/lib/observability.js +11 -1
- package/dist/lib/paths.d.ts +7 -0
- package/dist/lib/paths.js +28 -0
- package/dist/lib/recipes-discovery.js +32 -24
- package/dist/lib/recipes-usage.d.ts +18 -6
- package/dist/lib/recipes-usage.js +445 -34
- package/dist/lib/review-control.d.ts +14 -0
- package/dist/lib/review-control.js +111 -0
- package/dist/lib/review-diagnostics.d.ts +11 -0
- package/dist/lib/review-diagnostics.js +148 -0
- package/dist/lib/review-projection.d.ts +14 -0
- package/dist/lib/review-projection.js +170 -0
- package/dist/lib/run-ui-runtime.d.ts +18 -0
- package/dist/lib/run-ui-runtime.js +123 -0
- package/dist/lib/runs-artifacts.d.ts +1 -1
- package/dist/lib/runs-artifacts.js +1 -1
- package/dist/lib/runs-control.d.ts +8 -2
- package/dist/lib/runs-control.js +23 -6
- package/dist/lib/runs-identity.d.ts +1 -1
- package/dist/lib/runs-identity.js +1 -1
- package/dist/lib/runs-index.d.ts +11 -2
- package/dist/lib/runs-index.js +46 -23
- package/dist/lib/runs-mailbox.d.ts +1 -1
- package/dist/lib/runs-mailbox.js +1 -1
- package/dist/lib/runs-messages.d.ts +1 -1
- package/dist/lib/runs-messages.js +1 -1
- package/dist/lib/runs-outbox.d.ts +1 -1
- package/dist/lib/runs-outbox.js +1 -1
- package/dist/lib/runs-ownership.d.ts +1 -1
- package/dist/lib/runs-ownership.js +1 -1
- package/dist/lib/runs-parent-teardown.d.ts +51 -0
- package/dist/lib/runs-parent-teardown.js +172 -0
- package/dist/lib/runs-process.d.ts +1 -1
- package/dist/lib/runs-process.js +1 -1
- package/dist/lib/runs-retention.d.ts +1 -1
- package/dist/lib/runs-retention.js +1 -1
- package/dist/lib/runs-start.d.ts +5 -3
- package/dist/lib/runs-start.js +6 -48
- package/dist/lib/runs-status.d.ts +5 -3
- package/dist/lib/runs-status.js +4 -6
- package/dist/lib/runtime.d.ts +7 -1
- package/dist/lib/runtime.js +32 -18
- package/dist/lib/tool-review-lineage-transaction.d.ts +27 -0
- package/dist/lib/tool-review-lineage-transaction.js +597 -0
- package/dist/lib/tool-review-lineage.d.ts +24 -0
- package/dist/lib/tool-review-lineage.js +98 -0
- package/dist/lib/tool-review-scheduler.d.ts +80 -0
- package/dist/lib/tool-review-scheduler.js +494 -0
- package/dist/lib/tool-review-transaction.d.ts +50 -0
- package/dist/lib/tool-review-transaction.js +362 -0
- package/dist/lib/tool-review.d.ts +56 -0
- package/dist/lib/tool-review.js +197 -0
- package/dist/lib/tools-inspect.js +26 -3
- package/dist/lib/tools-local.js +4 -2
- package/dist/lib/tools-message.d.ts +1 -0
- package/dist/lib/tools-message.js +29 -17
- package/dist/lib/tools-spawn.js +4 -1
- package/dist/lib/tools.d.ts +1 -0
- package/dist/lib/tools.js +1 -0
- package/dist/recipes/draft-review.json +24 -0
- package/dist/recipes/tool-review.json +24 -0
- package/dist/scripts/release-gates.mjs +165 -0
- package/dist/skills/actors/SKILL.md +9 -8
- package/dist/skills/swarm/SKILL.md +1 -1
- package/docs/actor-inspector.md +20 -11
- package/docs/async-runs.md +9 -1
- package/docs/recipe-library.md +7 -3
- package/docs/template-recipes.md +4 -11
- package/docs/tool-registry.md +11 -4
- package/index.ts +27 -142
- package/lib/async-runs.ts +218 -62
- package/lib/automatic-review-runtime.ts +135 -0
- package/lib/draft-consolidation-transaction.ts +821 -0
- package/lib/draft-consolidation.ts +181 -0
- package/lib/draft-review.ts +325 -0
- package/lib/draft-sleep.ts +576 -0
- package/lib/file-state.ts +143 -19
- package/lib/inspector-actions.ts +79 -0
- package/lib/inspector-command.ts +54 -0
- package/lib/inspector-overlay.ts +325 -91
- package/lib/inspector.ts +72 -0
- package/lib/observability.ts +14 -1
- package/lib/paths.ts +43 -0
- package/lib/recipes-discovery.ts +34 -26
- package/lib/recipes-usage.ts +569 -40
- package/lib/review-control.ts +137 -0
- package/lib/review-diagnostics.ts +164 -0
- package/lib/review-projection.ts +200 -0
- package/lib/run-ui-runtime.ts +153 -0
- package/lib/runs-artifacts.ts +1 -1
- package/lib/runs-control.ts +49 -5
- package/lib/runs-identity.ts +1 -1
- package/lib/runs-index.ts +57 -21
- package/lib/runs-mailbox.ts +1 -1
- package/lib/runs-messages.ts +1 -1
- package/lib/runs-outbox.ts +1 -1
- package/lib/runs-ownership.ts +1 -1
- package/lib/runs-parent-teardown.ts +257 -0
- package/lib/runs-process.ts +1 -1
- package/lib/runs-retention.ts +1 -1
- package/lib/runs-start.ts +12 -68
- package/lib/runs-status.ts +12 -8
- package/lib/runtime.ts +34 -17
- package/lib/tool-review-lineage-transaction.ts +881 -0
- package/lib/tool-review-lineage.ts +145 -0
- package/lib/tool-review-scheduler.ts +635 -0
- package/lib/tool-review-transaction.ts +563 -0
- package/lib/tool-review.ts +270 -0
- package/lib/tools-inspect.ts +33 -3
- package/lib/tools-local.ts +8 -2
- package/lib/tools-message.ts +45 -30
- package/lib/tools-spawn.ts +8 -1
- package/lib/tools.ts +5 -0
- package/package.json +3 -2
- package/recipes/draft-review.json +24 -0
- package/recipes/tool-review.json +24 -0
- package/scripts/release-gates.mjs +165 -0
- package/skills/actors/SKILL.md +9 -8
- package/skills/swarm/SKILL.md +1 -1
package/dist/lib/async-runs.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Command-template async run lifecycle facade.
|
|
3
|
-
* Owns launch, state observation, listing, message/control facade methods, and retention while runs-* subdomains own narrower run internals.
|
|
3
|
+
* Owns: launch, state observation, listing, message/control facade methods, and retention while runs-* subdomains own narrower run internals.
|
|
4
4
|
*/
|
|
5
5
|
import { spawn } from "node:child_process";
|
|
6
|
+
import { randomUUID } from "node:crypto";
|
|
6
7
|
import { closeSync, existsSync, mkdirSync, openSync, readdirSync, statSync, writeFileSync, } from "node:fs";
|
|
7
8
|
import { basename, dirname, extname, join, relative, resolve } from "node:path";
|
|
8
9
|
import { fileURLToPath } from "node:url";
|
|
@@ -20,9 +21,10 @@ import { archiveTerminalRun, pruneTerminalRun } from "./runs-retention.js";
|
|
|
20
21
|
import { captureRunProcessIdentity, verifyRunProcessIdentity, } from "./runs-process.js";
|
|
21
22
|
import * as RunsStart from "./runs-start.js";
|
|
22
23
|
import * as RunsIndex from "./runs-index.js";
|
|
24
|
+
import * as RunsParentTeardown from "./runs-parent-teardown.js";
|
|
23
25
|
import { claimRunInboxMessageInStateDir, parseRunInboxLine, processRunInboxMessagesInStateDir, runInboxFile, updateRunInboxMessageStatusInStateDir, } from "./runs-mailbox.js";
|
|
24
26
|
import { deliverRunMessage, } from "./runs-messages.js";
|
|
25
|
-
import { buildRunStatus, tailFile, tailLines } from "./runs-status.js";
|
|
27
|
+
import { buildRunStatus, tailFile, tailLines, } from "./runs-status.js";
|
|
26
28
|
import { readJsonFileResilient } from "./state-readers.js";
|
|
27
29
|
const RUNNER_IDENTITY_GRACE_MS = 5000;
|
|
28
30
|
const DEFAULT_STATE_ROOT = Paths.getRunStateRoot();
|
|
@@ -201,8 +203,8 @@ function assertCurrentPlaceholderReferencesResolved(template, defaults, values,
|
|
|
201
203
|
throw Object.assign(new Error(`Template recipe requires the current Pi ${check.label} for inheritance, but no current ${check.label} was available. Pass explicit values or launch from a Pi session with a selected ${check.label}. unresolved=${refs.slice(0, 4).join(",")}`), { model_policy: modelPolicy });
|
|
202
204
|
}
|
|
203
205
|
}
|
|
204
|
-
function acquireStateStartLock(stateDir) {
|
|
205
|
-
return RunsStart.acquireStateStartLock(stateDir);
|
|
206
|
+
function acquireStateStartLock(stateDir, options = {}) {
|
|
207
|
+
return RunsStart.acquireStateStartLock(stateDir, options);
|
|
206
208
|
}
|
|
207
209
|
function prepareStateDirForStart(stateDir) {
|
|
208
210
|
RunsStart.prepareStateDirForStart(stateDir, readJson, RUNNER_PATH);
|
|
@@ -228,7 +230,9 @@ export function startRun(params, cwd) {
|
|
|
228
230
|
assertCurrentPlaceholderReferencesResolved(resolved.template, startParams.defaults, values, modelPolicy);
|
|
229
231
|
assertNoActiveRunState(stateDir);
|
|
230
232
|
mkdirSync(stateDir, { recursive: true });
|
|
231
|
-
const releaseStartLock = acquireStateStartLock(stateDir
|
|
233
|
+
const releaseStartLock = acquireStateStartLock(stateDir, {
|
|
234
|
+
onContention: startParams.lifecycleHooks?.onLockContention,
|
|
235
|
+
});
|
|
232
236
|
try {
|
|
233
237
|
claimRunStateDirectory(stateDir, run);
|
|
234
238
|
assertNoActiveRunState(stateDir);
|
|
@@ -244,8 +248,10 @@ export function startRun(params, cwd) {
|
|
|
244
248
|
const recipeContextRecords = recipeFile && includeActorRecipeContext
|
|
245
249
|
? RecipesReferences.buildRecipeContextRecords(recipeFile)
|
|
246
250
|
: undefined;
|
|
247
|
-
if (recipeFile &&
|
|
248
|
-
|
|
251
|
+
if (recipeFile &&
|
|
252
|
+
isMutableUsageRecipeFile(recipeFile) &&
|
|
253
|
+
!RecipesUsage.recordRecipeLaunch(recipeFile, new Date(), startParams.launch_source === "tool" ? "tool" : "spawn")) {
|
|
254
|
+
throw new Error(`Recipe launch rejected because its source changed during activation: ${recipeFile}. Reload recipe tools and retry.`);
|
|
249
255
|
}
|
|
250
256
|
const outFd = openSync(stdout, "a");
|
|
251
257
|
const errFd = openSync(stderr, "a");
|
|
@@ -267,6 +273,7 @@ export function startRun(params, cwd) {
|
|
|
267
273
|
...(recipe ? { recipe } : {}),
|
|
268
274
|
...(recipeFile ? { recipe_file: recipeFile } : {}),
|
|
269
275
|
run,
|
|
276
|
+
run_instance_id: randomUUID(),
|
|
270
277
|
state_dir: stateDir,
|
|
271
278
|
status: "running",
|
|
272
279
|
...(startParams.tool ? { tool: startParams.tool } : {}),
|
|
@@ -276,6 +283,9 @@ export function startRun(params, cwd) {
|
|
|
276
283
|
...(artifacts ? { artifacts } : {}),
|
|
277
284
|
...(startParams.control ? { control: startParams.control } : {}),
|
|
278
285
|
...(startParams.mailbox ? { mailbox: startParams.mailbox } : {}),
|
|
286
|
+
...(startParams.notification_policy === "silent"
|
|
287
|
+
? { notification_policy: "silent" }
|
|
288
|
+
: {}),
|
|
279
289
|
...(recipeContextRecords && recipeContextRecords.length > 0
|
|
280
290
|
? { recipe_context_records: recipeContextRecords }
|
|
281
291
|
: {}),
|
|
@@ -303,7 +313,7 @@ export function startRun(params, cwd) {
|
|
|
303
313
|
phase: "starting",
|
|
304
314
|
updatedAt: new Date().toISOString(),
|
|
305
315
|
});
|
|
306
|
-
writeFileSync(join(stateDir, "events.jsonl"), `${JSON.stringify({ event: "run.start", run, pid: meta.pid, ts: new Date().toISOString() })}\n`, { flag: "a" });
|
|
316
|
+
writeFileSync(join(stateDir, "events.jsonl"), `${JSON.stringify({ event: "run.start", run, run_instance_id: meta.run_instance_id, pid: meta.pid, ts: new Date().toISOString() })}\n`, { flag: "a" });
|
|
307
317
|
child.unref();
|
|
308
318
|
return meta;
|
|
309
319
|
}
|
|
@@ -312,17 +322,20 @@ export function startRun(params, cwd) {
|
|
|
312
322
|
}
|
|
313
323
|
}
|
|
314
324
|
export { parseRunOutboxEventLine } from "./runs-outbox.js";
|
|
315
|
-
|
|
316
|
-
|
|
325
|
+
function resolveRunStateDir(runOrDir) {
|
|
326
|
+
return resolve(runOrDir.includes("/")
|
|
317
327
|
? runOrDir
|
|
318
328
|
: join(DEFAULT_STATE_ROOT, safeRunId(runOrDir)));
|
|
329
|
+
}
|
|
330
|
+
export function getRunStatus(runOrDir) {
|
|
331
|
+
const stateDir = resolveRunStateDir(runOrDir);
|
|
319
332
|
const meta = readJson(join(stateDir, "run.json"));
|
|
320
333
|
if (!meta)
|
|
321
334
|
throw new Error(`Run not found: ${runOrDir}`);
|
|
322
335
|
return buildRunStatus(stateDir, runOrDir, meta, readJson, RUNNER_PATH, RUNNER_IDENTITY_GRACE_MS);
|
|
323
336
|
}
|
|
324
|
-
export function listRunStateDirs(stateRoot = DEFAULT_STATE_ROOT
|
|
325
|
-
return RunsIndex.listRunStateDirs(stateRoot
|
|
337
|
+
export function listRunStateDirs(stateRoot = DEFAULT_STATE_ROOT) {
|
|
338
|
+
return RunsIndex.listRunStateDirs(stateRoot);
|
|
326
339
|
}
|
|
327
340
|
export function rebuildRunStateIndex(stateRoot = DEFAULT_STATE_ROOT) {
|
|
328
341
|
return RunsIndex.rebuildRunStateIndex(stateRoot, getRunStatus);
|
|
@@ -333,6 +346,67 @@ export function readRunStateIndex(stateRoot = DEFAULT_STATE_ROOT) {
|
|
|
333
346
|
export function listRuns(stateRoot = DEFAULT_STATE_ROOT, statusFilter) {
|
|
334
347
|
return RunsIndex.listRuns(stateRoot, getRunStatus, readJson, statusFilter);
|
|
335
348
|
}
|
|
349
|
+
export function teardownRunsOwnedByParent(ownerId, stateRoot = DEFAULT_STATE_ROOT, options = {}) {
|
|
350
|
+
const result = RunsParentTeardown.teardownParentRuns(ownerId, {
|
|
351
|
+
getRunStatus,
|
|
352
|
+
killRun,
|
|
353
|
+
listRunStatuses: () => {
|
|
354
|
+
const discovered = RunsIndex.discoverRunStateDirs(stateRoot, Number.POSITIVE_INFINITY);
|
|
355
|
+
const failures = discovered.issues.map((issue) => ({
|
|
356
|
+
path: issue.path,
|
|
357
|
+
reason: issue.reason.replaceAll("_", " "),
|
|
358
|
+
}));
|
|
359
|
+
const statuses = discovered.stateDirs.flatMap((stateDir) => {
|
|
360
|
+
try {
|
|
361
|
+
return [getRunStatus(stateDir)];
|
|
362
|
+
}
|
|
363
|
+
catch (error) {
|
|
364
|
+
failures.push({
|
|
365
|
+
path: stateDir,
|
|
366
|
+
reason: error instanceof Error ? error.message : String(error),
|
|
367
|
+
});
|
|
368
|
+
return [];
|
|
369
|
+
}
|
|
370
|
+
});
|
|
371
|
+
return { failures, statuses };
|
|
372
|
+
},
|
|
373
|
+
recordAttempt: (attempt) => {
|
|
374
|
+
const current = getRunStatus(attempt.stateDir);
|
|
375
|
+
if (!attempt.runInstanceId ||
|
|
376
|
+
current.run_instance_id !== attempt.runInstanceId) {
|
|
377
|
+
throw new Error("run generation changed before teardown evidence");
|
|
378
|
+
}
|
|
379
|
+
writeFileSync(join(attempt.stateDir, "events.jsonl"), `${JSON.stringify({
|
|
380
|
+
event: "run.parent_teardown",
|
|
381
|
+
ownerId: attempt.ownerId,
|
|
382
|
+
outcome: attempt.outcome,
|
|
383
|
+
reason: attempt.reason,
|
|
384
|
+
run: attempt.run,
|
|
385
|
+
run_instance_id: attempt.runInstanceId,
|
|
386
|
+
type: "control.kill",
|
|
387
|
+
trigger: options.trigger ?? "parent_shutdown",
|
|
388
|
+
ts: new Date().toISOString(),
|
|
389
|
+
})}\n`, { flag: "a" });
|
|
390
|
+
},
|
|
391
|
+
});
|
|
392
|
+
const summaryPath = join(stateRoot, "teardown", `${Date.now()}-${randomUUID()}.json`);
|
|
393
|
+
try {
|
|
394
|
+
writeJsonAtomic(summaryPath, RunsParentTeardown.buildBoundedParentTeardownSummary(result, ownerId ?? "unknown", options.trigger ?? "parent_shutdown", new Date().toISOString()));
|
|
395
|
+
return { ...result, summaryPath };
|
|
396
|
+
}
|
|
397
|
+
catch (error) {
|
|
398
|
+
const failure = {
|
|
399
|
+
path: summaryPath,
|
|
400
|
+
reason: `summary: ${error instanceof Error ? error.message : String(error)}`,
|
|
401
|
+
};
|
|
402
|
+
return {
|
|
403
|
+
...result,
|
|
404
|
+
discoveryFailed: result.discoveryFailed + 1,
|
|
405
|
+
discoveryFailures: [...result.discoveryFailures, failure],
|
|
406
|
+
failed: result.failed + 1,
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
}
|
|
336
410
|
export function tailRun(runOrDir, lines = 40) {
|
|
337
411
|
const status = getRunStatus(runOrDir);
|
|
338
412
|
const stateDir = String(status.state_dir);
|
|
@@ -470,17 +544,38 @@ function finalizeInterruptedReviewEvidence(stateDir, phase, signal) {
|
|
|
470
544
|
updated_at: completedAt,
|
|
471
545
|
});
|
|
472
546
|
}
|
|
473
|
-
function stopRun(runOrDir, signal, event) {
|
|
474
|
-
const
|
|
475
|
-
const
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
547
|
+
function stopRun(runOrDir, signal, event, expected = {}) {
|
|
548
|
+
const stateDir = resolveRunStateDir(runOrDir);
|
|
549
|
+
const releaseControlLock = RunsStart.acquireStateStartLock(stateDir);
|
|
550
|
+
try {
|
|
551
|
+
expected.onLocked?.();
|
|
552
|
+
const status = getRunStatus(stateDir);
|
|
553
|
+
if (expected.ownerId !== undefined &&
|
|
554
|
+
status.ownerId !== expected.ownerId) {
|
|
555
|
+
return { stopped: false, reason: "ownership changed", status };
|
|
556
|
+
}
|
|
557
|
+
if (expected.runInstanceId !== undefined &&
|
|
558
|
+
status.run_instance_id !== expected.runInstanceId) {
|
|
559
|
+
return { stopped: false, reason: "run generation changed", status };
|
|
560
|
+
}
|
|
561
|
+
const pid = Number(status.pid || 0);
|
|
562
|
+
if (status.status !== "running" && status.status !== "exited") {
|
|
563
|
+
return { stopped: false, reason: "not running", status };
|
|
564
|
+
}
|
|
565
|
+
const identity = verifyRunProcessIdentity(pid, status.process_identity);
|
|
566
|
+
if (status.status === "exited") {
|
|
567
|
+
if (identity.status === "owner_mismatch" ||
|
|
568
|
+
identity.status === "unsupported_proof") {
|
|
569
|
+
return {
|
|
570
|
+
stopped: false,
|
|
571
|
+
reason: identity.status.replaceAll("_", " "),
|
|
572
|
+
process_identity_status: identity.status,
|
|
573
|
+
status,
|
|
574
|
+
};
|
|
575
|
+
}
|
|
576
|
+
return { stopped: false, reason: "not running", status };
|
|
577
|
+
}
|
|
578
|
+
if (!identity.valid) {
|
|
484
579
|
return {
|
|
485
580
|
stopped: false,
|
|
486
581
|
reason: identity.status.replaceAll("_", " "),
|
|
@@ -488,28 +583,22 @@ function stopRun(runOrDir, signal, event) {
|
|
|
488
583
|
status,
|
|
489
584
|
};
|
|
490
585
|
}
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
markTerminalHandled(stateDir, { event, signal });
|
|
504
|
-
if (event === "run.kill") {
|
|
505
|
-
finalizeInterruptedReviewEvidence(stateDir, "killed", signal);
|
|
506
|
-
markTerminalProgress(stateDir, "killed");
|
|
586
|
+
const signalResult = signalOwnedRunProcess(pid, signal, status.process_identity);
|
|
587
|
+
writeFileSync(join(stateDir, "events.jsonl"), `${JSON.stringify({ event, pid, signal, ...signalResult, ts: new Date().toISOString() })}\n`, { flag: "a" });
|
|
588
|
+
markTerminalHandled(stateDir, { event, signal });
|
|
589
|
+
if (event === "run.kill") {
|
|
590
|
+
finalizeInterruptedReviewEvidence(stateDir, "killed", signal);
|
|
591
|
+
markTerminalProgress(stateDir, "killed");
|
|
592
|
+
}
|
|
593
|
+
if (event === "run.cancel") {
|
|
594
|
+
finalizeInterruptedReviewEvidence(stateDir, "cancelled", signal);
|
|
595
|
+
markTerminalProgress(stateDir, "cancelled");
|
|
596
|
+
}
|
|
597
|
+
return { stopped: true, pid, signal, ...signalResult, state_dir: stateDir };
|
|
507
598
|
}
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
markTerminalProgress(stateDir, "cancelled");
|
|
599
|
+
finally {
|
|
600
|
+
releaseControlLock();
|
|
511
601
|
}
|
|
512
|
-
return { stopped: true, pid, signal, ...signalResult, state_dir: stateDir };
|
|
513
602
|
}
|
|
514
603
|
export function markRunTerminalNotificationHandled(stateDir, status) {
|
|
515
604
|
markTerminalHandled(stateDir, {
|
|
@@ -517,8 +606,8 @@ export function markRunTerminalNotificationHandled(stateDir, status) {
|
|
|
517
606
|
status,
|
|
518
607
|
});
|
|
519
608
|
}
|
|
520
|
-
export function cancelRun(runOrDir) {
|
|
521
|
-
const result = stopRun(runOrDir, "SIGTERM", "run.cancel");
|
|
609
|
+
export function cancelRun(runOrDir, expected = {}) {
|
|
610
|
+
const result = stopRun(runOrDir, "SIGTERM", "run.cancel", expected);
|
|
522
611
|
return Object.hasOwn(result, "stopped")
|
|
523
612
|
? { cancelled: result.stopped, ...result }
|
|
524
613
|
: result;
|
|
@@ -536,8 +625,8 @@ export function archiveRun(runOrDir) {
|
|
|
536
625
|
export function pruneRun(runOrDir, options = {}) {
|
|
537
626
|
return pruneTerminalRun(assertTerminalRun(runOrDir), options);
|
|
538
627
|
}
|
|
539
|
-
export function killRun(runOrDir) {
|
|
540
|
-
const result = stopRun(runOrDir, "SIGKILL", "run.kill");
|
|
628
|
+
export function killRun(runOrDir, expected = {}) {
|
|
629
|
+
const result = stopRun(runOrDir, "SIGKILL", "run.kill", expected);
|
|
541
630
|
return Object.hasOwn(result, "stopped")
|
|
542
631
|
? { killed: result.stopped, ...result }
|
|
543
632
|
: result;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Automatic recipe-review runtime composition.
|
|
3
|
+
* Zones: session-bound review schedulers, silent reviewer launch adapters, review controls
|
|
4
|
+
* Owns automatic-review lifecycle wiring without owning review decisions or filesystem transactions.
|
|
5
|
+
*/
|
|
6
|
+
import type * as Pi from "./pi.ts";
|
|
7
|
+
export interface AutomaticReviewRuntime {
|
|
8
|
+
close(): void;
|
|
9
|
+
handleMessage(type: string, body: unknown): Record<string, unknown>;
|
|
10
|
+
schedule(): void;
|
|
11
|
+
start(ctx: Pi.ExtensionContext): void;
|
|
12
|
+
}
|
|
13
|
+
export interface AutomaticReviewRuntimeDeps {
|
|
14
|
+
getActiveContext(): Pi.ExtensionContext | undefined;
|
|
15
|
+
getRunOwnerId(ctx: Pi.ExtensionContext): string;
|
|
16
|
+
getThinkingLevel(): unknown;
|
|
17
|
+
}
|
|
18
|
+
export declare function createAutomaticReviewRuntime(deps: AutomaticReviewRuntimeDeps): AutomaticReviewRuntime;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Automatic recipe-review runtime composition.
|
|
3
|
+
* Zones: session-bound review schedulers, silent reviewer launch adapters, review controls
|
|
4
|
+
* Owns automatic-review lifecycle wiring without owning review decisions or filesystem transactions.
|
|
5
|
+
*/
|
|
6
|
+
import { join } from "node:path";
|
|
7
|
+
import * as AsyncRuns from "./async-runs.js";
|
|
8
|
+
import * as DraftSleep from "./draft-sleep.js";
|
|
9
|
+
import * as ModelContext from "./model-context.js";
|
|
10
|
+
import * as Paths from "./paths.js";
|
|
11
|
+
import * as ReviewControl from "./review-control.js";
|
|
12
|
+
import * as ToolReviewScheduler from "./tool-review-scheduler.js";
|
|
13
|
+
export function createAutomaticReviewRuntime(deps) {
|
|
14
|
+
let draftScheduler;
|
|
15
|
+
let toolScheduler;
|
|
16
|
+
const close = () => {
|
|
17
|
+
draftScheduler?.close();
|
|
18
|
+
draftScheduler = undefined;
|
|
19
|
+
toolScheduler?.close();
|
|
20
|
+
toolScheduler = undefined;
|
|
21
|
+
};
|
|
22
|
+
const hasActiveActors = () => AsyncRuns.listRuns(Paths.EXTENSION_RUNTIME_PATHS.runStateRoot, "running").length > 0;
|
|
23
|
+
const policyValues = (ctx) => ModelContext.withCurrentModelValues({}, {
|
|
24
|
+
...ctx,
|
|
25
|
+
getThinkingLevel: deps.getThinkingLevel,
|
|
26
|
+
});
|
|
27
|
+
return {
|
|
28
|
+
close,
|
|
29
|
+
handleMessage(type, body) {
|
|
30
|
+
if (type !== "review.retry" && type !== "review.reset") {
|
|
31
|
+
throw new Error("tool:pi-actors accepts review.retry or review.reset messages.");
|
|
32
|
+
}
|
|
33
|
+
if (type === "review.retry" && !Paths.isAutomaticRecipeReviewEnabled()) {
|
|
34
|
+
throw new Error("Automatic recipe review is disabled by PI_ACTORS_AUTOMATIC_REVIEW.");
|
|
35
|
+
}
|
|
36
|
+
return ReviewControl.controlAutomaticReview(type, ReviewControl.parseAutomaticReviewScope(body), {
|
|
37
|
+
scheduleDraft: () => draftScheduler?.schedule(),
|
|
38
|
+
scheduleTool: () => toolScheduler?.schedule(),
|
|
39
|
+
});
|
|
40
|
+
},
|
|
41
|
+
schedule() {
|
|
42
|
+
draftScheduler?.schedule();
|
|
43
|
+
toolScheduler?.schedule();
|
|
44
|
+
},
|
|
45
|
+
start(ctx) {
|
|
46
|
+
close();
|
|
47
|
+
if (!Paths.isAutomaticRecipeReviewEnabled())
|
|
48
|
+
return;
|
|
49
|
+
ToolReviewScheduler.applyApprovedToolReviewAtSessionBoundary({
|
|
50
|
+
recipeRoot: Paths.getRecipeRoot(),
|
|
51
|
+
});
|
|
52
|
+
draftScheduler = DraftSleep.createDraftSleepScheduler({
|
|
53
|
+
hasActiveActors,
|
|
54
|
+
launch: (batch) => {
|
|
55
|
+
if (deps.getActiveContext() !== ctx) {
|
|
56
|
+
throw new Error("Draft review session changed before launch.");
|
|
57
|
+
}
|
|
58
|
+
return AsyncRuns.startRun({
|
|
59
|
+
file: join(Paths.getPackagedRecipeRoot(), "draft-review.json"),
|
|
60
|
+
launch_source: "tool",
|
|
61
|
+
notification_policy: "silent",
|
|
62
|
+
ownerId: deps.getRunOwnerId(ctx),
|
|
63
|
+
policy_values: policyValues(ctx),
|
|
64
|
+
run_id: DraftSleep.draftSleepRunId(batch.batchId),
|
|
65
|
+
values: { input_path: batch.reviewerInputPath },
|
|
66
|
+
}, ctx.cwd);
|
|
67
|
+
},
|
|
68
|
+
process: (state) => DraftSleep.processDraftSleepReview(state, {
|
|
69
|
+
getRunStatus: AsyncRuns.getRunStatus,
|
|
70
|
+
recipeRoot: Paths.getRecipeRoot(),
|
|
71
|
+
}),
|
|
72
|
+
});
|
|
73
|
+
toolScheduler = ToolReviewScheduler.createToolReviewScheduler({
|
|
74
|
+
hasActiveActors,
|
|
75
|
+
launch: (batch) => {
|
|
76
|
+
if (deps.getActiveContext() !== ctx) {
|
|
77
|
+
throw new Error("Tool review session changed before launch.");
|
|
78
|
+
}
|
|
79
|
+
return AsyncRuns.startRun({
|
|
80
|
+
file: join(Paths.getPackagedRecipeRoot(), "tool-review.json"),
|
|
81
|
+
launch_source: "tool",
|
|
82
|
+
notification_policy: "silent",
|
|
83
|
+
ownerId: deps.getRunOwnerId(ctx),
|
|
84
|
+
policy_values: policyValues(ctx),
|
|
85
|
+
run_id: ToolReviewScheduler.toolReviewRunId(batch.reviewId),
|
|
86
|
+
values: { input_path: batch.reviewerInputPath },
|
|
87
|
+
}, ctx.cwd);
|
|
88
|
+
},
|
|
89
|
+
process: (state) => ToolReviewScheduler.processToolReviewResult(state, {
|
|
90
|
+
getRunStatus: AsyncRuns.getRunStatus,
|
|
91
|
+
recipeRoot: Paths.getRecipeRoot(),
|
|
92
|
+
}),
|
|
93
|
+
});
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deterministic draft consolidation transaction and crash recovery.
|
|
3
|
+
* Owns: cycle claim, source/target compare-and-swap, complete recipe persistence, source quarantine, journal transitions, rollback, and runtime evidence.
|
|
4
|
+
*/
|
|
5
|
+
import type { DraftConsolidationPlan } from "./draft-consolidation.ts";
|
|
6
|
+
import { type DraftConsolidationInventoryItem } from "./draft-consolidation.ts";
|
|
7
|
+
export type DraftConsolidationTransactionPhase = "prepared" | "targets_validated" | "sources_quarantined" | "committed_evidence_pending" | "committed" | "rolled_back" | "rollback_required";
|
|
8
|
+
interface TargetOperation {
|
|
9
|
+
backupPath?: string;
|
|
10
|
+
expectedSha256: string | null;
|
|
11
|
+
intendedSha256: string;
|
|
12
|
+
path: string;
|
|
13
|
+
recipe: Record<string, unknown>;
|
|
14
|
+
target: string;
|
|
15
|
+
}
|
|
16
|
+
interface SourceOperation {
|
|
17
|
+
path: string;
|
|
18
|
+
quarantinePath: string;
|
|
19
|
+
sha256: string;
|
|
20
|
+
}
|
|
21
|
+
interface ConsolidationRootIdentity {
|
|
22
|
+
dev: string;
|
|
23
|
+
ino: string;
|
|
24
|
+
path: string;
|
|
25
|
+
realpath: string;
|
|
26
|
+
}
|
|
27
|
+
interface ConsolidationRootIdentities {
|
|
28
|
+
cycleDir: ConsolidationRootIdentity;
|
|
29
|
+
draftRoot: ConsolidationRootIdentity;
|
|
30
|
+
recipeRoot: ConsolidationRootIdentity;
|
|
31
|
+
}
|
|
32
|
+
export interface DraftConsolidationJournal {
|
|
33
|
+
cycleId: string;
|
|
34
|
+
error?: string;
|
|
35
|
+
operationsSha256: string;
|
|
36
|
+
phase: DraftConsolidationTransactionPhase;
|
|
37
|
+
plan: DraftConsolidationPlan;
|
|
38
|
+
planSha256: string;
|
|
39
|
+
roots: ConsolidationRootIdentities;
|
|
40
|
+
sourceScope: "batch" | "complete";
|
|
41
|
+
sources: SourceOperation[];
|
|
42
|
+
targets: TargetOperation[];
|
|
43
|
+
updatedAt: string;
|
|
44
|
+
}
|
|
45
|
+
export type DraftConsolidationCheckpoint = "prepared" | "target_written" | "targets_validated" | "source_quarantined" | "sources_quarantined" | "committed_evidence_pending" | "evidence_written";
|
|
46
|
+
export interface DraftConsolidationTransactionOptions {
|
|
47
|
+
checkpoint?(point: DraftConsolidationCheckpoint): void;
|
|
48
|
+
cycleDir: string;
|
|
49
|
+
draftRoot: string;
|
|
50
|
+
inventory: DraftConsolidationInventoryItem[];
|
|
51
|
+
onLockContention?(): void;
|
|
52
|
+
sourceScope?: "batch" | "complete";
|
|
53
|
+
recipeRoot: string;
|
|
54
|
+
}
|
|
55
|
+
export interface DraftConsolidationTransactionResult {
|
|
56
|
+
cycleId: string;
|
|
57
|
+
evidencePath: string;
|
|
58
|
+
phase: "committed" | "rolled_back";
|
|
59
|
+
plan: DraftConsolidationPlan;
|
|
60
|
+
sources: number;
|
|
61
|
+
targets: number;
|
|
62
|
+
}
|
|
63
|
+
export declare function applyDraftConsolidationPlan(plan: DraftConsolidationPlan, options: DraftConsolidationTransactionOptions): DraftConsolidationTransactionResult;
|
|
64
|
+
export declare function recoverDraftConsolidationCycle(options: Pick<DraftConsolidationTransactionOptions, "cycleDir" | "draftRoot" | "recipeRoot">): DraftConsolidationTransactionResult;
|
|
65
|
+
export {};
|