@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/lib/async-runs.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
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
|
|
|
6
6
|
import { spawn } from "node:child_process";
|
|
7
|
+
import { randomUUID } from "node:crypto";
|
|
7
8
|
import {
|
|
8
9
|
closeSync,
|
|
9
10
|
existsSync,
|
|
@@ -60,6 +61,7 @@ import {
|
|
|
60
61
|
} from "./runs-process.ts";
|
|
61
62
|
import * as RunsStart from "./runs-start.ts";
|
|
62
63
|
import * as RunsIndex from "./runs-index.ts";
|
|
64
|
+
import * as RunsParentTeardown from "./runs-parent-teardown.ts";
|
|
63
65
|
import {
|
|
64
66
|
claimRunInboxMessageInStateDir,
|
|
65
67
|
parseRunInboxLine,
|
|
@@ -75,7 +77,12 @@ import {
|
|
|
75
77
|
deliverRunMessage,
|
|
76
78
|
type SendRunMessageOptions,
|
|
77
79
|
} from "./runs-messages.ts";
|
|
78
|
-
import {
|
|
80
|
+
import {
|
|
81
|
+
buildRunStatus,
|
|
82
|
+
tailFile,
|
|
83
|
+
tailLines,
|
|
84
|
+
type AsyncRunStatus,
|
|
85
|
+
} from "./runs-status.ts";
|
|
79
86
|
import { readJsonFileResilient } from "./state-readers.ts";
|
|
80
87
|
|
|
81
88
|
const RUNNER_IDENTITY_GRACE_MS = 5000;
|
|
@@ -92,6 +99,9 @@ export interface AsyncRunStartParams {
|
|
|
92
99
|
control?: AsyncRunControlEndpoint;
|
|
93
100
|
file?: string;
|
|
94
101
|
launch_source?: AsyncRunLaunchSource;
|
|
102
|
+
lifecycleHooks?: {
|
|
103
|
+
onLockContention?(): void;
|
|
104
|
+
};
|
|
95
105
|
name?: string;
|
|
96
106
|
ownerId?: string;
|
|
97
107
|
run_id?: string;
|
|
@@ -111,6 +121,7 @@ export interface AsyncRunStartParams {
|
|
|
111
121
|
output?: string;
|
|
112
122
|
artifacts?: Record<string, RunArtifactDeclaration>;
|
|
113
123
|
mailbox?: RecipesReferences.TemplateRecipeMailbox;
|
|
124
|
+
notification_policy?: "normal" | "silent";
|
|
114
125
|
retire_when?: "children_terminal";
|
|
115
126
|
retry?: number | string;
|
|
116
127
|
failure?: CommandTemplateFailureScope;
|
|
@@ -122,13 +133,7 @@ export interface AsyncRunStartParams {
|
|
|
122
133
|
cwd?: string;
|
|
123
134
|
}
|
|
124
135
|
|
|
125
|
-
export type AsyncRunStatus
|
|
126
|
-
| "running"
|
|
127
|
-
| "done"
|
|
128
|
-
| "failed"
|
|
129
|
-
| "exited"
|
|
130
|
-
| "cancelled"
|
|
131
|
-
| "killed";
|
|
136
|
+
export type { AsyncRunStatus } from "./runs-status.ts";
|
|
132
137
|
|
|
133
138
|
export interface AsyncRunMeta {
|
|
134
139
|
argv: string[];
|
|
@@ -140,6 +145,7 @@ export interface AsyncRunMeta {
|
|
|
140
145
|
recipe?: string;
|
|
141
146
|
recipe_file?: string;
|
|
142
147
|
run: string;
|
|
148
|
+
run_instance_id: string;
|
|
143
149
|
state_dir: string;
|
|
144
150
|
status: AsyncRunStatus;
|
|
145
151
|
tool?: string;
|
|
@@ -149,6 +155,7 @@ export interface AsyncRunMeta {
|
|
|
149
155
|
control?: AsyncRunControlEndpoint;
|
|
150
156
|
mailbox?: RecipesReferences.TemplateRecipeMailbox;
|
|
151
157
|
model_policy?: CurrentPolicyProvenance;
|
|
158
|
+
notification_policy?: "normal" | "silent";
|
|
152
159
|
process_identity?: RunProcessIdentity;
|
|
153
160
|
recipe_context_records?: RecipesReferences.TemplateRecipeContextRecord[];
|
|
154
161
|
retire_when?: "children_terminal";
|
|
@@ -411,8 +418,11 @@ function assertCurrentPlaceholderReferencesResolved(
|
|
|
411
418
|
}
|
|
412
419
|
}
|
|
413
420
|
|
|
414
|
-
function acquireStateStartLock(
|
|
415
|
-
|
|
421
|
+
function acquireStateStartLock(
|
|
422
|
+
stateDir: string,
|
|
423
|
+
options: import("./file-state.ts").FileMutationLockOptions = {},
|
|
424
|
+
): () => void {
|
|
425
|
+
return RunsStart.acquireStateStartLock(stateDir, options);
|
|
416
426
|
}
|
|
417
427
|
|
|
418
428
|
function prepareStateDirForStart(stateDir: string): void {
|
|
@@ -448,7 +458,9 @@ export function startRun(
|
|
|
448
458
|
);
|
|
449
459
|
assertNoActiveRunState(stateDir);
|
|
450
460
|
mkdirSync(stateDir, { recursive: true });
|
|
451
|
-
const releaseStartLock = acquireStateStartLock(stateDir
|
|
461
|
+
const releaseStartLock = acquireStateStartLock(stateDir, {
|
|
462
|
+
onContention: startParams.lifecycleHooks?.onLockContention,
|
|
463
|
+
});
|
|
452
464
|
try {
|
|
453
465
|
claimRunStateDirectory(stateDir, run);
|
|
454
466
|
assertNoActiveRunState(stateDir);
|
|
@@ -466,11 +478,17 @@ export function startRun(
|
|
|
466
478
|
recipeFile && includeActorRecipeContext
|
|
467
479
|
? RecipesReferences.buildRecipeContextRecords(recipeFile)
|
|
468
480
|
: undefined;
|
|
469
|
-
if (
|
|
470
|
-
|
|
481
|
+
if (
|
|
482
|
+
recipeFile &&
|
|
483
|
+
isMutableUsageRecipeFile(recipeFile) &&
|
|
484
|
+
!RecipesUsage.recordRecipeLaunch(
|
|
471
485
|
recipeFile,
|
|
472
486
|
new Date(),
|
|
473
487
|
startParams.launch_source === "tool" ? "tool" : "spawn",
|
|
488
|
+
)
|
|
489
|
+
) {
|
|
490
|
+
throw new Error(
|
|
491
|
+
`Recipe launch rejected because its source changed during activation: ${recipeFile}. Reload recipe tools and retry.`,
|
|
474
492
|
);
|
|
475
493
|
}
|
|
476
494
|
const outFd = openSync(stdout, "a");
|
|
@@ -493,6 +511,7 @@ export function startRun(
|
|
|
493
511
|
...(recipe ? { recipe } : {}),
|
|
494
512
|
...(recipeFile ? { recipe_file: recipeFile } : {}),
|
|
495
513
|
run,
|
|
514
|
+
run_instance_id: randomUUID(),
|
|
496
515
|
state_dir: stateDir,
|
|
497
516
|
status: "running",
|
|
498
517
|
...(startParams.tool ? { tool: startParams.tool } : {}),
|
|
@@ -502,6 +521,9 @@ export function startRun(
|
|
|
502
521
|
...(artifacts ? { artifacts } : {}),
|
|
503
522
|
...(startParams.control ? { control: startParams.control } : {}),
|
|
504
523
|
...(startParams.mailbox ? { mailbox: startParams.mailbox } : {}),
|
|
524
|
+
...(startParams.notification_policy === "silent"
|
|
525
|
+
? { notification_policy: "silent" as const }
|
|
526
|
+
: {}),
|
|
505
527
|
...(recipeContextRecords && recipeContextRecords.length > 0
|
|
506
528
|
? { recipe_context_records: recipeContextRecords }
|
|
507
529
|
: {}),
|
|
@@ -535,7 +557,7 @@ export function startRun(
|
|
|
535
557
|
});
|
|
536
558
|
writeFileSync(
|
|
537
559
|
join(stateDir, "events.jsonl"),
|
|
538
|
-
`${JSON.stringify({ event: "run.start", run, pid: meta.pid, ts: new Date().toISOString() })}\n`,
|
|
560
|
+
`${JSON.stringify({ event: "run.start", run, run_instance_id: meta.run_instance_id, pid: meta.pid, ts: new Date().toISOString() })}\n`,
|
|
539
561
|
{ flag: "a" },
|
|
540
562
|
);
|
|
541
563
|
child.unref();
|
|
@@ -552,12 +574,16 @@ export type {
|
|
|
552
574
|
RunOutboxLevel,
|
|
553
575
|
} from "./runs-outbox.ts";
|
|
554
576
|
|
|
555
|
-
|
|
556
|
-
|
|
577
|
+
function resolveRunStateDir(runOrDir: string): string {
|
|
578
|
+
return resolve(
|
|
557
579
|
runOrDir.includes("/")
|
|
558
580
|
? runOrDir
|
|
559
581
|
: join(DEFAULT_STATE_ROOT, safeRunId(runOrDir)),
|
|
560
582
|
);
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
export function getRunStatus(runOrDir: string): Record<string, unknown> {
|
|
586
|
+
const stateDir = resolveRunStateDir(runOrDir);
|
|
561
587
|
const meta = readJson(join(stateDir, "run.json"));
|
|
562
588
|
if (!meta) throw new Error(`Run not found: ${runOrDir}`);
|
|
563
589
|
return buildRunStatus(
|
|
@@ -574,10 +600,8 @@ export type { RunStateIndexEntry } from "./runs-index.ts";
|
|
|
574
600
|
|
|
575
601
|
export function listRunStateDirs(
|
|
576
602
|
stateRoot = DEFAULT_STATE_ROOT,
|
|
577
|
-
depth = 0,
|
|
578
|
-
seen = new Set<string>(),
|
|
579
603
|
): string[] {
|
|
580
|
-
return RunsIndex.listRunStateDirs(stateRoot
|
|
604
|
+
return RunsIndex.listRunStateDirs(stateRoot);
|
|
581
605
|
}
|
|
582
606
|
|
|
583
607
|
export function rebuildRunStateIndex(
|
|
@@ -599,6 +623,103 @@ export function listRuns(
|
|
|
599
623
|
return RunsIndex.listRuns(stateRoot, getRunStatus, readJson, statusFilter);
|
|
600
624
|
}
|
|
601
625
|
|
|
626
|
+
export type {
|
|
627
|
+
ParentRunTeardownAttempt,
|
|
628
|
+
ParentRunTeardownDiscoveryFailure,
|
|
629
|
+
ParentRunTeardownResult,
|
|
630
|
+
} from "./runs-parent-teardown.ts";
|
|
631
|
+
|
|
632
|
+
export interface ParentRunsTeardownSummaryResult
|
|
633
|
+
extends RunsParentTeardown.ParentRunTeardownResult {
|
|
634
|
+
summaryPath?: string;
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
export function teardownRunsOwnedByParent(
|
|
638
|
+
ownerId: string | undefined,
|
|
639
|
+
stateRoot = DEFAULT_STATE_ROOT,
|
|
640
|
+
options: { trigger?: string } = {},
|
|
641
|
+
): ParentRunsTeardownSummaryResult {
|
|
642
|
+
const result = RunsParentTeardown.teardownParentRuns(ownerId, {
|
|
643
|
+
getRunStatus,
|
|
644
|
+
killRun,
|
|
645
|
+
listRunStatuses: () => {
|
|
646
|
+
const discovered = RunsIndex.discoverRunStateDirs(
|
|
647
|
+
stateRoot,
|
|
648
|
+
Number.POSITIVE_INFINITY,
|
|
649
|
+
);
|
|
650
|
+
const failures: RunsParentTeardown.ParentRunTeardownDiscoveryFailure[] =
|
|
651
|
+
discovered.issues.map((issue) => ({
|
|
652
|
+
path: issue.path,
|
|
653
|
+
reason: issue.reason.replaceAll("_", " "),
|
|
654
|
+
}));
|
|
655
|
+
const statuses = discovered.stateDirs.flatMap((stateDir) => {
|
|
656
|
+
try {
|
|
657
|
+
return [getRunStatus(stateDir)];
|
|
658
|
+
} catch (error) {
|
|
659
|
+
failures.push({
|
|
660
|
+
path: stateDir,
|
|
661
|
+
reason: error instanceof Error ? error.message : String(error),
|
|
662
|
+
});
|
|
663
|
+
return [];
|
|
664
|
+
}
|
|
665
|
+
});
|
|
666
|
+
return { failures, statuses };
|
|
667
|
+
},
|
|
668
|
+
recordAttempt: (attempt) => {
|
|
669
|
+
const current = getRunStatus(attempt.stateDir);
|
|
670
|
+
if (
|
|
671
|
+
!attempt.runInstanceId ||
|
|
672
|
+
current.run_instance_id !== attempt.runInstanceId
|
|
673
|
+
) {
|
|
674
|
+
throw new Error("run generation changed before teardown evidence");
|
|
675
|
+
}
|
|
676
|
+
writeFileSync(
|
|
677
|
+
join(attempt.stateDir, "events.jsonl"),
|
|
678
|
+
`${JSON.stringify({
|
|
679
|
+
event: "run.parent_teardown",
|
|
680
|
+
ownerId: attempt.ownerId,
|
|
681
|
+
outcome: attempt.outcome,
|
|
682
|
+
reason: attempt.reason,
|
|
683
|
+
run: attempt.run,
|
|
684
|
+
run_instance_id: attempt.runInstanceId,
|
|
685
|
+
type: "control.kill",
|
|
686
|
+
trigger: options.trigger ?? "parent_shutdown",
|
|
687
|
+
ts: new Date().toISOString(),
|
|
688
|
+
})}\n`,
|
|
689
|
+
{ flag: "a" },
|
|
690
|
+
);
|
|
691
|
+
},
|
|
692
|
+
});
|
|
693
|
+
const summaryPath = join(
|
|
694
|
+
stateRoot,
|
|
695
|
+
"teardown",
|
|
696
|
+
`${Date.now()}-${randomUUID()}.json`,
|
|
697
|
+
);
|
|
698
|
+
try {
|
|
699
|
+
writeJsonAtomic(
|
|
700
|
+
summaryPath,
|
|
701
|
+
RunsParentTeardown.buildBoundedParentTeardownSummary(
|
|
702
|
+
result,
|
|
703
|
+
ownerId ?? "unknown",
|
|
704
|
+
options.trigger ?? "parent_shutdown",
|
|
705
|
+
new Date().toISOString(),
|
|
706
|
+
),
|
|
707
|
+
);
|
|
708
|
+
return { ...result, summaryPath };
|
|
709
|
+
} catch (error) {
|
|
710
|
+
const failure = {
|
|
711
|
+
path: summaryPath,
|
|
712
|
+
reason: `summary: ${error instanceof Error ? error.message : String(error)}`,
|
|
713
|
+
};
|
|
714
|
+
return {
|
|
715
|
+
...result,
|
|
716
|
+
discoveryFailed: result.discoveryFailed + 1,
|
|
717
|
+
discoveryFailures: [...result.discoveryFailures, failure],
|
|
718
|
+
failed: result.failed + 1,
|
|
719
|
+
};
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
|
|
602
723
|
export function tailRun(runOrDir: string, lines = 40): string {
|
|
603
724
|
const status = getRunStatus(runOrDir);
|
|
604
725
|
const stateDir = String(status.state_dir);
|
|
@@ -815,26 +936,58 @@ function finalizeInterruptedReviewEvidence(
|
|
|
815
936
|
});
|
|
816
937
|
}
|
|
817
938
|
|
|
939
|
+
export interface RunControlExpectation {
|
|
940
|
+
onLocked?(): void;
|
|
941
|
+
ownerId?: string;
|
|
942
|
+
runInstanceId?: string;
|
|
943
|
+
}
|
|
944
|
+
|
|
818
945
|
function stopRun(
|
|
819
946
|
runOrDir: string,
|
|
820
947
|
signal: NodeJS.Signals,
|
|
821
948
|
event: string,
|
|
949
|
+
expected: RunControlExpectation = {},
|
|
822
950
|
): Record<string, unknown> {
|
|
823
|
-
const
|
|
824
|
-
const
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
}
|
|
829
|
-
const identity = verifyRunProcessIdentity(
|
|
830
|
-
pid,
|
|
831
|
-
status.process_identity as RunProcessIdentity | undefined,
|
|
832
|
-
);
|
|
833
|
-
if (status.status === "exited") {
|
|
951
|
+
const stateDir = resolveRunStateDir(runOrDir);
|
|
952
|
+
const releaseControlLock = RunsStart.acquireStateStartLock(stateDir);
|
|
953
|
+
try {
|
|
954
|
+
expected.onLocked?.();
|
|
955
|
+
const status = getRunStatus(stateDir);
|
|
834
956
|
if (
|
|
835
|
-
|
|
836
|
-
|
|
957
|
+
expected.ownerId !== undefined &&
|
|
958
|
+
status.ownerId !== expected.ownerId
|
|
837
959
|
) {
|
|
960
|
+
return { stopped: false, reason: "ownership changed", status };
|
|
961
|
+
}
|
|
962
|
+
if (
|
|
963
|
+
expected.runInstanceId !== undefined &&
|
|
964
|
+
status.run_instance_id !== expected.runInstanceId
|
|
965
|
+
) {
|
|
966
|
+
return { stopped: false, reason: "run generation changed", status };
|
|
967
|
+
}
|
|
968
|
+
const pid = Number(status.pid || 0);
|
|
969
|
+
if (status.status !== "running" && status.status !== "exited") {
|
|
970
|
+
return { stopped: false, reason: "not running", status };
|
|
971
|
+
}
|
|
972
|
+
const identity = verifyRunProcessIdentity(
|
|
973
|
+
pid,
|
|
974
|
+
status.process_identity as RunProcessIdentity | undefined,
|
|
975
|
+
);
|
|
976
|
+
if (status.status === "exited") {
|
|
977
|
+
if (
|
|
978
|
+
identity.status === "owner_mismatch" ||
|
|
979
|
+
identity.status === "unsupported_proof"
|
|
980
|
+
) {
|
|
981
|
+
return {
|
|
982
|
+
stopped: false,
|
|
983
|
+
reason: identity.status.replaceAll("_", " "),
|
|
984
|
+
process_identity_status: identity.status,
|
|
985
|
+
status,
|
|
986
|
+
};
|
|
987
|
+
}
|
|
988
|
+
return { stopped: false, reason: "not running", status };
|
|
989
|
+
}
|
|
990
|
+
if (!identity.valid) {
|
|
838
991
|
return {
|
|
839
992
|
stopped: false,
|
|
840
993
|
reason: identity.status.replaceAll("_", " "),
|
|
@@ -842,32 +995,29 @@ function stopRun(
|
|
|
842
995
|
status,
|
|
843
996
|
};
|
|
844
997
|
}
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
}
|
|
866
|
-
|
|
867
|
-
finalizeInterruptedReviewEvidence(stateDir, "cancelled", signal);
|
|
868
|
-
markTerminalProgress(stateDir, "cancelled");
|
|
998
|
+
const signalResult = signalOwnedRunProcess(
|
|
999
|
+
pid,
|
|
1000
|
+
signal,
|
|
1001
|
+
status.process_identity as RunProcessIdentity,
|
|
1002
|
+
);
|
|
1003
|
+
writeFileSync(
|
|
1004
|
+
join(stateDir, "events.jsonl"),
|
|
1005
|
+
`${JSON.stringify({ event, pid, signal, ...signalResult, ts: new Date().toISOString() })}\n`,
|
|
1006
|
+
{ flag: "a" },
|
|
1007
|
+
);
|
|
1008
|
+
markTerminalHandled(stateDir, { event, signal });
|
|
1009
|
+
if (event === "run.kill") {
|
|
1010
|
+
finalizeInterruptedReviewEvidence(stateDir, "killed", signal);
|
|
1011
|
+
markTerminalProgress(stateDir, "killed");
|
|
1012
|
+
}
|
|
1013
|
+
if (event === "run.cancel") {
|
|
1014
|
+
finalizeInterruptedReviewEvidence(stateDir, "cancelled", signal);
|
|
1015
|
+
markTerminalProgress(stateDir, "cancelled");
|
|
1016
|
+
}
|
|
1017
|
+
return { stopped: true, pid, signal, ...signalResult, state_dir: stateDir };
|
|
1018
|
+
} finally {
|
|
1019
|
+
releaseControlLock();
|
|
869
1020
|
}
|
|
870
|
-
return { stopped: true, pid, signal, ...signalResult, state_dir: stateDir };
|
|
871
1021
|
}
|
|
872
1022
|
|
|
873
1023
|
export function markRunTerminalNotificationHandled(
|
|
@@ -880,8 +1030,11 @@ export function markRunTerminalNotificationHandled(
|
|
|
880
1030
|
});
|
|
881
1031
|
}
|
|
882
1032
|
|
|
883
|
-
export function cancelRun(
|
|
884
|
-
|
|
1033
|
+
export function cancelRun(
|
|
1034
|
+
runOrDir: string,
|
|
1035
|
+
expected: RunControlExpectation = {},
|
|
1036
|
+
): Record<string, unknown> {
|
|
1037
|
+
const result = stopRun(runOrDir, "SIGTERM", "run.cancel", expected);
|
|
885
1038
|
return Object.hasOwn(result, "stopped")
|
|
886
1039
|
? { cancelled: result.stopped, ...result }
|
|
887
1040
|
: result;
|
|
@@ -906,8 +1059,11 @@ export function pruneRun(
|
|
|
906
1059
|
return pruneTerminalRun(assertTerminalRun(runOrDir), options);
|
|
907
1060
|
}
|
|
908
1061
|
|
|
909
|
-
export function killRun(
|
|
910
|
-
|
|
1062
|
+
export function killRun(
|
|
1063
|
+
runOrDir: string,
|
|
1064
|
+
expected: RunControlExpectation = {},
|
|
1065
|
+
): Record<string, unknown> {
|
|
1066
|
+
const result = stopRun(runOrDir, "SIGKILL", "run.kill", expected);
|
|
911
1067
|
return Object.hasOwn(result, "stopped")
|
|
912
1068
|
? { killed: result.stopped, ...result }
|
|
913
1069
|
: result;
|
|
@@ -0,0 +1,135 @@
|
|
|
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
|
+
|
|
7
|
+
import { join } from "node:path";
|
|
8
|
+
|
|
9
|
+
import * as AsyncRuns from "./async-runs.ts";
|
|
10
|
+
import * as DraftSleep from "./draft-sleep.ts";
|
|
11
|
+
import * as ModelContext from "./model-context.ts";
|
|
12
|
+
import * as Paths from "./paths.ts";
|
|
13
|
+
import type * as Pi from "./pi.ts";
|
|
14
|
+
import * as ReviewControl from "./review-control.ts";
|
|
15
|
+
import * as ToolReviewScheduler from "./tool-review-scheduler.ts";
|
|
16
|
+
|
|
17
|
+
export interface AutomaticReviewRuntime {
|
|
18
|
+
close(): void;
|
|
19
|
+
handleMessage(type: string, body: unknown): Record<string, unknown>;
|
|
20
|
+
schedule(): void;
|
|
21
|
+
start(ctx: Pi.ExtensionContext): void;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface AutomaticReviewRuntimeDeps {
|
|
25
|
+
getActiveContext(): Pi.ExtensionContext | undefined;
|
|
26
|
+
getRunOwnerId(ctx: Pi.ExtensionContext): string;
|
|
27
|
+
getThinkingLevel(): unknown;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function createAutomaticReviewRuntime(
|
|
31
|
+
deps: AutomaticReviewRuntimeDeps,
|
|
32
|
+
): AutomaticReviewRuntime {
|
|
33
|
+
let draftScheduler: DraftSleep.DraftSleepScheduler | undefined;
|
|
34
|
+
let toolScheduler: ToolReviewScheduler.ToolReviewScheduler | undefined;
|
|
35
|
+
|
|
36
|
+
const close = (): void => {
|
|
37
|
+
draftScheduler?.close();
|
|
38
|
+
draftScheduler = undefined;
|
|
39
|
+
toolScheduler?.close();
|
|
40
|
+
toolScheduler = undefined;
|
|
41
|
+
};
|
|
42
|
+
const hasActiveActors = (): boolean =>
|
|
43
|
+
AsyncRuns.listRuns(Paths.EXTENSION_RUNTIME_PATHS.runStateRoot, "running").length > 0;
|
|
44
|
+
const policyValues = (ctx: Pi.ExtensionContext): Record<string, unknown> =>
|
|
45
|
+
ModelContext.withCurrentModelValues(
|
|
46
|
+
{},
|
|
47
|
+
{
|
|
48
|
+
...(ctx as ModelContext.CurrentModelContext),
|
|
49
|
+
getThinkingLevel: deps.getThinkingLevel,
|
|
50
|
+
},
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
close,
|
|
55
|
+
handleMessage(type, body) {
|
|
56
|
+
if (type !== "review.retry" && type !== "review.reset") {
|
|
57
|
+
throw new Error("tool:pi-actors accepts review.retry or review.reset messages.");
|
|
58
|
+
}
|
|
59
|
+
if (type === "review.retry" && !Paths.isAutomaticRecipeReviewEnabled()) {
|
|
60
|
+
throw new Error(
|
|
61
|
+
"Automatic recipe review is disabled by PI_ACTORS_AUTOMATIC_REVIEW.",
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
return ReviewControl.controlAutomaticReview(
|
|
65
|
+
type,
|
|
66
|
+
ReviewControl.parseAutomaticReviewScope(body),
|
|
67
|
+
{
|
|
68
|
+
scheduleDraft: () => draftScheduler?.schedule(),
|
|
69
|
+
scheduleTool: () => toolScheduler?.schedule(),
|
|
70
|
+
},
|
|
71
|
+
);
|
|
72
|
+
},
|
|
73
|
+
schedule() {
|
|
74
|
+
draftScheduler?.schedule();
|
|
75
|
+
toolScheduler?.schedule();
|
|
76
|
+
},
|
|
77
|
+
start(ctx) {
|
|
78
|
+
close();
|
|
79
|
+
if (!Paths.isAutomaticRecipeReviewEnabled()) return;
|
|
80
|
+
ToolReviewScheduler.applyApprovedToolReviewAtSessionBoundary({
|
|
81
|
+
recipeRoot: Paths.getRecipeRoot(),
|
|
82
|
+
});
|
|
83
|
+
draftScheduler = DraftSleep.createDraftSleepScheduler({
|
|
84
|
+
hasActiveActors,
|
|
85
|
+
launch: (batch) => {
|
|
86
|
+
if (deps.getActiveContext() !== ctx) {
|
|
87
|
+
throw new Error("Draft review session changed before launch.");
|
|
88
|
+
}
|
|
89
|
+
return AsyncRuns.startRun(
|
|
90
|
+
{
|
|
91
|
+
file: join(Paths.getPackagedRecipeRoot(), "draft-review.json"),
|
|
92
|
+
launch_source: "tool",
|
|
93
|
+
notification_policy: "silent",
|
|
94
|
+
ownerId: deps.getRunOwnerId(ctx),
|
|
95
|
+
policy_values: policyValues(ctx),
|
|
96
|
+
run_id: DraftSleep.draftSleepRunId(batch.batchId),
|
|
97
|
+
values: { input_path: batch.reviewerInputPath },
|
|
98
|
+
},
|
|
99
|
+
ctx.cwd,
|
|
100
|
+
);
|
|
101
|
+
},
|
|
102
|
+
process: (state) =>
|
|
103
|
+
DraftSleep.processDraftSleepReview(state, {
|
|
104
|
+
getRunStatus: AsyncRuns.getRunStatus,
|
|
105
|
+
recipeRoot: Paths.getRecipeRoot(),
|
|
106
|
+
}),
|
|
107
|
+
});
|
|
108
|
+
toolScheduler = ToolReviewScheduler.createToolReviewScheduler({
|
|
109
|
+
hasActiveActors,
|
|
110
|
+
launch: (batch) => {
|
|
111
|
+
if (deps.getActiveContext() !== ctx) {
|
|
112
|
+
throw new Error("Tool review session changed before launch.");
|
|
113
|
+
}
|
|
114
|
+
return AsyncRuns.startRun(
|
|
115
|
+
{
|
|
116
|
+
file: join(Paths.getPackagedRecipeRoot(), "tool-review.json"),
|
|
117
|
+
launch_source: "tool",
|
|
118
|
+
notification_policy: "silent",
|
|
119
|
+
ownerId: deps.getRunOwnerId(ctx),
|
|
120
|
+
policy_values: policyValues(ctx),
|
|
121
|
+
run_id: ToolReviewScheduler.toolReviewRunId(batch.reviewId),
|
|
122
|
+
values: { input_path: batch.reviewerInputPath },
|
|
123
|
+
},
|
|
124
|
+
ctx.cwd,
|
|
125
|
+
);
|
|
126
|
+
},
|
|
127
|
+
process: (state) =>
|
|
128
|
+
ToolReviewScheduler.processToolReviewResult(state, {
|
|
129
|
+
getRunStatus: AsyncRuns.getRunStatus,
|
|
130
|
+
recipeRoot: Paths.getRecipeRoot(),
|
|
131
|
+
}),
|
|
132
|
+
});
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
|
+
}
|