pi-crew 0.11.30 → 0.11.35
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/CHANGELOG.md +23 -0
- package/dist/index.mjs +516 -514
- package/package.json +1 -1
- package/src/config/env-allowlist-registry.ts +18 -0
- package/src/config/env-vars.ts +26 -0
- package/src/extension/crew-cleanup.ts +11 -0
- package/src/runtime/child-pi/child-pi-spawn.ts +41 -1
- package/src/runtime/child-pi/child-pi.ts +4 -0
- package/src/runtime/child-pi/inference-broker.ts +553 -0
package/package.json
CHANGED
|
@@ -198,6 +198,24 @@ const CREW_ENV_FORWARDING_DECL: Record<string, CrewEnvForwardDecision> = {
|
|
|
198
198
|
reason: "per-task compound broker token via built.env/pipe (F4) — secret, never allowlisted",
|
|
199
199
|
},
|
|
200
200
|
PI_CREW_BROKER_TASK_ID: { kind: "child-write", reason: "child-pi-spawn broker handshake" },
|
|
201
|
+
PI_CREW_INFERENCE_BROKER: {
|
|
202
|
+
kind: "gap",
|
|
203
|
+
reason: "v0.11.31 inference broker toggle — resolved in the PARENT only (ensureInferenceBroker); deliberately never forwarded to children (a child re-starting its own broker would be nonsensical)",
|
|
204
|
+
},
|
|
205
|
+
PI_CREW_INFERENCE_TOKEN: {
|
|
206
|
+
kind: "child-write",
|
|
207
|
+
reason: "v0.11.31 per-worker inference broker token via built.env — secret, never allowlisted",
|
|
208
|
+
},
|
|
209
|
+
PI_CREW_INFERENCE_BUDGET_TOKENS: {
|
|
210
|
+
kind: "gap",
|
|
211
|
+
reason: "v0.11.31 per-worker budget read by the parent broker at token issuance (registerInferenceWorker)",
|
|
212
|
+
},
|
|
213
|
+
PI_CREW_INFERENCE_UPSTREAM: {
|
|
214
|
+
kind: "test-only",
|
|
215
|
+
reason: "v0.11.31 broker upstream override — TESTS ONLY (local mock), production never sets it",
|
|
216
|
+
},
|
|
217
|
+
PI_CREW_INFERENCE_UPSTREAM_ANTHROPIC: { kind: "test-only", reason: "v0.11.34 anthropic-only broker upstream override — TESTS ONLY" },
|
|
218
|
+
PI_CREW_INFERENCE_UPSTREAM_OPENAI: { kind: "test-only", reason: "v0.11.34 openai-only broker upstream override — TESTS ONLY" },
|
|
201
219
|
PI_CREW_STEERING_FILE: { kind: "child-write", reason: "child-pi-spawn per-run steering JSONL" },
|
|
202
220
|
PI_CREW_MAX_OUTPUT: { kind: "child-write", reason: "pi-args emits agent.maxTokens into built.env" },
|
|
203
221
|
PI_CREW_RPC_SECRET: { kind: "child-write", reason: "shared HMAC secret handed to the child at spawn — secret name, never allowlisted" },
|
package/src/config/env-vars.ts
CHANGED
|
@@ -494,6 +494,32 @@ export const CREW_ENV_VARS: Record<string, CrewEnvVarSpec> = {
|
|
|
494
494
|
name: "PI_CREW_WORKER_TRANSPORT",
|
|
495
495
|
doc: "W7 EXPERIMENTAL worker transport override: 'rpc' selects the prototype RPC frame-client (src/runtime/rpc/) at the run-worker seam (wired e2728f68; D1/DR2 also plumbs config runtime.workerTransport to the same seam — env WINS over config when set, invalid env fails safe to stdio); anything else/unset → config value, default stdio child-pi path (rpc-worker.ts resolveWorkerTransport)",
|
|
496
496
|
},
|
|
497
|
+
PI_CREW_INFERENCE_BROKER: {
|
|
498
|
+
name: "PI_CREW_INFERENCE_BROKER",
|
|
499
|
+
parser: "boolean",
|
|
500
|
+
doc: "v0.11.31 (S-broker PoC) opt-in loopback inference broker: '1' starts a 127.0.0.1 HTTP broker in the parent; child workers get ONLY a per-worker token (PI_CREW_INFERENCE_TOKEN) + a scoped agent dir (PI_CODING_AGENT_DIR containing models.json that reroutes the built-in anthropic provider to the broker) instead of any real provider key. Real key (ANTHROPIC_API_KEY / ANTHROPIC_AUTH_TOKEN from the PARENT env only) is substituted after requests leave the worker. FAIL-CLOSED: flag set without an upstream credential throws at first spawn (inference-broker.ts). PoC: anthropic upstream only, env-credential users only (auth.json users unsupported yet)",
|
|
501
|
+
},
|
|
502
|
+
PI_CREW_INFERENCE_TOKEN: {
|
|
503
|
+
name: "PI_CREW_INFERENCE_TOKEN",
|
|
504
|
+
doc: "v0.11.31 child-side per-worker inference broker token (256-bit random, heap-only on the parent, injected via built.env by prepareSpawnContext when the broker is active; interpolated into models.json apiKey as $PI_CREW_INFERENCE_TOKEN by the child at request time). NEVER user-set; workerKey-scoped — re-registering a task id replaces (kills) the old token",
|
|
505
|
+
},
|
|
506
|
+
PI_CREW_INFERENCE_BUDGET_TOKENS: {
|
|
507
|
+
name: "PI_CREW_INFERENCE_BUDGET_TOKENS",
|
|
508
|
+
parser: "int",
|
|
509
|
+
doc: "v0.11.31 per-worker inference token budget counted from streamed Anthropic usage events; 0/unset = unlimited. Requests past the budget get 429 (inference-broker.ts registerInferenceWorker)",
|
|
510
|
+
},
|
|
511
|
+
PI_CREW_INFERENCE_UPSTREAM: {
|
|
512
|
+
name: "PI_CREW_INFERENCE_UPSTREAM",
|
|
513
|
+
doc: "v0.11.31 inference broker upstream override applied to EVERY provider (defaults: anthropic https://api.anthropic.com, openai https://api.openai.com/v1). TESTS ONLY — points the broker at a local mock upstream; production callers must never set it",
|
|
514
|
+
},
|
|
515
|
+
PI_CREW_INFERENCE_UPSTREAM_ANTHROPIC: {
|
|
516
|
+
name: "PI_CREW_INFERENCE_UPSTREAM_ANTHROPIC",
|
|
517
|
+
doc: "v0.11.34 anthropic-only broker upstream override (beats PI_CREW_INFERENCE_UPSTREAM). TESTS ONLY — local mock; production must never set it",
|
|
518
|
+
},
|
|
519
|
+
PI_CREW_INFERENCE_UPSTREAM_OPENAI: {
|
|
520
|
+
name: "PI_CREW_INFERENCE_UPSTREAM_OPENAI",
|
|
521
|
+
doc: "v0.11.34 openai-only broker upstream override (beats PI_CREW_INFERENCE_UPSTREAM). TESTS ONLY — local mock; production must never set it",
|
|
522
|
+
},
|
|
497
523
|
PI_CREW_CLASSIFIER_ENABLED: {
|
|
498
524
|
name: "PI_CREW_CLASSIFIER_ENABLED",
|
|
499
525
|
parser: "boolean",
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { stopInferenceBroker } from "../runtime/child-pi/inference-broker.ts";
|
|
2
3
|
import { cleanupAllTrackedTempDirs } from "../runtime/model/pi-args.ts";
|
|
3
4
|
import { isSessionSwitchInFlight } from "../ui/inline-panel/view-session-store.ts";
|
|
4
5
|
import { logInternalError } from "../utils/internal-error.ts";
|
|
@@ -89,6 +90,16 @@ export function registerCleanupHandler(pi: ExtensionAPI, opts?: { disposeTermina
|
|
|
89
90
|
// Kill all child-pi processes
|
|
90
91
|
await cleanupChildProcesses();
|
|
91
92
|
|
|
93
|
+
// v0.11.31: stop the loopback inference broker — revokes every
|
|
94
|
+
// worker token (kill-switch) and removes the scoped agent dir BEFORE
|
|
95
|
+
// generic temp cleanup (which would also catch it — this is the
|
|
96
|
+
// explicit, token-revoking path).
|
|
97
|
+
try {
|
|
98
|
+
stopInferenceBroker();
|
|
99
|
+
} catch (error) {
|
|
100
|
+
logInternalError("crew-cleanup.inference-broker", error);
|
|
101
|
+
}
|
|
102
|
+
|
|
92
103
|
// Cleanup temp directories
|
|
93
104
|
await cleanupTempDirectories();
|
|
94
105
|
|
|
@@ -28,6 +28,13 @@ import { buildPiWorkerArgs, createSafeTempDir, getPiTempBase } from "../model/pi
|
|
|
28
28
|
import { getPiSpawnCommand } from "../pi-spawn.ts";
|
|
29
29
|
import { findLatestScratchpadSnapshot } from "../scratchpad/snapshot-lookup.ts";
|
|
30
30
|
import type { ChildPiRunInput, ChildPiRunResult } from "./child-pi.ts";
|
|
31
|
+
import {
|
|
32
|
+
getInferenceBrokerSnapshot,
|
|
33
|
+
INFERENCE_TOKEN_ENV,
|
|
34
|
+
PI_AGENT_DIR_ENV,
|
|
35
|
+
registerInferenceWorker,
|
|
36
|
+
requireInferenceBrokerIfEnabled,
|
|
37
|
+
} from "./inference-broker.ts";
|
|
31
38
|
|
|
32
39
|
// ── Env allowlist (base set always passed to children) ──────────────────
|
|
33
40
|
// Provider API keys are injected dynamically via buildScopedAllowList() only
|
|
@@ -102,7 +109,13 @@ export function buildChildPiSpawnOptions(
|
|
|
102
109
|
// provider does not spawn keyless. When no model is given, only
|
|
103
110
|
// BASE_ALLOWLIST system vars pass through — no provider keys leak.
|
|
104
111
|
const scopeModels: (string | undefined)[] = modelChain && modelChain.length > 0 ? modelChain : model !== undefined ? [model] : [];
|
|
105
|
-
|
|
112
|
+
// v0.11.31 inference broker: when active, the child's ONLY credential is
|
|
113
|
+
// its per-worker broker token — provider env keys are NEVER injected (they
|
|
114
|
+
// would defeat the broker by handing the worker the real reusable key
|
|
115
|
+
// again). The broker snapshot is read-only module state (no side effects —
|
|
116
|
+
// the broker is started by prepareSpawnContext, never here).
|
|
117
|
+
const inferenceBrokerActive = getInferenceBrokerSnapshot().active;
|
|
118
|
+
const allowList = !inferenceBrokerActive && scopeModels.length > 0 ? buildScopedAllowList(BASE_ALLOWLIST, scopeModels) : BASE_ALLOWLIST;
|
|
106
119
|
// R6-8: warn when the scoped models resolve to ZERO provider env keys AND at
|
|
107
120
|
// least one is a truly-bare id (no "provider/" prefix) — env-key auth + bare
|
|
108
121
|
// id means the child worker will very likely fail authentication (observed as
|
|
@@ -244,6 +257,15 @@ export function buildFinalChildPiSpawnOptions(
|
|
|
244
257
|
// process would never see steering file, kind, role, or broker creds.
|
|
245
258
|
// Safe because step (a) just proved builtEnv holds no secret keys.
|
|
246
259
|
spawnOptions.env = { ...spawnOptions.env, ...builtEnv };
|
|
260
|
+
// (c′) v0.11.31 inference broker: pi's NATIVE agent-dir override is NOT a
|
|
261
|
+
// control-namespace key, so it never travels through built.env (step (a)
|
|
262
|
+
// would reject it) — it is mapped here, at the spawn-options layer, from
|
|
263
|
+
// the broker snapshot when a per-worker token is present. The scoped dir
|
|
264
|
+
// contains ONLY models.json (no auth.json, no extensions, no skills).
|
|
265
|
+
const inferenceSnapshot = getInferenceBrokerSnapshot();
|
|
266
|
+
if (inferenceSnapshot.active && builtEnv[INFERENCE_TOKEN_ENV]) {
|
|
267
|
+
spawnOptions.env[PI_AGENT_DIR_ENV] = inferenceSnapshot.scopedAgentDir ?? "";
|
|
268
|
+
}
|
|
247
269
|
// (d) Return the composed SpawnOptions for spawn(...).
|
|
248
270
|
return spawnOptions;
|
|
249
271
|
}
|
|
@@ -265,6 +287,8 @@ export interface SpawnContext {
|
|
|
265
287
|
tempDir: string | undefined;
|
|
266
288
|
/** The per-call built.env (control vars only) — for security canary assertions. */
|
|
267
289
|
builtEnv: Record<string, string | undefined>;
|
|
290
|
+
/** v0.11.31: inference-broker worker key that owns this spawn's token (cleanup/revoke bookkeeping). */
|
|
291
|
+
inferenceWorkerKey?: string;
|
|
268
292
|
}
|
|
269
293
|
|
|
270
294
|
/**
|
|
@@ -308,6 +332,21 @@ export function prepareSpawnContext(
|
|
|
308
332
|
// child env gets parentDepth+1 = the true grandchild depth.
|
|
309
333
|
env: depthEnv,
|
|
310
334
|
});
|
|
335
|
+
// v0.11.31 inference broker (opt-in PI_CREW_INFERENCE_BROKER=1): the broker
|
|
336
|
+
// must already be running (child-pi.ts run() awaits maybeStartInferenceBroker()
|
|
337
|
+
// first — http listen needs an event-loop turn, so the SYNC spawn path can
|
|
338
|
+
// only require, never start). Issue this worker's token. Control-namespace
|
|
339
|
+
// key → passes assertOnlyControlEnvKeys. FAIL-CLOSED: flag on without a
|
|
340
|
+
// started broker or an upstream credential throws here (never silently
|
|
341
|
+
// spawn keyless / real-key workers). Worker key: the task id when present
|
|
342
|
+
// (retries REPLACE the old token — registerInferenceWorker semantics), else
|
|
343
|
+
// a per-spawn unique.
|
|
344
|
+
const inferenceSnapshot = requireInferenceBrokerIfEnabled();
|
|
345
|
+
let inferenceWorkerKey: string | undefined;
|
|
346
|
+
if (inferenceSnapshot.active) {
|
|
347
|
+
inferenceWorkerKey = input.agentId ? `inference:${input.agentId}` : `inference:spawn:${process.pid}:${Date.now().toString(36)}`;
|
|
348
|
+
built.env[INFERENCE_TOKEN_ENV] = registerInferenceWorker(inferenceWorkerKey);
|
|
349
|
+
}
|
|
311
350
|
// Pass steering file path to child for real-time steer injection
|
|
312
351
|
if (input.steeringFile) built.env.PI_CREW_STEERING_FILE = input.steeringFile;
|
|
313
352
|
// I5: run/task identity is threaded ALWAYS (not broker-gated) so the worker's
|
|
@@ -433,6 +472,7 @@ export function prepareSpawnContext(
|
|
|
433
472
|
mergedEnv: { ...process.env, ...built.env },
|
|
434
473
|
tempDir: built.tempDir,
|
|
435
474
|
builtEnv: built.env,
|
|
475
|
+
inferenceWorkerKey,
|
|
436
476
|
},
|
|
437
477
|
};
|
|
438
478
|
}
|
|
@@ -36,6 +36,7 @@ import { ChildPiSteeringController } from "./child-pi-steering.ts";
|
|
|
36
36
|
import { ChildPiLineObserver, isFinalAssistantEvent } from "./child-pi-streams.ts";
|
|
37
37
|
// Phase 2.3: the six timer constructs moved to child-pi-timers.ts (pure motion).
|
|
38
38
|
import { createChildPiTimers } from "./child-pi-timers.ts";
|
|
39
|
+
import { maybeStartInferenceBroker } from "./inference-broker.ts";
|
|
39
40
|
import { runMockChildPi } from "./mock-fixtures.ts";
|
|
40
41
|
// W2 (session-file recovery): deterministic worker session flags + crash-path
|
|
41
42
|
// tail-replay of the session JSONL (see session-recovery.ts header).
|
|
@@ -777,6 +778,9 @@ export async function runChildPi(input: ChildPiRunInput): Promise<ChildPiRunResu
|
|
|
777
778
|
// guard below returns before the argv is ever consumed.
|
|
778
779
|
const versionedInput: ChildPiRunInput =
|
|
779
780
|
!input.signal?.aborted && !resolveHermeticWorkers(input.hermeticWorkers) ? { ...input, piVersion: await probePiVersion() } : input;
|
|
781
|
+
// v0.11.31: start the inference broker (flag-gated, fail-closed) BEFORE the
|
|
782
|
+
// sync spawn-prep path — http listen needs an event-loop turn.
|
|
783
|
+
await maybeStartInferenceBroker();
|
|
780
784
|
const spawnPrep = prepareSpawnContext(brokerSpawn ? { ...versionedInput, brokerSpawn } : versionedInput, effectiveTask, depthEnv);
|
|
781
785
|
if (spawnPrep.kind === "aborted") return spawnPrep.result;
|
|
782
786
|
const { spawnSpec, mergedEnv, tempDir, builtEnv, builtArgs } = spawnPrep.ctx;
|