@llblab/pi-actors 0.37.0 → 0.38.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/BACKLOG.md +1 -1
- package/CHANGELOG.md +19 -0
- package/README.md +6 -5
- package/dist/index.js +23 -3
- package/dist/lib/async-runs.d.ts +5 -0
- package/dist/lib/async-runs.js +87 -8
- package/dist/lib/command-templates.d.ts +2 -0
- package/dist/lib/execution.d.ts +4 -0
- package/dist/lib/execution.js +154 -13
- package/dist/lib/model-context.d.ts +56 -0
- package/dist/lib/model-context.js +220 -0
- package/dist/lib/observability.d.ts +2 -0
- package/dist/lib/observability.js +32 -6
- package/dist/lib/preflight-diagnostics.d.ts +27 -0
- package/dist/lib/preflight-diagnostics.js +86 -0
- package/dist/lib/prompts.d.ts +1 -1
- package/dist/lib/prompts.js +2 -2
- package/dist/lib/recipes-context.d.ts +7 -0
- package/dist/lib/recipes-context.js +75 -5
- package/dist/lib/recipes-discovery.js +15 -0
- package/dist/lib/recipes-references.d.ts +2 -0
- package/dist/lib/recipes-references.js +10 -0
- package/dist/lib/tools-local.d.ts +2 -1
- package/dist/lib/tools-local.js +6 -4
- package/dist/lib/tools-response.js +22 -1
- package/dist/lib/tools-spawn.d.ts +2 -1
- package/dist/lib/tools-spawn.js +2 -1
- package/dist/recipes/lens-swarm.json +15 -2
- package/dist/recipes/pipeline-release-readiness.json +22 -3
- package/dist/recipes/pipeline-review-readiness.json +22 -3
- package/dist/recipes/subagent-judge.json +2 -1
- package/dist/recipes/subagent-merge.json +5 -2
- package/dist/recipes/subagent-normalize.json +2 -1
- package/dist/recipes/subagent-preflight.json +29 -0
- package/dist/recipes/subagent-review-coordinator.json +79 -7
- package/dist/recipes/subagent-review.json +2 -1
- package/dist/recipes/subagent-verify.json +2 -1
- package/dist/scripts/async-runner.mjs +84 -10
- package/dist/scripts/conformance.mjs +1 -0
- package/dist/skills/actors/SKILL.md +5 -3
- package/dist/skills/swarm/SKILL.md +2 -2
- package/docs/async-runs.md +3 -1
- package/docs/command-templates.md +4 -1
- package/docs/recipe-library.md +5 -2
- package/docs/template-recipes.md +2 -2
- package/index.ts +24 -3
- package/lib/async-runs.ts +157 -8
- package/lib/command-templates.ts +2 -0
- package/lib/execution.ts +218 -24
- package/lib/model-context.ts +359 -0
- package/lib/observability.ts +35 -6
- package/lib/preflight-diagnostics.ts +132 -0
- package/lib/prompts.ts +2 -2
- package/lib/recipes-context.ts +86 -5
- package/lib/recipes-discovery.ts +15 -0
- package/lib/recipes-references.ts +12 -0
- package/lib/tools-local.ts +22 -8
- package/lib/tools-response.ts +26 -1
- package/lib/tools-spawn.ts +6 -2
- package/package.json +1 -1
- package/recipes/lens-swarm.json +15 -2
- package/recipes/pipeline-release-readiness.json +22 -3
- package/recipes/pipeline-review-readiness.json +22 -3
- package/recipes/subagent-judge.json +2 -1
- package/recipes/subagent-merge.json +5 -2
- package/recipes/subagent-normalize.json +2 -1
- package/recipes/subagent-preflight.json +29 -0
- package/recipes/subagent-review-coordinator.json +79 -7
- package/recipes/subagent-review.json +2 -1
- package/recipes/subagent-verify.json +2 -1
- package/scripts/async-runner.mjs +84 -10
- package/scripts/conformance.mjs +1 -0
- package/skills/actors/SKILL.md +5 -3
- package/skills/swarm/SKILL.md +2 -2
package/lib/execution.ts
CHANGED
|
@@ -32,10 +32,13 @@ export interface ToolExecResult {
|
|
|
32
32
|
export interface BranchReport {
|
|
33
33
|
code: number;
|
|
34
34
|
command: string;
|
|
35
|
+
failureReason?: string;
|
|
35
36
|
killed: boolean;
|
|
36
37
|
label: string;
|
|
37
38
|
status: "done" | "failed" | "timeout";
|
|
38
39
|
stderr?: string;
|
|
40
|
+
stderrBytes: number;
|
|
41
|
+
stdout?: string;
|
|
39
42
|
stdoutBytes: number;
|
|
40
43
|
}
|
|
41
44
|
|
|
@@ -62,6 +65,7 @@ export interface RegisteredToolExecutionResult {
|
|
|
62
65
|
killed: boolean;
|
|
63
66
|
}>;
|
|
64
67
|
softQuorum?: SoftQuorumReport;
|
|
68
|
+
failureReason?: string;
|
|
65
69
|
template: CommandTemplates.CommandTemplateValue;
|
|
66
70
|
templateWarnings?: string[];
|
|
67
71
|
tool: string;
|
|
@@ -123,6 +127,20 @@ function formatCommandDetail(commands: string[]): string {
|
|
|
123
127
|
return commands.length === 1 ? commands[0] : commands.join(" && ");
|
|
124
128
|
}
|
|
125
129
|
|
|
130
|
+
function getExecutionFailureReason(
|
|
131
|
+
branches: BranchReport[],
|
|
132
|
+
softQuorum: SoftQuorumReport | undefined,
|
|
133
|
+
stderr: string,
|
|
134
|
+
): string {
|
|
135
|
+
if (/parallel branch quorum not met/.test(stderr)) {
|
|
136
|
+
return "parallel_quorum_not_met";
|
|
137
|
+
}
|
|
138
|
+
if (branches.length > 0 && softQuorum?.usable === false) {
|
|
139
|
+
return "all_branches_unusable";
|
|
140
|
+
}
|
|
141
|
+
return "command_failed";
|
|
142
|
+
}
|
|
143
|
+
|
|
126
144
|
function mergeDefaults(
|
|
127
145
|
inherited: Record<string, unknown> | undefined,
|
|
128
146
|
own: Record<string, unknown> | undefined,
|
|
@@ -145,22 +163,40 @@ function getBranchStatus(result: ToolExecResult): BranchReport["status"] {
|
|
|
145
163
|
return result.killed ? "timeout" : "failed";
|
|
146
164
|
}
|
|
147
165
|
|
|
166
|
+
function getBranchFailureReason(
|
|
167
|
+
result: ToolExecResult,
|
|
168
|
+
): string | undefined {
|
|
169
|
+
if (result.code === 0) {
|
|
170
|
+
return result.stdout.trim() ? undefined : "empty_output";
|
|
171
|
+
}
|
|
172
|
+
if (result.killed) return "timeout_or_killed";
|
|
173
|
+
return result.stderr.trim() ? "nonzero_exit" : "nonzero_exit_empty_stderr";
|
|
174
|
+
}
|
|
175
|
+
|
|
148
176
|
function createBranchReport(
|
|
149
177
|
label: string,
|
|
150
178
|
command: string,
|
|
151
179
|
result: ToolExecResult,
|
|
152
180
|
): BranchReport {
|
|
181
|
+
const failureReason = getBranchFailureReason(result);
|
|
153
182
|
return {
|
|
154
183
|
code: result.code,
|
|
155
184
|
command,
|
|
185
|
+
...(failureReason ? { failureReason } : {}),
|
|
156
186
|
killed: result.killed,
|
|
157
187
|
label,
|
|
158
188
|
status: getBranchStatus(result),
|
|
159
|
-
...(result.stderr ? { stderr: result.stderr.slice(
|
|
189
|
+
...(result.stderr ? { stderr: result.stderr.slice(-1000) } : {}),
|
|
190
|
+
stderrBytes: Buffer.byteLength(result.stderr),
|
|
191
|
+
...(result.stdout ? { stdout: result.stdout.slice(-1000) } : {}),
|
|
160
192
|
stdoutBytes: Buffer.byteLength(result.stdout),
|
|
161
193
|
};
|
|
162
194
|
}
|
|
163
195
|
|
|
196
|
+
function isUsableBranch(branch: BranchReport): boolean {
|
|
197
|
+
return branch.status === "done" && branch.stdoutBytes > 0;
|
|
198
|
+
}
|
|
199
|
+
|
|
164
200
|
function createSoftQuorum(
|
|
165
201
|
branches: BranchReport[],
|
|
166
202
|
): SoftQuorumReport | undefined {
|
|
@@ -173,10 +209,14 @@ function createSoftQuorum(
|
|
|
173
209
|
done,
|
|
174
210
|
expected: branches.length,
|
|
175
211
|
failed,
|
|
176
|
-
usable:
|
|
212
|
+
usable: branches.some(isUsableBranch),
|
|
177
213
|
};
|
|
178
214
|
}
|
|
179
215
|
|
|
216
|
+
function countUsableBranches(branches: BranchReport[]): number {
|
|
217
|
+
return branches.filter(isUsableBranch).length;
|
|
218
|
+
}
|
|
219
|
+
|
|
180
220
|
function getMaxParallelBranches(): number {
|
|
181
221
|
const raw = Number(process.env.PI_ACTORS_MAX_PARALLEL_BRANCHES ?? "");
|
|
182
222
|
return Number.isInteger(raw) && raw > 0 ? raw : DEFAULT_MAX_PARALLEL_BRANCHES;
|
|
@@ -230,6 +270,34 @@ function normalizeRetry(
|
|
|
230
270
|
return resolved;
|
|
231
271
|
}
|
|
232
272
|
|
|
273
|
+
function normalizeConcurrency(
|
|
274
|
+
value: number | string | undefined,
|
|
275
|
+
values: Record<string, unknown>,
|
|
276
|
+
branchCount: number,
|
|
277
|
+
): number {
|
|
278
|
+
const resolved = resolveNumericControlField(value, values, "concurrency");
|
|
279
|
+
if (resolved === undefined) return branchCount;
|
|
280
|
+
if (!Number.isInteger(resolved) || resolved < 1)
|
|
281
|
+
throw new Error("Command template concurrency must be a positive integer.");
|
|
282
|
+
return Math.min(resolved, branchCount);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function normalizeMinSuccessful(
|
|
286
|
+
value: number | string | undefined,
|
|
287
|
+
values: Record<string, unknown>,
|
|
288
|
+
branchCount: number,
|
|
289
|
+
): number | undefined {
|
|
290
|
+
const resolved = resolveNumericControlField(value, values, "min_successful");
|
|
291
|
+
if (resolved === undefined) return undefined;
|
|
292
|
+
if (!Number.isInteger(resolved) || resolved < 0)
|
|
293
|
+
throw new Error("Command template min_successful must be a non-negative integer.");
|
|
294
|
+
if (resolved > branchCount)
|
|
295
|
+
throw new Error(
|
|
296
|
+
`Command template min_successful ${resolved} exceeds branch count ${branchCount}.`,
|
|
297
|
+
);
|
|
298
|
+
return resolved;
|
|
299
|
+
}
|
|
300
|
+
|
|
233
301
|
function getRecoverConfig(
|
|
234
302
|
config: CommandTemplates.CommandTemplateValue,
|
|
235
303
|
): CommandTemplates.CommandTemplateConfig {
|
|
@@ -309,11 +377,34 @@ async function applyDelay(
|
|
|
309
377
|
await sleep(resolved, signal);
|
|
310
378
|
}
|
|
311
379
|
|
|
380
|
+
function getParallelStatus(
|
|
381
|
+
branches: BranchReport[],
|
|
382
|
+
minSuccessful: number | undefined,
|
|
383
|
+
): "complete" | "degraded" | "insufficient_data" {
|
|
384
|
+
const usable = countUsableBranches(branches);
|
|
385
|
+
if (minSuccessful !== undefined && usable < minSuccessful) {
|
|
386
|
+
return "insufficient_data";
|
|
387
|
+
}
|
|
388
|
+
return branches.every(isUsableBranch) ? "complete" : "degraded";
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
function formatParallelStatusHeader(
|
|
392
|
+
branches: BranchReport[],
|
|
393
|
+
minSuccessful: number | undefined,
|
|
394
|
+
): string | undefined {
|
|
395
|
+
if (minSuccessful === undefined) return undefined;
|
|
396
|
+
return `--- parallel_status: ${getParallelStatus(
|
|
397
|
+
branches,
|
|
398
|
+
minSuccessful,
|
|
399
|
+
)} usable: ${countUsableBranches(branches)} expected: ${branches.length} minimum: ${minSuccessful} ---`;
|
|
400
|
+
}
|
|
401
|
+
|
|
312
402
|
function joinParallelStdout(
|
|
313
403
|
branches: BranchReport[],
|
|
314
404
|
results: ToolExecResult[],
|
|
405
|
+
minSuccessful?: number,
|
|
315
406
|
): string {
|
|
316
|
-
|
|
407
|
+
const body = results
|
|
317
408
|
.map((result, index) => {
|
|
318
409
|
const branch = branches[index];
|
|
319
410
|
const header = `--- branch: ${branch.label} status: ${branch.status} ---`;
|
|
@@ -322,6 +413,30 @@ function joinParallelStdout(
|
|
|
322
413
|
return `${header}\nexit: ${branch.code}${stderr}`;
|
|
323
414
|
})
|
|
324
415
|
.join("\n");
|
|
416
|
+
return [formatParallelStatusHeader(branches, minSuccessful), body]
|
|
417
|
+
.filter(Boolean)
|
|
418
|
+
.join("\n");
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
async function mapConcurrent<T, TResult>(
|
|
422
|
+
items: readonly T[],
|
|
423
|
+
concurrency: number,
|
|
424
|
+
fn: (item: T, index: number) => Promise<TResult>,
|
|
425
|
+
): Promise<TResult[]> {
|
|
426
|
+
const results = new Array<TResult>(items.length);
|
|
427
|
+
let nextIndex = 0;
|
|
428
|
+
async function worker(): Promise<void> {
|
|
429
|
+
for (;;) {
|
|
430
|
+
const index = nextIndex;
|
|
431
|
+
nextIndex += 1;
|
|
432
|
+
if (index >= items.length) return;
|
|
433
|
+
results[index] = await fn(items[index]!, index);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
await Promise.all(
|
|
437
|
+
Array.from({ length: Math.min(concurrency, items.length) }, () => worker()),
|
|
438
|
+
);
|
|
439
|
+
return results;
|
|
325
440
|
}
|
|
326
441
|
|
|
327
442
|
async function executeRetriableTemplateConfig(
|
|
@@ -466,7 +581,17 @@ async function executeTemplateConfig(
|
|
|
466
581
|
};
|
|
467
582
|
});
|
|
468
583
|
return executeTemplateConfig(
|
|
469
|
-
{
|
|
584
|
+
{
|
|
585
|
+
parallel: normalized.parallel === true,
|
|
586
|
+
...(normalized.concurrency !== undefined
|
|
587
|
+
? { concurrency: normalized.concurrency }
|
|
588
|
+
: {}),
|
|
589
|
+
...(normalized.min_successful !== undefined
|
|
590
|
+
? { min_successful: normalized.min_successful }
|
|
591
|
+
: {}),
|
|
592
|
+
...(normalized.failure !== undefined ? { failure: normalized.failure } : {}),
|
|
593
|
+
template: repeatedSteps,
|
|
594
|
+
},
|
|
470
595
|
context,
|
|
471
596
|
params,
|
|
472
597
|
exec,
|
|
@@ -552,19 +677,27 @@ async function executeTemplateConfig(
|
|
|
552
677
|
throw new Error(formatToolText("Tool template produced no command steps."));
|
|
553
678
|
if (normalized.parallel === true) {
|
|
554
679
|
assertParallelBranchLimit(steps.length);
|
|
555
|
-
const
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
680
|
+
const concurrency = normalizeConcurrency(
|
|
681
|
+
normalized.concurrency,
|
|
682
|
+
controlValues,
|
|
683
|
+
steps.length,
|
|
684
|
+
);
|
|
685
|
+
const minSuccessful = normalizeMinSuccessful(
|
|
686
|
+
normalized.min_successful,
|
|
687
|
+
controlValues,
|
|
688
|
+
steps.length,
|
|
689
|
+
);
|
|
690
|
+
const branchResults = await mapConcurrent(steps, concurrency, (step) =>
|
|
691
|
+
executeTemplateConfig(
|
|
692
|
+
step,
|
|
693
|
+
context,
|
|
694
|
+
params,
|
|
695
|
+
exec,
|
|
696
|
+
cwd,
|
|
697
|
+
signal,
|
|
698
|
+
stdin,
|
|
699
|
+
false,
|
|
700
|
+
actorRecipeContext,
|
|
568
701
|
),
|
|
569
702
|
);
|
|
570
703
|
const commands = branchResults.flatMap((item) => item.commands);
|
|
@@ -576,6 +709,12 @@ async function executeTemplateConfig(
|
|
|
576
709
|
item.result,
|
|
577
710
|
),
|
|
578
711
|
);
|
|
712
|
+
const usableBranches = countUsableBranches(branches);
|
|
713
|
+
const quorumUnmet =
|
|
714
|
+
minSuccessful !== undefined && usableBranches < minSuccessful;
|
|
715
|
+
const quorumFailureMessage = quorumUnmet
|
|
716
|
+
? `parallel branch quorum not met: usable ${usableBranches}/${branches.length}, minimum ${minSuccessful}`
|
|
717
|
+
: "";
|
|
579
718
|
const nodeFailure = getFailureScope(normalized);
|
|
580
719
|
const rootFailure = branchResults.find((item, index) => {
|
|
581
720
|
if (item.result.code === 0) return false;
|
|
@@ -603,6 +742,9 @@ async function executeTemplateConfig(
|
|
|
603
742
|
const firstFailedBranch = branchResults.find(
|
|
604
743
|
(item) => item.result.code !== 0,
|
|
605
744
|
);
|
|
745
|
+
const allBranchesUnusable = branches.every(
|
|
746
|
+
(branch) => !isUsableBranch(branch),
|
|
747
|
+
);
|
|
606
748
|
const successful = branchResults.map((item) => {
|
|
607
749
|
if (item.result.code === 0) return item.result;
|
|
608
750
|
addResultFailure(failures, item);
|
|
@@ -615,9 +757,31 @@ async function executeTemplateConfig(
|
|
|
615
757
|
.map((item) => item.stderr)
|
|
616
758
|
.filter(Boolean)
|
|
617
759
|
.join("\n"),
|
|
618
|
-
stdout: joinParallelStdout(branches, successful),
|
|
760
|
+
stdout: joinParallelStdout(branches, successful, minSuccessful),
|
|
619
761
|
};
|
|
620
|
-
if (
|
|
762
|
+
if (quorumUnmet && nodeFailure === "root") {
|
|
763
|
+
return {
|
|
764
|
+
commands,
|
|
765
|
+
branches: [
|
|
766
|
+
...branchResults.flatMap((item) => item.branches),
|
|
767
|
+
...branches,
|
|
768
|
+
],
|
|
769
|
+
criticalFailure: true,
|
|
770
|
+
failureScope: "root",
|
|
771
|
+
failures,
|
|
772
|
+
result: {
|
|
773
|
+
...result,
|
|
774
|
+
code: firstFailedBranch?.result.code || 1,
|
|
775
|
+
stderr: [result.stderr, quorumFailureMessage]
|
|
776
|
+
.filter(Boolean)
|
|
777
|
+
.join("\n"),
|
|
778
|
+
},
|
|
779
|
+
};
|
|
780
|
+
}
|
|
781
|
+
const branchFailureTriggered = minSuccessful === undefined
|
|
782
|
+
? firstFailedBranch || allBranchesUnusable
|
|
783
|
+
: quorumUnmet;
|
|
784
|
+
if (branchFailureTriggered && nodeFailure === "branch") {
|
|
621
785
|
return {
|
|
622
786
|
commands,
|
|
623
787
|
branches: [
|
|
@@ -626,7 +790,19 @@ async function executeTemplateConfig(
|
|
|
626
790
|
],
|
|
627
791
|
failureScope: "branch",
|
|
628
792
|
failures,
|
|
629
|
-
result: {
|
|
793
|
+
result: {
|
|
794
|
+
...result,
|
|
795
|
+
code: firstFailedBranch?.result.code || 1,
|
|
796
|
+
stderr: [
|
|
797
|
+
result.stderr,
|
|
798
|
+
allBranchesUnusable
|
|
799
|
+
? "all parallel branches failed or produced empty output"
|
|
800
|
+
: "",
|
|
801
|
+
quorumFailureMessage,
|
|
802
|
+
]
|
|
803
|
+
.filter(Boolean)
|
|
804
|
+
.join("\n"),
|
|
805
|
+
},
|
|
630
806
|
};
|
|
631
807
|
}
|
|
632
808
|
return {
|
|
@@ -735,6 +911,7 @@ export async function executeRegisteredTool(
|
|
|
735
911
|
);
|
|
736
912
|
const command = formatCommandDetail(executed.commands);
|
|
737
913
|
const result = executed.result;
|
|
914
|
+
const softQuorum = createSoftQuorum(executed.branches);
|
|
738
915
|
if (result.code !== 0) {
|
|
739
916
|
const formatted = formatFailureOutput(
|
|
740
917
|
cfg.name,
|
|
@@ -743,7 +920,26 @@ export async function executeRegisteredTool(
|
|
|
743
920
|
result.stdout,
|
|
744
921
|
result.stderr,
|
|
745
922
|
);
|
|
746
|
-
throw new Error(formatted.text)
|
|
923
|
+
throw Object.assign(new Error(formatted.text), {
|
|
924
|
+
details: {
|
|
925
|
+
branches: executed.branches,
|
|
926
|
+
code: result.code,
|
|
927
|
+
command,
|
|
928
|
+
killed: result.killed,
|
|
929
|
+
...(executed.failures.length > 0
|
|
930
|
+
? { nonCriticalFailures: executed.failures }
|
|
931
|
+
: {}),
|
|
932
|
+
...(softQuorum ? { softQuorum } : {}),
|
|
933
|
+
failureReason: getExecutionFailureReason(
|
|
934
|
+
executed.branches,
|
|
935
|
+
softQuorum,
|
|
936
|
+
result.stderr,
|
|
937
|
+
),
|
|
938
|
+
template: cfg.template!,
|
|
939
|
+
tool: cfg.name,
|
|
940
|
+
truncated: formatted.truncated,
|
|
941
|
+
},
|
|
942
|
+
});
|
|
747
943
|
}
|
|
748
944
|
const formatted = formatOutput(cfg.name, "stdout", result.stdout);
|
|
749
945
|
const templateWarnings = CommandTemplates.getCommandTemplateWarnings(
|
|
@@ -760,9 +956,7 @@ export async function executeRegisteredTool(
|
|
|
760
956
|
...(executed.failures.length > 0
|
|
761
957
|
? { nonCriticalFailures: executed.failures }
|
|
762
958
|
: {}),
|
|
763
|
-
...(
|
|
764
|
-
? { softQuorum: createSoftQuorum(executed.branches) }
|
|
765
|
-
: {}),
|
|
959
|
+
...(softQuorum ? { softQuorum } : {}),
|
|
766
960
|
template: cfg.template!,
|
|
767
961
|
...(templateWarnings.length > 0 ? { templateWarnings } : {}),
|
|
768
962
|
tool: cfg.name,
|