pi-crew 0.9.46 → 0.9.48
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 +83 -0
- package/README.md +16 -2
- package/dist/build-meta.json +289 -164
- package/dist/index.mjs +1744 -2732
- package/dist/index.mjs.map +4 -4
- package/docs/decisions/2026-07-21-broker-phase4-default-on.md +77 -0
- package/docs/decisions/2026-07-21-broker-windows-perms.md +91 -0
- package/docs/decisions/2026-07-22-broker-phase4-gated-on.md +99 -0
- package/docs/decisions/README.md +3 -0
- package/docs/publishing.md +29 -0
- package/package.json +3 -1
- package/scripts/build-bundle.mjs +7 -0
- package/scripts/postinstall.mjs +60 -1
- package/scripts/pty_probe.py +174 -0
- package/skills/real-test-pi-crew/SKILL.md +659 -0
- package/src/config/config.ts +42 -1
- package/src/config/defaults.ts +45 -1
- package/src/config/types.ts +19 -0
- package/src/extension/register.ts +6 -1
- package/src/extension/registration/context-builder.ts +4 -0
- package/src/extension/registration/lifecycle-handlers.ts +166 -3
- package/src/extension/registration/registration-types.ts +9 -0
- package/src/prompt/prompt-runtime.ts +108 -0
- package/src/runtime/broker-issuer.ts +37 -0
- package/src/runtime/child-pi-spawn.ts +53 -0
- package/src/runtime/child-pi.ts +42 -11
- package/src/runtime/crew-broker-child.ts +88 -0
- package/src/runtime/crew-broker-client.ts +673 -0
- package/src/runtime/crew-broker-tokens.ts +84 -0
- package/src/runtime/crew-broker.ts +1276 -0
- package/src/runtime/dynamic-workflow-context.ts +7 -3
- package/src/runtime/dynamic-workflow-runner.ts +1 -1
- package/src/runtime/plan-templates.ts +8 -6
- package/src/schema/config-schema.ts +14 -0
- package/src/state/mailbox.ts +43 -0
- package/src/ui/key-utils.ts +42 -0
- package/src/ui/keybinding-map.ts +29 -3
- package/src/ui/run-dashboard.ts +28 -0
- package/src/ui/settings-overlay.ts +42 -22
- package/src/utils/ndjson.ts +115 -0
- package/src/utils/session-utils.ts +30 -0
- package/src/utils/socket-path.ts +127 -0
- package/workflows/default.workflow.md +1 -1
- package/workflows/fast-fix.workflow.md +1 -1
- package/workflows/plan-execute.workflow.md +1 -1
- package/workflows/review.workflow.md +1 -1
package/src/runtime/child-pi.ts
CHANGED
|
@@ -9,9 +9,10 @@ import { atomicWriteFile } from "../state/atomic-write.ts";
|
|
|
9
9
|
import type { WorkerExitStatus } from "../state/types.ts";
|
|
10
10
|
import { logInternalError } from "../utils/internal-error.ts";
|
|
11
11
|
import { redactSecretString } from "../utils/redaction.ts";
|
|
12
|
+
import { getActiveBrokerIssuer } from "./broker-issuer.ts";
|
|
12
13
|
import { FINAL_DRAIN_MS, HARD_KILL_MS, POST_EXIT_STDIO_GUARD_MS, RESPONSE_TIMEOUT_MS } from "./child-pi-constants.ts";
|
|
13
14
|
import { appendBoundedTail, clearHardKillTimer, killProcessTree, registerActiveChild, unregisterActiveChild } from "./child-pi-kill.ts";
|
|
14
|
-
import {
|
|
15
|
+
import { buildFinalChildPiSpawnOptions, prepareSpawnContext } from "./child-pi-spawn.ts";
|
|
15
16
|
import { ChildPiSteeringController } from "./child-pi-steering.ts";
|
|
16
17
|
// Internal helpers for active-child bookkeeping (extracted to child-pi-kill.ts).
|
|
17
18
|
import { ChildPiLineObserver } from "./child-pi-streams.ts";
|
|
@@ -25,7 +26,9 @@ export {
|
|
|
25
26
|
// ── Re-export from child-pi-spawn.ts (H-7 decomposition step 6) ──
|
|
26
27
|
// buildChildPiSpawnOptions was previously exported from child-pi.ts. Keep the
|
|
27
28
|
// public API surface stable by re-exporting from the new module.
|
|
28
|
-
|
|
29
|
+
// buildFinalChildPiSpawnOptions (BLOCKER 2 / S5) — composed spawn helper that
|
|
30
|
+
// owns the canary + filter + spread sequence; lives in child-pi-spawn.ts.
|
|
31
|
+
export { buildChildPiSpawnOptions, buildFinalChildPiSpawnOptions } from "./child-pi-spawn.ts";
|
|
29
32
|
// ── Re-export from child-pi-streams.ts (H-7 decomposition step 4) ──
|
|
30
33
|
export { ChildPiLineObserver } from "./child-pi-streams.ts";
|
|
31
34
|
|
|
@@ -142,6 +145,25 @@ export interface ChildPiRunInput {
|
|
|
142
145
|
role?: string;
|
|
143
146
|
/** Root directory for artifacts (used to validate transcriptPath). */
|
|
144
147
|
artifactsRoot?: string;
|
|
148
|
+
/**
|
|
149
|
+
* Optional broker spawn context (Phase 0 inter-pi broker). When present,
|
|
150
|
+
* `prepareSpawnContext` injects `PI_CREW_BROKER_SOCKET` and
|
|
151
|
+
* `PI_CREW_BROKER_TOKEN` into the child env (control-namespace keys,
|
|
152
|
+
* accepted by `assertOnlyControlEnvKeys`). The token is a 128-bit random
|
|
153
|
+
* value issued by the parent's CrewBroker at spawn time; it lives only
|
|
154
|
+
* in the parent's in-memory `Map<runId, token>` and in the child's env.
|
|
155
|
+
* NEVER persisted to manifest, events, mailbox, or any run-dir file.
|
|
156
|
+
*/
|
|
157
|
+
brokerSpawn?: { socketPath: string; token: string };
|
|
158
|
+
/**
|
|
159
|
+
* Optional Phase 0 broker credentials issuer. Called only when
|
|
160
|
+
* `brokerSpawn` is not already set on the input. The default no-op
|
|
161
|
+
* issuer returns undefined, leaving the child to run without broker
|
|
162
|
+
* credentials (today's behavior — file paths remain authoritative).
|
|
163
|
+
* The production wiring passes a closure that delegates to the
|
|
164
|
+
* session's `CrewBrokerLifecycleController.issueForChild`.
|
|
165
|
+
*/
|
|
166
|
+
brokerIssuer?: (runId: string) => Promise<{ socketPath: string; token: string } | undefined>;
|
|
145
167
|
}
|
|
146
168
|
|
|
147
169
|
export interface ChildPiRunResult {
|
|
@@ -368,19 +390,28 @@ export async function runChildPi(input: ChildPiRunInput): Promise<ChildPiRunResu
|
|
|
368
390
|
// prepareSpawnContext builds the worker args, attaches the steering file env,
|
|
369
391
|
// and handles the pre-spawn abort check (returns an immediate-abort result
|
|
370
392
|
// if the parent signal has already fired).
|
|
371
|
-
|
|
393
|
+
//
|
|
394
|
+
// Phase 0 broker: if the caller did not pre-fill `brokerSpawn`, ask the
|
|
395
|
+
// optional issuer for one. The issuer is gated by the lifecycle controller
|
|
396
|
+
// (root-session + flag); a no-op issuer yields undefined (no credentials).
|
|
397
|
+
let brokerSpawn = input.brokerSpawn;
|
|
398
|
+
const brokerIssuer = input.brokerIssuer ?? getActiveBrokerIssuer();
|
|
399
|
+
if (!brokerSpawn && brokerIssuer && input.runId) {
|
|
400
|
+
try {
|
|
401
|
+
brokerSpawn = await brokerIssuer(input.runId);
|
|
402
|
+
} catch {
|
|
403
|
+
brokerSpawn = undefined;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
const spawnPrep = prepareSpawnContext(brokerSpawn ? { ...input, brokerSpawn } : input, effectiveTask);
|
|
372
407
|
if (spawnPrep.kind === "aborted") return spawnPrep.result;
|
|
373
408
|
const { spawnSpec, mergedEnv, tempDir, builtEnv } = spawnPrep.ctx;
|
|
374
409
|
try {
|
|
375
410
|
return await new Promise<ChildPiRunResult>((resolve) => {
|
|
376
|
-
//
|
|
377
|
-
//
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
// before reaching the child. The canary guards against accidental
|
|
381
|
-
// additions to built.env leaking secrets to children.
|
|
382
|
-
assertOnlyControlEnvKeys(builtEnv);
|
|
383
|
-
const child = spawn(spawnSpec.command, spawnSpec.args, buildChildPiSpawnOptions(input.cwd, mergedEnv, input.model));
|
|
411
|
+
// Compose the final SpawnOptions: canary + filter + spread are now
|
|
412
|
+
// owned by buildFinalChildPiSpawnOptions (see child-pi-spawn.ts, BLOCKER 2 / S5).
|
|
413
|
+
const spawnOptions = buildFinalChildPiSpawnOptions(input.cwd, mergedEnv, builtEnv, input.model);
|
|
414
|
+
const child = spawn(spawnSpec.command, spawnSpec.args, spawnOptions);
|
|
384
415
|
if (child.pid) {
|
|
385
416
|
registerActiveChild(child.pid, child);
|
|
386
417
|
input.onSpawn?.(child.pid);
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* crew-broker-child.ts — Child-side broker bootstrap.
|
|
3
|
+
*
|
|
4
|
+
* Runs INSIDE a child pi worker (via prompt-runtime). When the parent injected
|
|
5
|
+
* broker credentials (`PI_CREW_BROKER_SOCKET` + `PI_CREW_BROKER_TOKEN` +
|
|
6
|
+
* `PI_CREW_BROKER_RUN_ID` + `PI_CREW_BROKER_TASK_ID`), it constructs a
|
|
7
|
+
* `CrewBrokerClient`, establishes the persistent connection, and routes
|
|
8
|
+
* pushed `mailbox.message` steer frames to the caller's `onSteer` handler
|
|
9
|
+
* (which sanitizes + delivers via `pi.sendMessage`).
|
|
10
|
+
*
|
|
11
|
+
* If any credential is absent (the default — flag off / not wired), this is a
|
|
12
|
+
* no-op: the child keeps using the file-poll steering path. The broker socket
|
|
13
|
+
* is a latency accelerator layered ON TOP of the durable file path, never a
|
|
14
|
+
* replacement — so a connect failure is invisible to the worker.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import type * as net from "node:net";
|
|
18
|
+
import { type BrokerEventFrame, CrewBrokerClient, type CrewBrokerClientOptions } from "./crew-broker-client.ts";
|
|
19
|
+
|
|
20
|
+
export interface ChildBrokerClientHandle {
|
|
21
|
+
/** True once the client has completed the handshake. */
|
|
22
|
+
readonly active: boolean;
|
|
23
|
+
/** Tear down the connection. Idempotent. */
|
|
24
|
+
close(): Promise<void>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface StartChildBrokerClientOptions {
|
|
28
|
+
/** Env source (defaults to process.env). */
|
|
29
|
+
env?: NodeJS.ProcessEnv;
|
|
30
|
+
/** Invoked with the raw (untrusted) steer body and optional message id for
|
|
31
|
+
* every pushed `mailbox.message` whose kind === "steer". The caller MUST
|
|
32
|
+
* sanitize before delivering to the agent and may use `id` for dedup. */
|
|
33
|
+
onSteer?: (message: string, id?: string) => void;
|
|
34
|
+
/** Optional observer for every received event frame (diagnostics/tests). */
|
|
35
|
+
onEvent?: (event: BrokerEventFrame) => void;
|
|
36
|
+
/** Test seam: override the net module. */
|
|
37
|
+
netModule?: typeof net;
|
|
38
|
+
/** Test seam: override client construction. */
|
|
39
|
+
clientFactory?: (opts: CrewBrokerClientOptions) => CrewBrokerClient;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const NOOP_HANDLE: ChildBrokerClientHandle = {
|
|
43
|
+
active: false,
|
|
44
|
+
close: async () => {},
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export function startChildBrokerClient(options: StartChildBrokerClientOptions = {}): ChildBrokerClientHandle {
|
|
48
|
+
const env = options.env ?? process.env;
|
|
49
|
+
const socketPath = env.PI_CREW_BROKER_SOCKET;
|
|
50
|
+
const token = env.PI_CREW_BROKER_TOKEN;
|
|
51
|
+
const runId = env.PI_CREW_BROKER_RUN_ID;
|
|
52
|
+
const taskId = env.PI_CREW_BROKER_TASK_ID;
|
|
53
|
+
if (!socketPath || !token || !runId || !taskId) {
|
|
54
|
+
return NOOP_HANDLE;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const factory = options.clientFactory ?? ((o: CrewBrokerClientOptions) => new CrewBrokerClient(o));
|
|
58
|
+
const client = factory({
|
|
59
|
+
runId,
|
|
60
|
+
taskId,
|
|
61
|
+
socketPath,
|
|
62
|
+
token,
|
|
63
|
+
netModule: options.netModule,
|
|
64
|
+
onEvent: (ev) => {
|
|
65
|
+
options.onEvent?.(ev);
|
|
66
|
+
if (ev.event === "mailbox.message" && options.onSteer) {
|
|
67
|
+
const data = ev.data as { id?: unknown; kind?: unknown; body?: unknown } | undefined;
|
|
68
|
+
if (data && data.kind === "steer" && typeof data.body === "string") {
|
|
69
|
+
options.onSteer(data.body, typeof data.id === "string" ? data.id : undefined);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
// Establish the persistent connection so the broker can push events. This
|
|
76
|
+
// is fire-and-forget: on any failure the client becomes sticky-fallback and
|
|
77
|
+
// the file-poll steering path remains fully functional.
|
|
78
|
+
void client.reconnect().catch(() => {});
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
get active() {
|
|
82
|
+
return client.mode === "connected";
|
|
83
|
+
},
|
|
84
|
+
close: async () => {
|
|
85
|
+
await client.close();
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
}
|