pi-crew 0.10.3 → 0.10.4
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 +2 -1
- package/README.md +5 -1
- package/dist/index.mjs +10685 -6882
- package/docs/architecture.md +4 -4
- package/docs/commands-reference.md +3 -0
- package/docs/publishing.md +15 -3
- package/install.mjs +90 -39
- package/package.json +8 -3
- package/scripts/README.md +4 -3
- package/skills/real-test-pi-crew/REPORT-TEMPLATE.md +1 -0
- package/skills/real-test-pi-crew/SKILL.md +153 -6
- package/src/config/migration-validator.ts +113 -0
- package/src/extension/cross-extension-rpc.ts +3 -7
- package/src/extension/register.ts +13 -0
- package/src/extension/registration/observability.ts +3 -7
- package/src/extension/registration/subagent-tools.ts +3 -7
- package/src/extension/registration/team-tool.ts +3 -7
- package/src/extension/registration/ui.ts +3 -8
- package/src/extension/registration/viewers.ts +3 -10
- package/src/extension/team-manager-command.ts +3 -7
- package/src/extension/team-tool/api/agent-control.ts +17 -10
- package/src/extension/team-tool/api/heartbeat.ts +4 -3
- package/src/extension/team-tool/api/mailbox.ts +33 -20
- package/src/extension/team-tool/api/plan-approval.ts +5 -5
- package/src/extension/team-tool/api/task-claims.ts +8 -7
- package/src/extension/team-tool/cancel.ts +6 -0
- package/src/extension/team-tool/handle-settings.ts +4 -1
- package/src/extension/team-tool/run.ts +3 -7
- package/src/extension/team-tool/status.ts +5 -0
- package/src/extension/team-tool.ts +6 -14
- package/src/hooks/registry.ts +3 -0
- package/src/prompt/scratchpad-lifecycle.ts +3 -3
- package/src/runtime/background-runner.ts +30 -35
- package/src/runtime/broker/crew-broker.ts +112 -441
- package/src/runtime/broker/delegate/delegate-event.ts +37 -0
- package/src/runtime/broker/mailbox-observer/mailbox-fanout.ts +59 -0
- package/src/runtime/broker/protocol/connection-state.ts +103 -0
- package/src/runtime/broker/protocol/events-replay.ts +68 -0
- package/src/runtime/broker/protocol/manifest-loader.ts +20 -0
- package/src/runtime/broker/protocol/msg-inbox.ts +69 -0
- package/src/runtime/broker/protocol/request-parsers.ts +175 -0
- package/src/runtime/broker/protocol/wait-auth.ts +46 -0
- package/src/runtime/child-pi/child-pi.ts +15 -0
- package/src/runtime/finalize-run.ts +15 -7
- package/src/runtime/foreground-control.ts +19 -6
- package/src/runtime/goal-workflow/dynamic-workflow-context.ts +6 -0
- package/src/runtime/goal-workflow/dynamic-workflow-runner.ts +3 -0
- package/src/runtime/goal-workflow/goal-loop-runner.ts +29 -27
- package/src/runtime/goal-workflow/goal-state-store.ts +3 -0
- package/src/runtime/heartbeat/heartbeat-watcher.ts +3 -3
- package/src/runtime/model/pi-args.ts +6 -1
- package/src/runtime/plan-replan.ts +3 -0
- package/src/runtime/stale-reconciler.ts +28 -3
- package/src/runtime/supervisor-contact.ts +3 -0
- package/src/runtime/task-runner/child-executor.ts +33 -0
- package/src/runtime/team-runner.ts +3 -3
- package/src/state/stores/ownership-map.ts +5 -4
- package/src/state/stores/plan-store.ts +12 -0
- package/src/state/stores/state-store.ts +5 -0
- package/src/ui/powerbar-publisher.ts +3 -7
- package/src/ui/run-action-dispatcher.ts +7 -10
- package/src/ui/settings-overlay.ts +4 -1
|
@@ -23,14 +23,10 @@ import { listRecentRuns } from "./run-index.ts";
|
|
|
23
23
|
import type { PiTeamsToolResult } from "./tool-result.ts";
|
|
24
24
|
|
|
25
25
|
type ExecuteTeamRunFn = typeof _executeTeamRunFn;
|
|
26
|
-
let _cachedExecuteTeamRun: ExecuteTeamRunFn | undefined;
|
|
27
26
|
async function executeTeamRun(...args: Parameters<ExecuteTeamRunFn>): Promise<Awaited<ReturnType<ExecuteTeamRunFn>>> {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
_cachedExecuteTeamRun = mod.executeTeamRun;
|
|
32
|
-
}
|
|
33
|
-
return _cachedExecuteTeamRun(...args);
|
|
27
|
+
// LAZY: heavy runtime — defer 1.4s import cost until team run actually executes.
|
|
28
|
+
const mod = await import("../runtime/team-runner.ts");
|
|
29
|
+
return mod.executeTeamRun(...args);
|
|
34
30
|
}
|
|
35
31
|
|
|
36
32
|
import { directTeamAndWorkflowFromRun } from "../runtime/direct-run.ts";
|
|
@@ -44,14 +40,10 @@ import { buildParentContext, formatScoped, result, type TeamContext } from "./te
|
|
|
44
40
|
import type { handleRun as _handleRunFn } from "./team-tool/run.ts";
|
|
45
41
|
|
|
46
42
|
type HandleRunFn = typeof _handleRunFn;
|
|
47
|
-
let _cachedHandleRun: HandleRunFn | undefined;
|
|
48
43
|
async function handleRun(...args: Parameters<HandleRunFn>): Promise<Awaited<ReturnType<HandleRunFn>>> {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
_cachedHandleRun = mod.handleRun;
|
|
53
|
-
}
|
|
54
|
-
return _cachedHandleRun(...args);
|
|
44
|
+
// LAZY: run.ts pulls in spawnBackgroundTeamRun + resolveCrewRuntime; also avoids jiti import race in child-process contexts.
|
|
45
|
+
const mod = await import("./team-tool/run.ts");
|
|
46
|
+
return mod.handleRun(...args);
|
|
55
47
|
}
|
|
56
48
|
|
|
57
49
|
import { t } from "../i18n.ts";
|
package/src/hooks/registry.ts
CHANGED
|
@@ -179,6 +179,9 @@ export async function executeHook(name: HookName, ctx: HookContext): Promise<Hoo
|
|
|
179
179
|
}
|
|
180
180
|
|
|
181
181
|
export function appendHookEvent(manifest: TeamRunManifest, report: HookExecutionReport): void {
|
|
182
|
+
// REVIEW FIX (2026-09-10): reverted M2b buffered conversion — hook.executed
|
|
183
|
+
// events are read back synchronously (recovery-hooks tests, hooks audit
|
|
184
|
+
// display) and are low-frequency (per hook execution).
|
|
182
185
|
appendEvent(manifest.eventsPath, {
|
|
183
186
|
type: "hook.executed",
|
|
184
187
|
runId: manifest.runId,
|
|
@@ -32,7 +32,7 @@ import * as path from "node:path";
|
|
|
32
32
|
import { type Static, Type } from "@sinclair/typebox";
|
|
33
33
|
import { defineTool, type ExtensionAPI, type ToolDefinition } from "../extension/pi-api.ts";
|
|
34
34
|
import { EngineManager, type ExecuteResult } from "../runtime/scratchpad/engine.ts";
|
|
35
|
-
import { appendEventFireAndForget } from "../state/event-log/event-log.ts";
|
|
35
|
+
import { appendEventBuffered, appendEventFireAndForget } from "../state/event-log/event-log.ts";
|
|
36
36
|
// D5/MAJOR-S1: PI_CREW_PARENT_PID + PI_CREW_GUEST build the guest's zombie-
|
|
37
37
|
// backstop env (PI_CREW_KIND_ENV is already exported above).
|
|
38
38
|
import { type ArtifactWriteOptions, writeArtifact } from "../state/stores/artifact-store.ts";
|
|
@@ -84,12 +84,12 @@ function emitScratchpadMetric(
|
|
|
84
84
|
// never break the cell. The real appendEventFireAndForget already catches
|
|
85
85
|
// async; this guard also absorbs a sync throw from a bad writer.
|
|
86
86
|
try {
|
|
87
|
-
|
|
87
|
+
appendEventBuffered(eventsPath, {
|
|
88
88
|
type,
|
|
89
89
|
runId,
|
|
90
90
|
taskId: env[PI_CREW_TASK_ID_ENV],
|
|
91
91
|
data,
|
|
92
|
-
});
|
|
92
|
+
}).catch((e) => logInternalError("scratchpad_lifecycle.buffered", e, `type=${type}`));
|
|
93
93
|
} catch {
|
|
94
94
|
// Metric is best-effort — drop the event, never the cell.
|
|
95
95
|
}
|
|
@@ -5,7 +5,7 @@ import { loadConfig } from "../config/config.ts";
|
|
|
5
5
|
import { getCrewEnv } from "../config/env-vars.ts";
|
|
6
6
|
import { atomicWriteFile } from "../state/atomic-write.ts";
|
|
7
7
|
import { withRunLockSync } from "../state/coordination/locks.ts";
|
|
8
|
-
import { appendEvent, appendEventFireAndForget } from "../state/event-log/event-log.ts";
|
|
8
|
+
import { appendEvent, appendEventBuffered, appendEventFireAndForget } from "../state/event-log/event-log.ts";
|
|
9
9
|
import { createRunPaths, loadRunManifestById, saveRunManifestAsync, updateRunStatus } from "../state/stores/state-store.ts";
|
|
10
10
|
import type { TeamRunManifest, TeamTaskState } from "../state/types.ts";
|
|
11
11
|
import { allTeams, discoverTeams } from "../teams/discover-teams.ts";
|
|
@@ -18,8 +18,6 @@ import { allWorkflows, discoverWorkflows } from "../workflows/discover-workflows
|
|
|
18
18
|
import { primePeerDep } from "./peer-dep.ts";
|
|
19
19
|
import type { executeTeamRun as ExecuteTeamRunFn } from "./team-runner.ts";
|
|
20
20
|
|
|
21
|
-
let _cachedExecuteTeamRun: typeof ExecuteTeamRunFn | undefined;
|
|
22
|
-
|
|
23
21
|
/** Maximum runtime for a single background run before the watchdog force-aborts
|
|
24
22
|
* it. Prevents zombie background-runner processes when a team run hangs forever
|
|
25
23
|
* (e.g. a hung child Pi process, a stuck lock, or a test that spawns a run
|
|
@@ -33,16 +31,13 @@ const MAX_BACKGROUND_RUN_MS = (() => {
|
|
|
33
31
|
return Number.isFinite(env) && env > 0 ? env : 2 * 60 * 60 * 1000;
|
|
34
32
|
})();
|
|
35
33
|
async function executeTeamRun(...args: Parameters<typeof ExecuteTeamRunFn>): Promise<Awaited<ReturnType<typeof ExecuteTeamRunFn>>> {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
_cachedExecuteTeamRun = mod.executeTeamRun;
|
|
44
|
-
}
|
|
45
|
-
return _cachedExecuteTeamRun(...args);
|
|
34
|
+
// FIX (split-scope install): prime the ESM peer dep BEFORE team-runner is
|
|
35
|
+
// imported, so its transitive skill-instructions.ts can read getAgentDir()
|
|
36
|
+
// from the primed cache instead of crashing on `Cannot find module`.
|
|
37
|
+
await primePeerDep().catch(() => undefined);
|
|
38
|
+
// LAZY: avoid pulling team-runner into background-runner at module load time.
|
|
39
|
+
const mod = await import("./team-runner.ts");
|
|
40
|
+
return mod.executeTeamRun(...args);
|
|
46
41
|
}
|
|
47
42
|
|
|
48
43
|
import { logInternalError } from "../utils/internal-error.ts";
|
|
@@ -256,11 +251,11 @@ export function startInterruptGuard(
|
|
|
256
251
|
/* best-effort ack — interruptHandled gate prevents re-fire */
|
|
257
252
|
}
|
|
258
253
|
|
|
259
|
-
|
|
254
|
+
appendEventBuffered(manifest.eventsPath, {
|
|
260
255
|
type: "async.interrupt_detected",
|
|
261
256
|
runId: manifest.runId,
|
|
262
257
|
message: "Background runner detected foreground interrupt — killing child processes and exiting.",
|
|
263
|
-
});
|
|
258
|
+
}).catch((e) => logInternalError("background-runner.buffered", e, "type=async.interrupt_detected"));
|
|
264
259
|
// FIX: Terminate ALL child-pi processes IMMEDIATELY before exiting.
|
|
265
260
|
// Previously this was missing, causing orphaned child processes to run forever
|
|
266
261
|
// after the background-runner exited. terminateActiveChildPiProcesses sends
|
|
@@ -311,7 +306,7 @@ function setupUnhandledRejectionGuard(
|
|
|
311
306
|
console.error("[background-runner] Stack:", reason instanceof Error ? reason.stack : "N/A");
|
|
312
307
|
try {
|
|
313
308
|
if (state.eventsPath && state.runId) {
|
|
314
|
-
|
|
309
|
+
appendEventBuffered(state.eventsPath, {
|
|
315
310
|
type: "async.failed",
|
|
316
311
|
runId: state.runId,
|
|
317
312
|
message: `Unhandled rejection: ${message}`,
|
|
@@ -320,7 +315,7 @@ function setupUnhandledRejectionGuard(
|
|
|
320
315
|
stack: reason instanceof Error ? reason.stack : undefined,
|
|
321
316
|
handled: false,
|
|
322
317
|
},
|
|
323
|
-
});
|
|
318
|
+
}).catch((e) => logInternalError("background-runner.buffered", e, "type=async.failed source=unhandledRejection"));
|
|
324
319
|
}
|
|
325
320
|
} catch (appendErr) {
|
|
326
321
|
console.error("[background-runner] Failed to write async.failed event:", appendErr);
|
|
@@ -377,12 +372,12 @@ function runCleanup(
|
|
|
377
372
|
console.log(`[background-runner] runCleanup: unregisterWorker error: ${errorMessage(error)}`);
|
|
378
373
|
if (eventsPath) {
|
|
379
374
|
try {
|
|
380
|
-
|
|
375
|
+
appendEventBuffered(eventsPath, {
|
|
381
376
|
type: "background.unregister_worker_failed",
|
|
382
377
|
runId: argValue("--run-id") ?? "unknown",
|
|
383
378
|
message: `unregisterWorker failed: ${errorMessage(error)}`,
|
|
384
379
|
data: { pid: process.pid },
|
|
385
|
-
});
|
|
380
|
+
}).catch((e) => logInternalError("background-runner.buffered", e, "type=background.unregister_worker_failed"));
|
|
386
381
|
} catch {
|
|
387
382
|
/* best-effort */
|
|
388
383
|
}
|
|
@@ -538,12 +533,12 @@ async function main(): Promise<void> {
|
|
|
538
533
|
const runId = argValue("--run-id");
|
|
539
534
|
if (runId && manifest.eventsPath) {
|
|
540
535
|
try {
|
|
541
|
-
|
|
536
|
+
appendEventBuffered(manifest.eventsPath, {
|
|
542
537
|
type: "async.sigterm_received_graceful_shutdown",
|
|
543
538
|
runId,
|
|
544
539
|
message: `SIGTERM received, graceful shutdown via abort pid=${process.pid}`,
|
|
545
540
|
data: { pid: process.pid, ppid: process.ppid },
|
|
546
|
-
});
|
|
541
|
+
}).catch((e) => logInternalError("background-runner.buffered", e, "type=async.sigterm_received_graceful_shutdown"));
|
|
547
542
|
} catch {
|
|
548
543
|
/* best-effort */
|
|
549
544
|
}
|
|
@@ -596,12 +591,12 @@ async function main(): Promise<void> {
|
|
|
596
591
|
const codeStr = code === undefined ? "<none>" : String(code);
|
|
597
592
|
if (runId2 && manifest.eventsPath) {
|
|
598
593
|
try {
|
|
599
|
-
|
|
594
|
+
appendEventBuffered(manifest.eventsPath, {
|
|
600
595
|
type: "async.exit",
|
|
601
596
|
runId: runId2,
|
|
602
597
|
message: `Background runner exit(${codeStr}) pid=${process.pid}`,
|
|
603
598
|
data: { code, pid: process.pid },
|
|
604
|
-
});
|
|
599
|
+
}).catch((e) => logInternalError("background-runner.buffered", e, "type=async.exit"));
|
|
605
600
|
} catch {
|
|
606
601
|
/* best-effort */
|
|
607
602
|
}
|
|
@@ -635,11 +630,11 @@ async function main(): Promise<void> {
|
|
|
635
630
|
// NOTE: intentionally no unref() — the guard keeps the event loop alive
|
|
636
631
|
// to prevent premature worker exit. See parent-guard.ts:86 for rationale.
|
|
637
632
|
|
|
638
|
-
|
|
633
|
+
appendEventBuffered(manifest.eventsPath, {
|
|
639
634
|
type: "async.started",
|
|
640
635
|
runId: manifest.runId,
|
|
641
636
|
data: { pid: process.pid },
|
|
642
|
-
});
|
|
637
|
+
}).catch((e) => logInternalError("background-runner.buffered", e, "type=async.started"));
|
|
643
638
|
debugLog(`[background-runner] async.started written, pid=${process.pid}`);
|
|
644
639
|
writeAsyncStartMarker(manifest, {
|
|
645
640
|
pid: process.pid,
|
|
@@ -663,12 +658,12 @@ async function main(): Promise<void> {
|
|
|
663
658
|
const watchdogTimer = setTimeout(() => {
|
|
664
659
|
console.error(`[background-runner] WATCHDOG: run ${runId} exceeded ${MAX_BACKGROUND_RUN_MS}ms — aborting (zombie prevention)`);
|
|
665
660
|
try {
|
|
666
|
-
|
|
661
|
+
appendEventBuffered(manifest.eventsPath, {
|
|
667
662
|
type: "async.watchdog_fired",
|
|
668
663
|
runId,
|
|
669
664
|
message: `Run exceeded ${MAX_BACKGROUND_RUN_MS}ms and was force-aborted to prevent a zombie background-runner process.`,
|
|
670
665
|
data: { maxRunMs: MAX_BACKGROUND_RUN_MS },
|
|
671
|
-
});
|
|
666
|
+
}).catch((e) => logInternalError("background-runner.buffered", e, "type=async.watchdog_fired"));
|
|
672
667
|
} catch {
|
|
673
668
|
/* best-effort event log */
|
|
674
669
|
}
|
|
@@ -825,12 +820,12 @@ async function main(): Promise<void> {
|
|
|
825
820
|
updatedAt: new Date().toISOString(),
|
|
826
821
|
};
|
|
827
822
|
await saveRunManifestAsync(manifest);
|
|
828
|
-
|
|
823
|
+
appendEventBuffered(manifest.eventsPath, {
|
|
829
824
|
type: "runtime.resolved",
|
|
830
825
|
runId: manifest.runId,
|
|
831
826
|
message: `Runtime resolved: ${runtime.kind} safety=${runtime.safety}`,
|
|
832
827
|
data: { runtimeResolution, async: true },
|
|
833
|
-
});
|
|
828
|
+
}).catch((e) => logInternalError("background-runner.buffered", e, "type=runtime.resolved"));
|
|
834
829
|
if (runtime.safety === "blocked")
|
|
835
830
|
throw new Error(runtime.reason ?? "Child worker execution is disabled; refusing to create no-op scaffold subagents.");
|
|
836
831
|
const executeWorkers = runtime.kind !== "scaffold";
|
|
@@ -890,11 +885,11 @@ async function main(): Promise<void> {
|
|
|
890
885
|
} // close if (!earlyResult) — team-run setup+execute done; earlyResult path skips to here
|
|
891
886
|
manifest = result!.manifest;
|
|
892
887
|
tasks = result!.tasks;
|
|
893
|
-
|
|
888
|
+
appendEventBuffered(manifest.eventsPath, {
|
|
894
889
|
type: "async.completed",
|
|
895
890
|
runId: manifest.runId,
|
|
896
891
|
data: { status: manifest.status, tasks: tasks.length },
|
|
897
|
-
});
|
|
892
|
+
}).catch((e) => logInternalError("background-runner.buffered", e, "type=async.completed"));
|
|
898
893
|
console.log(`[background-runner] async.completed written, status=${manifest.status}`);
|
|
899
894
|
if (manifest.status === "failed" || manifest.status === "cancelled" || manifest.status === "blocked") process.exitCode = 1;
|
|
900
895
|
} catch (error) {
|
|
@@ -915,11 +910,11 @@ async function main(): Promise<void> {
|
|
|
915
910
|
const fresh = loaded?.manifest ?? manifest;
|
|
916
911
|
if (fresh) {
|
|
917
912
|
manifest = updateRunStatus(fresh, "failed", message);
|
|
918
|
-
|
|
913
|
+
appendEventBuffered(manifest.eventsPath, {
|
|
919
914
|
type: "async.failed",
|
|
920
915
|
runId: manifest.runId,
|
|
921
916
|
message,
|
|
922
|
-
});
|
|
917
|
+
}).catch((e) => logInternalError("background-runner.buffered", e, "type=async.failed source=updateFailed"));
|
|
923
918
|
}
|
|
924
919
|
return fresh;
|
|
925
920
|
},
|
|
@@ -988,12 +983,12 @@ try {
|
|
|
988
983
|
const mRunId = argValue("--run-id");
|
|
989
984
|
if (mCwd && mRunId) {
|
|
990
985
|
const mEventsPath = createRunPaths(mCwd, mRunId).eventsPath;
|
|
991
|
-
|
|
986
|
+
appendEventBuffered(mEventsPath, {
|
|
992
987
|
type: "async.failed",
|
|
993
988
|
runId: mRunId,
|
|
994
989
|
message: errorMessage(err),
|
|
995
990
|
data: { stack: err instanceof Error ? err.stack : undefined },
|
|
996
|
-
});
|
|
991
|
+
}).catch((e) => logInternalError("background-runner.buffered", e, "type=async.failed source=catch"));
|
|
997
992
|
}
|
|
998
993
|
} catch {
|
|
999
994
|
/* best-effort — don't let event-write failure mask the original error */
|