pi-crew 0.11.47 → 0.11.49
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/.githooks/pre-push +15 -0
- package/CHANGELOG.md +92 -0
- package/dist/index.mjs +440 -440
- package/package.json +1 -1
- package/skills/real-test-pi-crew/SKILL.md +5 -0
- package/src/config/env-allowlist-registry.ts +2 -2
- package/src/config/env-vars.ts +1 -1
- package/src/extension/autonomous-policy.ts +8 -1
- package/src/extension/knowledge-injection.ts +5 -1
- package/src/prompt/prompt-runtime.ts +11 -3
- package/src/prompt/surface-worker.ts +1 -1
- package/src/runtime/broker/crew-broker-child.ts +63 -28
- package/src/runtime/broker/crew-broker-delegate-request.ts +5 -5
- package/src/runtime/broker/stdin-handshake.ts +113 -17
- package/src/runtime/child-pi/child-pi-spawn.ts +88 -32
- package/src/runtime/child-pi/child-pi.ts +32 -13
- package/src/state/stores/state-store.ts +34 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
2
|
import * as fs from "node:fs";
|
|
3
3
|
import * as path from "node:path";
|
|
4
|
+
import type { Writable } from "node:stream";
|
|
4
5
|
import type { AgentConfig } from "../../agents/agent-config.ts";
|
|
5
6
|
import { loadConfig } from "../../config/config.ts";
|
|
6
7
|
import type { PiTeamsConfig } from "../../config/types.ts";
|
|
@@ -279,6 +280,16 @@ export interface ChildPiRunInput {
|
|
|
279
280
|
* value issued by the parent's CrewBroker at spawn time; it lives only
|
|
280
281
|
* in the parent's in-memory `Map<runId, token>` and in the child's env.
|
|
281
282
|
* NEVER persisted to manifest, events, mailbox, or any run-dir file.
|
|
283
|
+
*
|
|
284
|
+
* OPT-IN fd3 route (PI_CREW_BROKER_STDIN=1, FIX-1 2026-10-28): the token
|
|
285
|
+
* is still written into built.env for every brokered spawn — RPC/surface
|
|
286
|
+
* branches compose WITHOUT the payload and need the env credential, but
|
|
287
|
+
* the headless JSON branch composes WITH ctx.brokerStdinPayload and
|
|
288
|
+
* `buildFinalChildPiSpawnOptions` strips the token from the FINAL child
|
|
289
|
+
* env there — on that branch alone the token rides the DEDICATED fd3 pipe
|
|
290
|
+
* (stdio entry 4; heap → fd3 → heap) instead. fd0 stays "ignore"
|
|
291
|
+
* (pre-0.11.46 shape) so pi@1.0.4 readPipedStdin can never race or merge
|
|
292
|
+
* the credential into the worker prompt.
|
|
282
293
|
*/
|
|
283
294
|
brokerSpawn?: { socketPath: string; token: string };
|
|
284
295
|
/**
|
|
@@ -852,9 +863,10 @@ async function runChildPiInner(input: ChildPiRunInput): Promise<ChildPiRunResult
|
|
|
852
863
|
// R6-8: modelChain threads the candidate chain for provider-key scoping.
|
|
853
864
|
// v0.11.38 W10-1: workerCacheSplit — headless single-run workers opt into
|
|
854
865
|
// short prompt-cache retention (see PI_CACHE_RETENTION_ENV).
|
|
855
|
-
// LOW-1 closure: ctx.brokerStdinPayload (when present)
|
|
856
|
-
//
|
|
857
|
-
//
|
|
866
|
+
// LOW-1 closure → FD-3: ctx.brokerStdinPayload (when present) adds the
|
|
867
|
+
// DEDICATED fd3 pipe (4th stdio entry; fd0 stays "ignore") + strips
|
|
868
|
+
// PI_CREW_BROKER_TOKEN from the child env — the token travels over
|
|
869
|
+
// stdio[3] right after spawn instead (heap → fd3 → heap).
|
|
858
870
|
const spawnOptions = buildFinalChildPiSpawnOptions(
|
|
859
871
|
input.cwd,
|
|
860
872
|
mergedEnv,
|
|
@@ -920,22 +932,29 @@ async function runChildPiInner(input: ChildPiRunInput): Promise<ChildPiRunResult
|
|
|
920
932
|
ts: new Date().toISOString(),
|
|
921
933
|
});
|
|
922
934
|
}
|
|
923
|
-
// LOW-1 closure: hand the worker its broker
|
|
924
|
-
//
|
|
925
|
-
//
|
|
926
|
-
//
|
|
927
|
-
//
|
|
928
|
-
//
|
|
929
|
-
//
|
|
930
|
-
//
|
|
935
|
+
// LOW-1 closure → FD-3 (2026-10-28): hand the worker its broker
|
|
936
|
+
// credential over the DEDICATED fd3 pipe (stdio[3]) — one
|
|
937
|
+
// buildBrokerStdinLine JSON line (v2, per-task compound token), then
|
|
938
|
+
// close our write end. fd0 is "ignore" (pre-0.11.46 shape): pi@1.0.4
|
|
939
|
+
// readPipedStdin consumes ALL piped fd0 bytes as PROMPT CONTENT in
|
|
940
|
+
// every mode except --mode rpc, so the earlier fd0 handshake raced it
|
|
941
|
+
// three ways (token JSON leaked into the prompt / creds-less silence /
|
|
942
|
+
// pause() deadlock → SIGTERM exit 0 with ~0 stdout — the 0.11.46/47
|
|
943
|
+
// live incident). The worker-side reader (crew-broker-child.ts via
|
|
944
|
+
// stdin-handshake.readFirstLineFromFd) binds the payload by runId +
|
|
945
|
+
// taskId. Best-effort in BOTH directions: any write failure (EPIPE on
|
|
946
|
+
// a fast-exiting child, destroyed stream, missing stdio[3] entry)
|
|
947
|
+
// leaves the worker creds-less exactly like a broker-less spawn —
|
|
948
|
+
// never throws out of the spawn path.
|
|
931
949
|
if (spawnPrep.ctx.brokerStdinPayload) {
|
|
950
|
+
const fd3 = child.stdio[3] as Writable | null | undefined;
|
|
932
951
|
try {
|
|
933
|
-
|
|
952
|
+
fd3?.write(spawnPrep.ctx.brokerStdinPayload);
|
|
934
953
|
} catch {
|
|
935
954
|
/* best-effort: worker proceeds creds-less */
|
|
936
955
|
}
|
|
937
956
|
try {
|
|
938
|
-
|
|
957
|
+
fd3?.end();
|
|
939
958
|
} catch {
|
|
940
959
|
/* EPIPE: child already died — spawn error handling owns that */
|
|
941
960
|
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as fsp from "node:fs/promises";
|
|
3
3
|
import * as path from "node:path";
|
|
4
|
+
import { loadConfig } from "../../config/config.ts";
|
|
5
|
+
import { LIMIT_CEILINGS } from "../../config/config-validation.ts";
|
|
4
6
|
import { DEFAULT_CACHE, DEFAULT_PATHS } from "../../config/defaults.ts";
|
|
5
7
|
import { errors } from "../../errors.ts";
|
|
6
8
|
import type { TeamConfig } from "../../teams/team-config.ts";
|
|
@@ -433,6 +435,24 @@ export function createTasksFromWorkflow(
|
|
|
433
435
|
});
|
|
434
436
|
}
|
|
435
437
|
|
|
438
|
+
/**
|
|
439
|
+
* Admission cap for {@link createRunManifest}: the effective
|
|
440
|
+
* `limits.maxTasksPerRun` from loaded config, falling back to the hard
|
|
441
|
+
* ceiling LIMIT_CEILINGS.maxTasksPerRun — no parsed config value can exceed
|
|
442
|
+
* the ceiling (config-validation clamps it), so a manifest beyond the ceiling
|
|
443
|
+
* is invalid regardless of config. Config-load failure degrades to the
|
|
444
|
+
* ceiling only (an admission check must never itself block run creation on a
|
|
445
|
+
* config read problem; loadConfig surfaces its own warnings separately).
|
|
446
|
+
*/
|
|
447
|
+
function resolveMaxTasksPerRunAdmissionCap(cwd: string): number {
|
|
448
|
+
try {
|
|
449
|
+
const configured = loadConfig(cwd).config.limits?.maxTasksPerRun;
|
|
450
|
+
return typeof configured === "number" && Number.isFinite(configured) ? configured : LIMIT_CEILINGS.maxTasksPerRun;
|
|
451
|
+
} catch {
|
|
452
|
+
return LIMIT_CEILINGS.maxTasksPerRun;
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
436
456
|
export function createRunManifest(params: {
|
|
437
457
|
cwd: string;
|
|
438
458
|
team: TeamConfig;
|
|
@@ -458,6 +478,20 @@ export function createRunManifest(params: {
|
|
|
458
478
|
const paths = createRunPaths(params.cwd, params.runId);
|
|
459
479
|
const now = (params.now ? params.now() : new Date()).toISOString();
|
|
460
480
|
const tasks = params.workflow ? createTasksFromWorkflow(paths.runId, params.workflow, params.team, params.cwd, params.goal) : [];
|
|
481
|
+
// Fix-wave C (2026-10-28): PRE-DISPATCH admission gate for
|
|
482
|
+
// limits.maxTasksPerRun. evaluateCrewPolicy only runs at finalize
|
|
483
|
+
// (finalize-run.ts), so an oversized manifest was previously fully
|
|
484
|
+
// DISPATCHED and only blocked after the run ended. createRunManifest is the
|
|
485
|
+
// single funnel where every normal run entry (team-tool run/parallel,
|
|
486
|
+
// goal-loop turns) materializes its task manifest BEFORE dispatch, so the
|
|
487
|
+
// cap is refused here — before any state directory or event is written.
|
|
488
|
+
// Mid-run dynamic task growth remains owned by the finalize-time policy.
|
|
489
|
+
const maxTasksPerRunCap = resolveMaxTasksPerRunAdmissionCap(params.cwd);
|
|
490
|
+
if (tasks.length > maxTasksPerRunCap) {
|
|
491
|
+
throw errors.config(
|
|
492
|
+
`Refusing to admit run '${params.workflow?.name ?? params.team.name}': manifest has ${tasks.length} tasks, exceeding limits.maxTasksPerRun=${maxTasksPerRunCap}. Reduce the task count or raise 'limits.maxTasksPerRun' in pi-crew config (hard ceiling ${LIMIT_CEILINGS.maxTasksPerRun}).`,
|
|
493
|
+
);
|
|
494
|
+
}
|
|
461
495
|
const manifest: TeamRunManifest = {
|
|
462
496
|
schemaVersion: CURRENT_SCHEMA_VERSION,
|
|
463
497
|
runId: paths.runId,
|