pi-crew 0.9.56 → 0.9.57
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 +1 -1
- package/CHANGELOG.md +29 -0
- package/README.md +53 -5
- package/dist/index.mjs +5350 -4350
- package/docs/README.md +54 -0
- package/package.json +12 -12
- package/skills/real-test-pi-crew/SKILL.md +93 -3
- package/src/agents/discover-agents.ts +68 -10
- package/src/benchmark/feedback-loop.ts +1 -1
- package/src/config/config.ts +2 -1
- package/src/config/types.ts +12 -0
- package/src/extension/action-suggestions.ts +16 -63
- package/src/extension/async-notifier.ts +3 -3
- package/src/extension/command-completions.ts +2 -2
- package/src/extension/crew-cleanup.ts +3 -3
- package/src/extension/crew-vibes/footer.ts +4 -2
- package/src/extension/cross-extension-rpc.ts +10 -5
- package/src/extension/knowledge-injection.ts +57 -10
- package/src/extension/register.ts +2 -2
- package/src/extension/registration/artifact-cleanup.ts +41 -3
- package/src/extension/registration/commands.ts +4 -4
- package/src/extension/registration/context-builder.ts +10 -10
- package/src/extension/registration/crash-recovery-cache.ts +3 -3
- package/src/extension/registration/foreground-run-controller.ts +1 -1
- package/src/extension/registration/lazy-configurers.ts +2 -2
- package/src/extension/registration/lifecycle-handlers.ts +12 -12
- package/src/extension/registration/lifecycle.ts +2 -2
- package/src/extension/registration/observability.ts +2 -2
- package/src/extension/registration/registration-types.ts +3 -3
- package/src/extension/registration/runtime-cleanup.ts +1 -1
- package/src/extension/registration/subagent-helpers.ts +2 -2
- package/src/extension/registration/subagent-manager-setup.ts +2 -2
- package/src/extension/registration/subagent-tools.ts +21 -16
- package/src/extension/registration/team-tool.ts +108 -17
- package/src/extension/registration/viewers.ts +3 -4
- package/src/extension/registration/wire-cross-extension.ts +7 -7
- package/src/extension/rpc-hmac.ts +3 -2
- package/src/extension/run-bundle-schema.ts +13 -4
- package/src/extension/run-export.ts +2 -2
- package/src/extension/run-index.ts +2 -3
- package/src/extension/run-maintenance.ts +57 -1
- package/src/extension/team-manager-command.ts +3 -1
- package/src/extension/team-tool/anchor.ts +8 -2
- package/src/extension/team-tool/api.ts +117 -39
- package/src/extension/team-tool/cancel.ts +23 -8
- package/src/extension/team-tool/chain-executor.ts +1 -1
- package/src/extension/team-tool/dispatch/automate.ts +17 -2
- package/src/extension/team-tool/dispatch/control.ts +17 -2
- package/src/extension/team-tool/dispatch/manage.ts +34 -2
- package/src/extension/team-tool/dispatch/run.ts +17 -2
- package/src/extension/team-tool/dispatch/status.ts +37 -5
- package/src/extension/team-tool/doctor.ts +2 -2
- package/src/extension/team-tool/explain.ts +1 -1
- package/src/extension/team-tool/goal-wrap.ts +5 -5
- package/src/extension/team-tool/goal.ts +30 -9
- package/src/extension/team-tool/handle-schedule.ts +37 -19
- package/src/extension/team-tool/inspect.ts +21 -5
- package/src/extension/team-tool/lifecycle-actions.ts +53 -10
- package/src/extension/team-tool/parallel-dispatch.ts +14 -5
- package/src/extension/team-tool/param-error.ts +24 -0
- package/src/extension/team-tool/respond.ts +20 -6
- package/src/extension/team-tool/run.ts +27 -17
- package/src/extension/team-tool/status.ts +11 -5
- package/src/extension/team-tool.ts +26 -19
- package/src/hooks/registry.ts +1 -1
- package/src/i18n.ts +22 -0
- package/src/observability/event-bus.ts +1 -1
- package/src/prompt/prompt-runtime.ts +2 -2
- package/src/runtime/README.md +35 -0
- package/src/runtime/agent-control.ts +1 -1
- package/src/runtime/anchor-manager.ts +0 -2
- package/src/runtime/async-runner.ts +1 -1
- package/src/runtime/attention-events.ts +1 -1
- package/src/runtime/auto-summarize.ts +0 -2
- package/src/runtime/background-runner.ts +103 -30
- package/src/runtime/{crew-broker-child.ts → broker/crew-broker-child.ts} +2 -2
- package/src/runtime/{crew-broker-client.ts → broker/crew-broker-client.ts} +4 -7
- package/src/runtime/{crew-broker.ts → broker/crew-broker.ts} +13 -21
- package/src/runtime/chain-runner.ts +50 -5
- package/src/runtime/{child-pi-constants.ts → child-pi/child-pi-constants.ts} +1 -1
- package/src/runtime/{child-pi-kill.ts → child-pi/child-pi-kill.ts} +3 -3
- package/src/runtime/{child-pi-spawn.ts → child-pi/child-pi-spawn.ts} +28 -12
- package/src/runtime/{child-pi-steering.ts → child-pi/child-pi-steering.ts} +1 -1
- package/src/runtime/{child-pi-streams.ts → child-pi/child-pi-streams.ts} +11 -4
- package/src/runtime/{child-pi-transcript.ts → child-pi/child-pi-transcript.ts} +6 -6
- package/src/runtime/{child-pi.ts → child-pi/child-pi.ts} +28 -72
- package/src/runtime/{compact-stages → compaction/compact-stages}/bounded-tail.ts +1 -1
- package/src/runtime/{compact-stages → compaction/compact-stages}/index.ts +2 -2
- package/src/runtime/{compaction-summary.ts → compaction/compaction-summary.ts} +3 -3
- package/src/runtime/{tool-output-pruner.ts → compaction/tool-output-pruner.ts} +1 -1
- package/src/runtime/custom-tools/irc-tool.ts +2 -2
- package/src/runtime/delivery-coordinator.ts +0 -3
- package/src/runtime/diagnostic-export.ts +3 -3
- package/src/runtime/foreground-control.ts +1 -1
- package/src/runtime/foreground-watchdog.ts +1 -1
- package/src/runtime/{adaptive-plan.ts → goal-workflow/adaptive-plan.ts} +8 -8
- package/src/runtime/{dynamic-workflow-context.ts → goal-workflow/dynamic-workflow-context.ts} +16 -16
- package/src/runtime/{dynamic-workflow-runner.ts → goal-workflow/dynamic-workflow-runner.ts} +10 -10
- package/src/runtime/{goal-achievement.ts → goal-workflow/goal-achievement.ts} +3 -3
- package/src/runtime/{goal-evaluator.ts → goal-workflow/goal-evaluator.ts} +8 -8
- package/src/runtime/{goal-loop-runner.ts → goal-workflow/goal-loop-runner.ts} +27 -25
- package/src/runtime/{goal-state-store.ts → goal-workflow/goal-state-store.ts} +8 -8
- package/src/runtime/{plan-templates.ts → goal-workflow/plan-templates.ts} +1 -1
- package/src/runtime/group-join.ts +3 -3
- package/src/runtime/handoff-manager.ts +0 -2
- package/src/runtime/{heartbeat-watcher.ts → heartbeat/heartbeat-watcher.ts} +7 -7
- package/src/runtime/iteration-hooks.ts +1 -1
- package/src/runtime/{live-agent-control.ts → live-session/live-agent-control.ts} +2 -2
- package/src/runtime/{live-agent-manager.ts → live-session/live-agent-manager.ts} +4 -4
- package/src/runtime/{live-session-runtime.ts → live-session/live-session-runtime.ts} +39 -41
- package/src/runtime/manifest-cache.ts +1 -1
- package/src/runtime/{model-fallback.ts → model/model-fallback.ts} +1 -1
- package/src/runtime/{pi-args.ts → model/pi-args.ts} +20 -5
- package/src/runtime/model/provider-extensions.ts +116 -0
- package/src/runtime/{runtime-policy.ts → model/runtime-policy.ts} +2 -2
- package/src/runtime/{runtime-resolver.ts → model/runtime-resolver.ts} +3 -3
- package/src/runtime/{runtime-warmup.ts → model/runtime-warmup.ts} +8 -8
- package/src/runtime/orphan-worker-registry.ts +1 -1
- package/src/runtime/{progress-tracker.ts → output/progress-tracker.ts} +1 -1
- package/src/runtime/{result-extractor.ts → output/result-extractor.ts} +4 -4
- package/src/runtime/{sidechain-output.ts → output/sidechain-output.ts} +2 -2
- package/src/runtime/{stream-preview.ts → output/stream-preview.ts} +1 -1
- package/src/runtime/{streaming-output.ts → output/streaming-output.ts} +2 -2
- package/src/runtime/{tool-progress.ts → output/tool-progress.ts} +1 -1
- package/src/runtime/parent-guard.ts +30 -10
- package/src/runtime/pipeline-runner.ts +2 -2
- package/src/runtime/policy-engine.ts +2 -2
- package/src/runtime/{cancellation.ts → process/cancellation.ts} +1 -1
- package/src/runtime/{post-exit-stdio-guard.ts → process/post-exit-stdio-guard.ts} +1 -1
- package/src/runtime/{process-lifecycle.ts → process/process-lifecycle.ts} +3 -3
- package/src/runtime/{checkpoint.ts → recovery/checkpoint.ts} +4 -4
- package/src/runtime/{crash-recovery.ts → recovery/crash-recovery.ts} +93 -21
- package/src/runtime/{overflow-recovery.ts → recovery/overflow-recovery.ts} +1 -1
- package/src/runtime/{recovery-recipes.ts → recovery/recovery-recipes.ts} +1 -1
- package/src/runtime/{retry-executor.ts → recovery/retry-executor.ts} +2 -2
- package/src/runtime/{retry-runner.ts → recovery/retry-runner.ts} +2 -8
- package/src/runtime/run-tracker.ts +1 -1
- package/src/runtime/run-worker.ts +3 -3
- package/src/runtime/{coalesce-tasks.ts → scheduling/coalesce-tasks.ts} +3 -3
- package/src/runtime/{concurrency.ts → scheduling/concurrency.ts} +1 -1
- package/src/runtime/{parallel-research.ts → scheduling/parallel-research.ts} +4 -2
- package/src/runtime/{run-coalesced-task-group.ts → scheduling/run-coalesced-task-group.ts} +95 -36
- package/src/runtime/{task-graph-scheduler.ts → scheduling/task-graph-scheduler.ts} +1 -1
- package/src/runtime/session-resources.ts +1 -1
- package/src/runtime/settings-store.ts +1 -1
- package/src/runtime/single-agent-compose.ts +1 -0
- package/src/runtime/stale-reconciler.ts +1 -1
- package/src/runtime/subagent-manager.ts +1 -1
- package/src/runtime/supervisor-contact.ts +1 -1
- package/src/runtime/task-output-context.ts +7 -5
- package/src/runtime/task-runner/child-executor.ts +62 -22
- package/src/runtime/task-runner/live-executor.ts +8 -8
- package/src/runtime/task-runner/post-execution.ts +12 -12
- package/src/runtime/task-runner/pre-execution.ts +6 -6
- package/src/runtime/task-runner/progress.ts +1 -1
- package/src/runtime/task-runner/run-projection.ts +1 -1
- package/src/runtime/task-runner/scaffold-executor.ts +1 -1
- package/src/runtime/task-runner/state-helpers.ts +2 -2
- package/src/runtime/task-runner.ts +4 -4
- package/src/runtime/team-runner.ts +380 -139
- package/src/runtime/{completion-guard.ts → verification/completion-guard.ts} +1 -1
- package/src/runtime/{green-contract.ts → verification/green-contract.ts} +1 -1
- package/src/runtime/{post-checks.ts → verification/post-checks.ts} +4 -4
- package/src/runtime/{verification-gates.ts → verification/verification-gates.ts} +5 -5
- package/src/runtime/{verification-integrity.ts → verification/verification-integrity.ts} +1 -1
- package/src/schema/config-schema.ts +1 -0
- package/src/schema/team-tool-schema.ts +133 -177
- package/src/state/README.md +44 -0
- package/src/state/atomic-write.ts +24 -3
- package/src/state/{locks.ts → coordination/locks.ts} +118 -38
- package/src/state/{mailbox.ts → coordination/mailbox.ts} +14 -8
- package/src/state/{schedule.ts → coordination/schedule.ts} +3 -3
- package/src/state/{task-claims.ts → coordination/task-claims.ts} +1 -1
- package/src/state/decision-ledger.ts +1 -1
- package/src/state/{event-log-rotation.ts → event-log/event-log-rotation.ts} +161 -31
- package/src/state/{event-log.ts → event-log/event-log.ts} +38 -26
- package/src/state/{jsonl-writer.ts → event-log/jsonl-writer.ts} +14 -6
- package/src/state/hook-instinct-bridge.ts +2 -2
- package/src/state/{active-run-registry.ts → stores/active-run-registry.ts} +7 -7
- package/src/state/{artifact-store.ts → stores/artifact-store.ts} +62 -4
- package/src/state/{blob-store.ts → stores/blob-store.ts} +3 -3
- package/src/state/{health-store.ts → stores/health-store.ts} +3 -3
- package/src/state/{instinct-store.ts → stores/instinct-store.ts} +3 -3
- package/src/state/{observation-store.ts → stores/observation-store.ts} +2 -2
- package/src/state/{run-cache.ts → stores/run-cache.ts} +4 -4
- package/src/state/{run-graph.ts → stores/run-graph.ts} +4 -4
- package/src/state/{run-metrics.ts → stores/run-metrics.ts} +3 -3
- package/src/state/{state-store.ts → stores/state-store.ts} +236 -17
- package/src/state/types.ts +27 -3
- package/src/ui/component.ts +55 -0
- package/src/ui/crew-select-list.ts +3 -1
- package/src/ui/dashboard-panes/agents-pane.ts +1 -1
- package/src/ui/dwf-phase-display.ts +1 -1
- package/src/ui/heartbeat-aggregator.ts +1 -1
- package/src/ui/keybinding-map.ts +179 -6
- package/src/ui/layout-primitives.ts +9 -5
- package/src/ui/live-conversation-overlay.ts +1 -1
- package/src/ui/live-run-sidebar.ts +1 -1
- package/src/ui/overlay-stack.ts +148 -0
- package/src/ui/overlays/agent-picker-overlay.ts +1 -1
- package/src/ui/overlays/mailbox-compose-overlay.ts +1 -1
- package/src/ui/overlays/mailbox-detail-overlay.ts +2 -2
- package/src/ui/powerbar-publisher.ts +1 -1
- package/src/ui/render-scheduler.ts +1 -1
- package/src/ui/run-action-dispatcher.ts +3 -3
- package/src/ui/run-dashboard.ts +5 -33
- package/src/ui/run-event-bus.ts +3 -3
- package/src/ui/run-snapshot-cache.ts +37 -18
- package/src/ui/settings-overlay.ts +13 -21
- package/src/ui/snapshot-types.ts +1 -1
- package/src/ui/status-colors.ts +0 -2
- package/src/ui/terminal-status.ts +1 -1
- package/src/ui/theme-adapter.ts +33 -13
- package/src/ui/tool-renderers/index.ts +8 -0
- package/src/ui/transcript-viewer.ts +37 -13
- package/src/ui/widget/index.ts +32 -9
- package/src/ui/widget/widget-formatters.ts +62 -4
- package/src/ui/widget/widget-model.ts +2 -2
- package/src/ui/widget/widget-renderer.ts +1 -1
- package/src/utils/conflict-detect.ts +58 -5
- package/src/utils/ndjson.ts +2 -2
- package/src/utils/socket-path.ts +1 -1
- package/src/workflows/workflow-config.ts +1 -1
- package/src/worktree/cleanup.ts +1 -1
- package/src/worktree/worktree-manager.ts +88 -39
- package/workflows/chain.workflow.md +2 -2
- package/index.bundle.ts +0 -25
- package/scripts/bench-check.mjs +0 -96
- package/scripts/bench-cold-start.mjs +0 -216
- package/scripts/build-bundle.mjs +0 -92
- package/scripts/check-all-skills.ts +0 -294
- package/scripts/check-bundle-size.mjs +0 -38
- package/scripts/check-bundle-staleness.mjs +0 -97
- package/scripts/check-conflict-markers.mjs +0 -91
- package/scripts/check-lazy-imports.mjs +0 -28
- package/scripts/dev-runner.mjs +0 -106
- package/scripts/profile-startup.mjs +0 -125
- package/scripts/release-smoke.mjs +0 -74
- package/scripts/run-bench.mjs +0 -47
- package/scripts/run-real-chain.ts +0 -41
- package/scripts/smoke-team-perf.ts +0 -43
- package/scripts/test-issue-29-crash.ts +0 -111
- package/scripts/test-issue-29-e2e.ts +0 -183
- package/scripts/test-issue-29-real-runtime.ts +0 -330
- package/scripts/test-issue-29-real-tasks.ts +0 -387
- package/scripts/test-issue-29-team-tool.ts +0 -105
- package/scripts/test-runner.mjs +0 -84
- package/scripts/verify-flicker-fix.ts +0 -109
- package/scripts/verify-skill.ts +0 -550
- package/scripts/watch-bundle.mjs +0 -259
- package/scripts/watchdog-harness.ts +0 -119
- package/src/adapters/claude-adapter.ts +0 -23
- package/src/adapters/codex-adapter.ts +0 -21
- package/src/adapters/cursor-adapter.ts +0 -17
- package/src/adapters/export-util.ts +0 -143
- package/src/adapters/index.ts +0 -15
- package/src/adapters/registry.ts +0 -18
- package/src/adapters/types.ts +0 -23
- package/src/runtime/code-summary.ts +0 -292
- package/src/runtime/cross-extension-rpc.ts +0 -143
- package/src/runtime/hidden-handoff.ts +0 -407
- package/src/runtime/prose-compressor.ts +0 -162
- package/src/subagents/async-entry.ts +0 -1
- package/src/subagents/index.ts +0 -3
- package/src/subagents/live/control.ts +0 -1
- package/src/subagents/live/manager.ts +0 -1
- package/src/subagents/live/realtime.ts +0 -1
- package/src/subagents/live/session-runtime.ts +0 -1
- package/src/subagents/manager.ts +0 -1
- package/src/subagents/spawn.ts +0 -1
- package/test-integration-check.ts +0 -118
- /package/src/runtime/{broker-issuer.ts → broker/broker-issuer.ts} +0 -0
- /package/src/runtime/{crew-broker-tokens.ts → broker/crew-broker-tokens.ts} +0 -0
- /package/src/runtime/{child-pi-pool.ts → child-pi/child-pi-pool.ts} +0 -0
- /package/src/runtime/{compact-pipeline.ts → compaction/compact-pipeline.ts} +0 -0
- /package/src/runtime/{compact-stages → compaction/compact-stages}/ansi-strip-stage.ts +0 -0
- /package/src/runtime/{compact-stages → compaction/compact-stages}/blank-collapse-stage.ts +0 -0
- /package/src/runtime/{compact-stages → compaction/compact-stages}/deduplicate-stage.ts +0 -0
- /package/src/runtime/{compact-stages → compaction/compact-stages}/head-snap-stage.ts +0 -0
- /package/src/runtime/{compact-stages → compaction/compact-stages}/tail-capture-stage.ts +0 -0
- /package/src/runtime/{compact-stages → compaction/compact-stages}/truncation-stage.ts +0 -0
- /package/src/runtime/{important-line-classifier.ts → compaction/important-line-classifier.ts} +0 -0
- /package/src/runtime/{heartbeat-gradient.ts → heartbeat/heartbeat-gradient.ts} +0 -0
- /package/src/runtime/{worker-heartbeat.ts → heartbeat/worker-heartbeat.ts} +0 -0
- /package/src/runtime/{worker-startup.ts → heartbeat/worker-startup.ts} +0 -0
- /package/src/runtime/{intercom-bridge.ts → live-session/intercom-bridge.ts} +0 -0
- /package/src/runtime/{live-control-realtime.ts → live-session/live-control-realtime.ts} +0 -0
- /package/src/runtime/{live-extension-bridge.ts → live-session/live-extension-bridge.ts} +0 -0
- /package/src/runtime/{live-irc.ts → live-session/live-irc.ts} +0 -0
- /package/src/runtime/{live-session-health.ts → live-session/live-session-health.ts} +0 -0
- /package/src/runtime/{model-resolver.ts → model/model-resolver.ts} +0 -0
- /package/src/runtime/{model-scope.ts → model/model-scope.ts} +0 -0
- /package/src/runtime/{output-validator.ts → output/output-validator.ts} +0 -0
- /package/src/runtime/{pi-json-output.ts → output/pi-json-output.ts} +0 -0
- /package/src/runtime/{progress-event-coalescer.ts → output/progress-event-coalescer.ts} +0 -0
- /package/src/runtime/{cancellation-token.ts → process/cancellation-token.ts} +0 -0
- /package/src/runtime/{zombie-scanner.ts → process/zombie-scanner.ts} +0 -0
- /package/src/runtime/{crash-classification.ts → recovery/crash-classification.ts} +0 -0
- /package/src/runtime/{batch-barrier.ts → scheduling/batch-barrier.ts} +0 -0
- /package/src/runtime/{global-worker-cap.ts → scheduling/global-worker-cap.ts} +0 -0
- /package/src/runtime/{parallel-utils.ts → scheduling/parallel-utils.ts} +0 -0
- /package/src/runtime/{scheduler.ts → scheduling/scheduler.ts} +0 -0
- /package/src/runtime/{semaphore.ts → scheduling/semaphore.ts} +0 -0
- /package/src/runtime/{task-graph.ts → scheduling/task-graph.ts} +0 -0
- /package/src/runtime/{verification-worktree.ts → verification/verification-worktree.ts} +0 -0
- /package/src/state/{event-reconstructor.ts → event-log/event-reconstructor.ts} +0 -0
- /package/src/state/{worker-atomic-writer.ts → event-log/worker-atomic-writer.ts} +0 -0
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env -S npx tsx
|
|
2
|
-
/**
|
|
3
|
-
* Crash reproduction test for issue #29: verifies the defense-in-depth fix
|
|
4
|
-
* in subagent-manager.ts:start() prevents unhandled rejections from
|
|
5
|
-
* crashing the host process.
|
|
6
|
-
*
|
|
7
|
-
* Strategy: spawn a subagent whose runner throws, then do NOT await
|
|
8
|
-
* record.promise. Without the fix, this triggers unhandledRejection which
|
|
9
|
-
* Node.js converts to uncaughtException. With the fix, the .catch inside
|
|
10
|
-
* start() absorbs the rejection.
|
|
11
|
-
*
|
|
12
|
-
* Run with: npx tsx scripts/test-issue-29-crash.ts
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
import * as fs from "node:fs";
|
|
16
|
-
import * as os from "node:os";
|
|
17
|
-
import * as path from "node:path";
|
|
18
|
-
import { fileURLToPath } from "node:url";
|
|
19
|
-
|
|
20
|
-
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-crew-issue-29-crash-"));
|
|
21
|
-
fs.mkdirSync(path.join(tmpDir, ".pi"), { recursive: true });
|
|
22
|
-
|
|
23
|
-
console.log("Issue #29 CRASH reproduction test");
|
|
24
|
-
console.log(`Project: ${tmpDir}`);
|
|
25
|
-
console.log();
|
|
26
|
-
|
|
27
|
-
let crashed = false;
|
|
28
|
-
let crashError: Error | undefined;
|
|
29
|
-
const crashEvents: string[] = [];
|
|
30
|
-
|
|
31
|
-
process.on("uncaughtException", (error) => {
|
|
32
|
-
crashed = true;
|
|
33
|
-
crashError = error;
|
|
34
|
-
crashEvents.push(`uncaughtException: ${error.message}`);
|
|
35
|
-
console.error(`[CRASH] uncaughtException: ${error.message}`);
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
process.on("unhandledRejection", (reason) => {
|
|
39
|
-
crashed = true;
|
|
40
|
-
const err = reason instanceof Error ? reason : new Error(String(reason));
|
|
41
|
-
crashError = err;
|
|
42
|
-
crashEvents.push(`unhandledRejection: ${err.message}`);
|
|
43
|
-
console.error(`[CRASH] unhandledRejection: ${err.message}`);
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
async function main(): Promise<void> {
|
|
47
|
-
const piCrewRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
48
|
-
const { SubagentManager } = await import(path.join(piCrewRoot, "src/runtime/subagent-manager.ts"));
|
|
49
|
-
|
|
50
|
-
const mgr = new SubagentManager();
|
|
51
|
-
|
|
52
|
-
const runnerThatThrows = async (): Promise<never> => {
|
|
53
|
-
throw new Error("simulated subagent failure (issue #29 crash test)");
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
const record = mgr.spawn(
|
|
57
|
-
{
|
|
58
|
-
cwd: tmpDir,
|
|
59
|
-
type: "test",
|
|
60
|
-
description: "issue-29 crash test",
|
|
61
|
-
prompt: "throw",
|
|
62
|
-
background: false,
|
|
63
|
-
},
|
|
64
|
-
runnerThatThrows as Parameters<typeof mgr.spawn>[1],
|
|
65
|
-
);
|
|
66
|
-
|
|
67
|
-
console.log(`Spawned subagent ${record.id}`);
|
|
68
|
-
|
|
69
|
-
// Do NOT await — this is the scenario that crashes pi.
|
|
70
|
-
console.log("Waiting 300ms without awaiting record.promise...");
|
|
71
|
-
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
72
|
-
|
|
73
|
-
console.log();
|
|
74
|
-
console.log(`Results:`);
|
|
75
|
-
console.log(` record.status = ${record.status}`);
|
|
76
|
-
console.log(` process.crashed = ${crashed}`);
|
|
77
|
-
console.log(` crashEvents = ${JSON.stringify(crashEvents)}`);
|
|
78
|
-
console.log();
|
|
79
|
-
|
|
80
|
-
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
81
|
-
|
|
82
|
-
if (crashed) {
|
|
83
|
-
console.error("✗ FAIL: process crashed (uncaughtException/unhandledRejection fired)");
|
|
84
|
-
console.error(" The defense-in-depth fix in subagent-manager.ts did NOT prevent the crash.");
|
|
85
|
-
if (crashError?.stack) {
|
|
86
|
-
console.error(" Stack trace:");
|
|
87
|
-
console.error(
|
|
88
|
-
crashError.stack
|
|
89
|
-
.split("\n")
|
|
90
|
-
.slice(0, 10)
|
|
91
|
-
.map((l) => ` ${l}`)
|
|
92
|
-
.join("\n"),
|
|
93
|
-
);
|
|
94
|
-
}
|
|
95
|
-
process.exit(1);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
if (record.status !== "error") {
|
|
99
|
-
console.error(`✗ FAIL: record.status should be 'error', got '${record.status}'`);
|
|
100
|
-
process.exit(1);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
console.log("✓ PASS: no crash, record marked 'error'");
|
|
104
|
-
process.exit(0);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
main().catch((error) => {
|
|
108
|
-
console.error("Test script itself crashed:");
|
|
109
|
-
console.error(error);
|
|
110
|
-
process.exit(1);
|
|
111
|
-
});
|
|
@@ -1,183 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env -S npx tsx
|
|
2
|
-
/**
|
|
3
|
-
* End-to-end test for issue #29: "Hardcoded .crew/state/runs path crashes pi
|
|
4
|
-
* (uncaughtException) in .pi-based projects".
|
|
5
|
-
*
|
|
6
|
-
* This script reproduces the EXACT scenario from the issue:
|
|
7
|
-
* 1. Create a .pi/-only project (no .crew/ directory).
|
|
8
|
-
* 2. Set up an uncaughtException + unhandledRejection detector.
|
|
9
|
-
* 3. Trigger the bug path: start a subagent whose runner throws, then
|
|
10
|
-
* NOT await record.promise (mimics the indirect caller pattern
|
|
11
|
-
* described in the issue).
|
|
12
|
-
* 4. Exit 0 if no crash, exit 1 if crash detected.
|
|
13
|
-
*
|
|
14
|
-
* Run with: npx tsx scripts/test-issue-29-e2e.ts
|
|
15
|
-
*
|
|
16
|
-
* The script must be run via `tsx` so that the dynamic ESM import of
|
|
17
|
-
* subagent-manager resolves correctly.
|
|
18
|
-
*/
|
|
19
|
-
|
|
20
|
-
import * as fs from "node:fs";
|
|
21
|
-
import * as os from "node:os";
|
|
22
|
-
import * as path from "node:path";
|
|
23
|
-
import { fileURLToPath } from "node:url";
|
|
24
|
-
|
|
25
|
-
// ── Step 1: Set up a .pi/-only project (no .crew/) ──────────────────────
|
|
26
|
-
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-crew-issue-29-e2e-"));
|
|
27
|
-
fs.mkdirSync(path.join(tmpDir, ".pi"), { recursive: true });
|
|
28
|
-
// CRITICALLY: no .crew/ directory
|
|
29
|
-
const projectRoot = tmpDir;
|
|
30
|
-
|
|
31
|
-
console.log("=".repeat(72));
|
|
32
|
-
console.log("Issue #29 end-to-end reproduction test");
|
|
33
|
-
console.log("=".repeat(72));
|
|
34
|
-
console.log(`Project root: ${projectRoot}`);
|
|
35
|
-
console.log(`Has .pi/: ${fs.existsSync(path.join(projectRoot, ".pi"))}`);
|
|
36
|
-
console.log(`Has .crew/: ${fs.existsSync(path.join(projectRoot, ".crew"))} (should be false)`);
|
|
37
|
-
console.log();
|
|
38
|
-
|
|
39
|
-
// ── Step 2: Install crash detectors ──────────────────────────────────────
|
|
40
|
-
let crashed = false;
|
|
41
|
-
let crashError: Error | undefined;
|
|
42
|
-
|
|
43
|
-
process.on("uncaughtException", (error) => {
|
|
44
|
-
crashed = true;
|
|
45
|
-
crashError = error;
|
|
46
|
-
console.error("✗ FATAL: uncaughtException fired");
|
|
47
|
-
console.error(" This is the bug described in issue #29.");
|
|
48
|
-
console.error(` Error: ${error.message}`);
|
|
49
|
-
if (error.stack) console.error(` Stack: ${error.stack.split("\n").slice(0, 8).join("\n")}`);
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
process.on("unhandledRejection", (reason) => {
|
|
53
|
-
crashed = true;
|
|
54
|
-
const reasonErr = reason instanceof Error ? reason : new Error(String(reason));
|
|
55
|
-
crashError = reasonErr;
|
|
56
|
-
console.error("✗ FATAL: unhandledRejection fired");
|
|
57
|
-
console.error(" This is the bug described in issue #29.");
|
|
58
|
-
console.error(` Reason: ${reasonErr.message}`);
|
|
59
|
-
if (reasonErr.stack) console.error(` Stack: ${reasonErr.stack.split("\n").slice(0, 8).join("\n")}`);
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
// ── Step 3: Trigger the bug path ─────────────────────────────────────────
|
|
63
|
-
|
|
64
|
-
async function main(): Promise<void> {
|
|
65
|
-
const piCrewRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
66
|
-
|
|
67
|
-
// Dynamic import so the .ts files are loaded through tsx, not require.
|
|
68
|
-
const { SubagentManager } = await import(path.join(piCrewRoot, "src/runtime/subagent-manager.ts"));
|
|
69
|
-
const { waitForRun } = await import(path.join(piCrewRoot, "src/runtime/run-tracker.ts"));
|
|
70
|
-
const { projectCrewRoot } = await import(path.join(piCrewRoot, "src/utils/paths.ts"));
|
|
71
|
-
|
|
72
|
-
// Verify the resolver returns the .pi/teams/ path for our project.
|
|
73
|
-
const resolvedRoot = projectCrewRoot(projectRoot);
|
|
74
|
-
console.log(`projectCrewRoot(projectRoot) = ${resolvedRoot}`);
|
|
75
|
-
const expectedRoot = path.join(projectRoot, ".pi", "teams");
|
|
76
|
-
if (resolvedRoot !== expectedRoot) {
|
|
77
|
-
console.error(`✗ projectCrewRoot returned ${resolvedRoot}, expected ${expectedRoot}`);
|
|
78
|
-
process.exit(1);
|
|
79
|
-
}
|
|
80
|
-
console.log("✓ projectCrewRoot correctly resolves to .pi/teams/");
|
|
81
|
-
console.log();
|
|
82
|
-
|
|
83
|
-
// ── Test 3a: Direct waitForRun() crash path ─────────────────────────
|
|
84
|
-
console.log("Test 3a: Direct waitForRun() with non-existent runId");
|
|
85
|
-
console.log("-".repeat(72));
|
|
86
|
-
const startA = Date.now();
|
|
87
|
-
try {
|
|
88
|
-
await waitForRun("team_e2e_never_exists_xyz", projectRoot, {
|
|
89
|
-
timeoutMs: 500,
|
|
90
|
-
pollIntervalMs: 50,
|
|
91
|
-
});
|
|
92
|
-
console.error("✗ waitForRun should have thrown");
|
|
93
|
-
} catch (error) {
|
|
94
|
-
const msg = (error as Error).message;
|
|
95
|
-
const elapsed = Date.now() - startA;
|
|
96
|
-
console.log(`✓ waitForRun threw after ${elapsed}ms (as expected)`);
|
|
97
|
-
console.log(` Message: ${msg}`);
|
|
98
|
-
if (msg.includes(".pi/teams")) {
|
|
99
|
-
console.log("✓ Error message references .pi/teams/ (resolver applied)");
|
|
100
|
-
} else if (msg.includes(".crew/state/runs")) {
|
|
101
|
-
console.error(`✗ Error message still references .crew/state/runs/ (BUG NOT FIXED)`);
|
|
102
|
-
process.exit(1);
|
|
103
|
-
} else {
|
|
104
|
-
console.error(`✗ Error message doesn't reference expected path: ${msg}`);
|
|
105
|
-
process.exit(1);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
console.log();
|
|
109
|
-
|
|
110
|
-
// ── Test 3b: Indirect crash path via SubagentManager ────────────────
|
|
111
|
-
// This is the ACTUAL crash path: subagent fails → record.promise rejects
|
|
112
|
-
// → no caller awaits → unhandled rejection → pi crashes.
|
|
113
|
-
console.log("Test 3b: SubagentManager with runner that throws (no awaiter)");
|
|
114
|
-
console.log("-".repeat(72));
|
|
115
|
-
const mgr = new SubagentManager();
|
|
116
|
-
|
|
117
|
-
const runnerThatThrows = async (): Promise<never> => {
|
|
118
|
-
// Simulate a failed run lookup inside the subagent — this is what
|
|
119
|
-
// waitForRun() throws when the run directory doesn't exist.
|
|
120
|
-
// The runner returns a synthetic result, and then pollRunToTerminal
|
|
121
|
-
// would call waitForRun internally, but to keep the test focused we
|
|
122
|
-
// throw directly from the runner (mimics any unhandled subagent failure).
|
|
123
|
-
throw new Error(
|
|
124
|
-
`Run team_e2e_simulated not found. No run directory at ${path.join(projectRoot, ".crew", "state", "runs", "team_e2e_simulated")}`,
|
|
125
|
-
);
|
|
126
|
-
};
|
|
127
|
-
|
|
128
|
-
const record = mgr.spawn(
|
|
129
|
-
{
|
|
130
|
-
cwd: projectRoot,
|
|
131
|
-
type: "test",
|
|
132
|
-
description: "issue-29-e2e test",
|
|
133
|
-
prompt: "simulate failure",
|
|
134
|
-
background: false,
|
|
135
|
-
},
|
|
136
|
-
runnerThatThrows as Parameters<typeof mgr.spawn>[1],
|
|
137
|
-
);
|
|
138
|
-
|
|
139
|
-
console.log(`Spawned subagent ${record.id} (status: ${record.status})`);
|
|
140
|
-
|
|
141
|
-
// CRITICALLY: do NOT await record.promise — this is the scenario
|
|
142
|
-
// described in the issue where the throw escapes as an unhandled
|
|
143
|
-
// rejection because no caller awaits.
|
|
144
|
-
console.log("Waiting 500ms without awaiting record.promise...");
|
|
145
|
-
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
146
|
-
|
|
147
|
-
console.log(`After 500ms:`);
|
|
148
|
-
console.log(` record.status = ${record.status}`);
|
|
149
|
-
console.log(` process.crashed = ${crashed}`);
|
|
150
|
-
console.log();
|
|
151
|
-
|
|
152
|
-
// ── Step 4: Final verdict ───────────────────────────────────────────
|
|
153
|
-
if (crashed) {
|
|
154
|
-
console.error("=".repeat(72));
|
|
155
|
-
console.error("✗ TEST FAILED: process crashed (uncaughtException or unhandledRejection)");
|
|
156
|
-
console.error("=".repeat(72));
|
|
157
|
-
console.error("The fix in commit a80fe6c did NOT prevent the crash.");
|
|
158
|
-
process.exit(1);
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
if (record.status !== "error") {
|
|
162
|
-
console.error(`✗ TEST FAILED: record.status should be 'error', got '${record.status}'`);
|
|
163
|
-
process.exit(1);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
console.log("=".repeat(72));
|
|
167
|
-
console.log("✓ ALL TESTS PASSED");
|
|
168
|
-
console.log(" - projectCrewRoot correctly resolves .pi/teams/");
|
|
169
|
-
console.log(" - waitForRun() throws with .pi/teams/ in error message");
|
|
170
|
-
console.log(" - SubagentManager rejection is caught by defense-in-depth .catch");
|
|
171
|
-
console.log(" - No uncaughtException or unhandledRejection fired");
|
|
172
|
-
console.log("=".repeat(72));
|
|
173
|
-
|
|
174
|
-
// Cleanup
|
|
175
|
-
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
176
|
-
process.exit(0);
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
main().catch((error) => {
|
|
180
|
-
console.error("Test script itself crashed:");
|
|
181
|
-
console.error(error);
|
|
182
|
-
process.exit(1);
|
|
183
|
-
});
|
|
@@ -1,330 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env -S npx tsx
|
|
2
|
-
/**
|
|
3
|
-
* REAL-RUNTIME E2E test for issue #29.
|
|
4
|
-
*
|
|
5
|
-
* This is the most realistic test possible without invoking the full
|
|
6
|
-
* `pi` agent loop. It:
|
|
7
|
-
* 1. Sets up a .pi/-only project (no .crew/).
|
|
8
|
-
* 2. Creates a real team run manifest via createRunManifest().
|
|
9
|
-
* 3. Calls spawnBackgroundTeamRun() which spawns a REAL detached
|
|
10
|
-
* background-runner.ts process (the same path used by `team action='run'`).
|
|
11
|
-
* 4. Waits for the background runner to write background.log and exit.
|
|
12
|
-
* 5. Verifies EVERY path the fix touched:
|
|
13
|
-
* - logPath is at .pi/teams/state/runs/<runId>/background.log
|
|
14
|
-
* - exit-code.txt is at .pi/teams/state/runs/<runId>/exit-code.txt
|
|
15
|
-
* - manifest.json is at .pi/teams/state/runs/<runId>/manifest.json
|
|
16
|
-
* - tasks.json is at .pi/teams/state/runs/<runId>/tasks.json
|
|
17
|
-
* - events.jsonl is at .pi/teams/state/runs/<runId>/events.jsonl
|
|
18
|
-
* 6. Verifies NO files leak to .crew/state/runs/...
|
|
19
|
-
* 7. Captures any uncaughtException/unhandledRejection from the test
|
|
20
|
-
* process (which would indicate the bug fires).
|
|
21
|
-
*
|
|
22
|
-
* The test uses PI_CREW_EXECUTE_WORKERS=false so the team-runner uses
|
|
23
|
-
* "scaffold" runtime (no child Pi agents spawned), but the background-
|
|
24
|
-
* runner process itself is REAL — it actually runs, writes to disk, and
|
|
25
|
-
* exits. This exercises all 11 fixed sites:
|
|
26
|
-
* - background-runner.ts:139 (log redirect path)
|
|
27
|
-
* - background-runner.ts:172 (exit-code path)
|
|
28
|
-
* - run-tracker.ts:82 (waitForRun slow path early-exit)
|
|
29
|
-
* - checkpoint.ts:166,177,188,199,209 (5 sites)
|
|
30
|
-
* - skill-effectiveness.ts:115,125 (when tasks complete)
|
|
31
|
-
* - decision-ledger.ts:29 (when rollouts happen)
|
|
32
|
-
*
|
|
33
|
-
* Run with: npx tsx scripts/test-issue-29-real-runtime.ts
|
|
34
|
-
*/
|
|
35
|
-
|
|
36
|
-
import * as fs from "node:fs";
|
|
37
|
-
import * as os from "node:os";
|
|
38
|
-
import * as path from "node:path";
|
|
39
|
-
import { fileURLToPath } from "node:url";
|
|
40
|
-
|
|
41
|
-
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-crew-issue-29-runtime-"));
|
|
42
|
-
fs.mkdirSync(path.join(tmpDir, ".pi"), { recursive: true });
|
|
43
|
-
// CRITICALLY: no .crew/
|
|
44
|
-
|
|
45
|
-
console.log("=".repeat(72));
|
|
46
|
-
console.log("Issue #29 REAL-RUNTIME E2E test");
|
|
47
|
-
console.log(" (spawns REAL detached background-runner.ts process)");
|
|
48
|
-
console.log("=".repeat(72));
|
|
49
|
-
console.log(`Project: ${tmpDir}`);
|
|
50
|
-
console.log(` Has .pi/ : ${fs.existsSync(path.join(tmpDir, ".pi"))}`);
|
|
51
|
-
console.log(` Has .crew/: ${fs.existsSync(path.join(tmpDir, ".crew"))} (should be false)`);
|
|
52
|
-
console.log();
|
|
53
|
-
|
|
54
|
-
// ── Crash detection ──────────────────────────────────────────────────────
|
|
55
|
-
let crashed = false;
|
|
56
|
-
let crashError: Error | undefined;
|
|
57
|
-
process.on("uncaughtException", (error) => {
|
|
58
|
-
crashed = true;
|
|
59
|
-
crashError = error;
|
|
60
|
-
console.error(`[CRASH] uncaughtException: ${error.message}`);
|
|
61
|
-
});
|
|
62
|
-
process.on("unhandledRejection", (reason) => {
|
|
63
|
-
crashed = true;
|
|
64
|
-
const err = reason instanceof Error ? reason : new Error(String(reason));
|
|
65
|
-
crashError = err;
|
|
66
|
-
console.error(`[CRASH] unhandledRejection: ${err.message}`);
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
// Force placeholder workers in the background runner (no LLM needed).
|
|
70
|
-
// The runtime resolver checks for "0" (string) to disable child worker execution.
|
|
71
|
-
process.env.PI_CREW_EXECUTE_WORKERS = "0";
|
|
72
|
-
process.env.PI_TEAMS_EXECUTE_WORKERS = "0";
|
|
73
|
-
// We also need to set runConfig.runtime.mode = "scaffold" in the manifest
|
|
74
|
-
// because the background-runner explicitly blocks PI_CREW_EXECUTE_WORKERS
|
|
75
|
-
// from leaking to the child (security feature).
|
|
76
|
-
|
|
77
|
-
const piCrewRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
78
|
-
let pass = 0;
|
|
79
|
-
let fail = 0;
|
|
80
|
-
const failures: string[] = [];
|
|
81
|
-
|
|
82
|
-
function check(label: string, condition: boolean, detail = ""): void {
|
|
83
|
-
if (condition) {
|
|
84
|
-
pass++;
|
|
85
|
-
console.log(` ✓ ${label}`);
|
|
86
|
-
} else {
|
|
87
|
-
fail++;
|
|
88
|
-
failures.push(label);
|
|
89
|
-
console.error(` ✗ ${label}${detail ? ` — ${detail}` : ""}`);
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
async function sleep(ms: number): Promise<void> {
|
|
94
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
async function waitForFile(filePath: string, timeoutMs: number): Promise<boolean> {
|
|
98
|
-
const start = Date.now();
|
|
99
|
-
while (Date.now() - start < timeoutMs) {
|
|
100
|
-
if (fs.existsSync(filePath)) return true;
|
|
101
|
-
await sleep(100);
|
|
102
|
-
}
|
|
103
|
-
return false;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
async function main(): Promise<void> {
|
|
107
|
-
const { createRunManifest } = await import(path.join(piCrewRoot, "src/state/state-store.ts"));
|
|
108
|
-
const { discoverTeams, allTeams } = await import(path.join(piCrewRoot, "src/teams/discover-teams.ts"));
|
|
109
|
-
const { discoverWorkflows, allWorkflows } = await import(path.join(piCrewRoot, "src/workflows/discover-workflows.ts"));
|
|
110
|
-
const { spawnBackgroundTeamRun } = await import(path.join(piCrewRoot, "src/runtime/async-runner.ts"));
|
|
111
|
-
const { projectCrewRoot, clearProjectRootCache } = await import(path.join(piCrewRoot, "src/utils/paths.ts"));
|
|
112
|
-
const { loadRunManifestById } = await import(path.join(piCrewRoot, "src/state/state-store.ts"));
|
|
113
|
-
|
|
114
|
-
clearProjectRootCache();
|
|
115
|
-
const root = projectCrewRoot(tmpDir);
|
|
116
|
-
check(`projectCrewRoot returns .pi/teams/`, root === path.join(tmpDir, ".pi", "teams"));
|
|
117
|
-
|
|
118
|
-
// ── Set up a team run ─────────────────────────────────────────────
|
|
119
|
-
console.log();
|
|
120
|
-
console.log("Step 1: Create team run manifest");
|
|
121
|
-
console.log("-".repeat(72));
|
|
122
|
-
|
|
123
|
-
const teams = allTeams(discoverTeams(tmpDir));
|
|
124
|
-
const workflows = allWorkflows(discoverWorkflows(tmpDir));
|
|
125
|
-
const team = teams.find((t) => t.name === "fast-fix");
|
|
126
|
-
const workflow = workflows.find((w) => w.name === "fast-fix");
|
|
127
|
-
check("fast-fix team discovered", Boolean(team));
|
|
128
|
-
check("fast-fix workflow discovered", Boolean(workflow));
|
|
129
|
-
|
|
130
|
-
if (!team || !workflow) {
|
|
131
|
-
console.error("Cannot continue without team/workflow");
|
|
132
|
-
process.exit(1);
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
const { manifest } = createRunManifest({
|
|
136
|
-
cwd: tmpDir,
|
|
137
|
-
team,
|
|
138
|
-
workflow,
|
|
139
|
-
goal: "REAL RUNTIME test: spawn background runner, verify paths land in .pi/teams/state/runs/",
|
|
140
|
-
workspaceMode: "single",
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
// Force scaffold mode so no child Pi workers are spawned (no LLM needed).
|
|
144
|
-
// This is what the background-runner will use.
|
|
145
|
-
const scaffoldRuntimeResolution = {
|
|
146
|
-
kind: "scaffold" as const,
|
|
147
|
-
requestedMode: "scaffold" as const,
|
|
148
|
-
available: true,
|
|
149
|
-
reason: "Test: forced scaffold mode for E2E without LLM",
|
|
150
|
-
resolvedAt: new Date().toISOString(),
|
|
151
|
-
};
|
|
152
|
-
const scaffoldRunConfig = {
|
|
153
|
-
...((manifest as { runConfig?: unknown }).runConfig ?? {}),
|
|
154
|
-
executeWorkers: false,
|
|
155
|
-
runtime: {
|
|
156
|
-
...(((manifest as { runConfig?: { runtime?: unknown } }).runConfig?.runtime ?? {}) as Record<string, unknown>),
|
|
157
|
-
mode: "scaffold",
|
|
158
|
-
},
|
|
159
|
-
};
|
|
160
|
-
const manifestWithRuntime = {
|
|
161
|
-
...manifest,
|
|
162
|
-
runtimeResolution: scaffoldRuntimeResolution,
|
|
163
|
-
runConfig: scaffoldRunConfig,
|
|
164
|
-
};
|
|
165
|
-
|
|
166
|
-
// Override status to queued (createRunManifest sets it, but let's be explicit)
|
|
167
|
-
const queuedManifest = {
|
|
168
|
-
...manifestWithRuntime,
|
|
169
|
-
status: "queued" as const,
|
|
170
|
-
};
|
|
171
|
-
console.log(` Created run: ${queuedManifest.runId}`);
|
|
172
|
-
console.log(` stateRoot: ${queuedManifest.stateRoot}`);
|
|
173
|
-
console.log(` runtime: ${queuedManifest.runtimeResolution?.kind ?? "(default)"}`);
|
|
174
|
-
|
|
175
|
-
check(
|
|
176
|
-
`manifest.stateRoot is under .pi/teams/state/runs/`,
|
|
177
|
-
queuedManifest.stateRoot === path.join(tmpDir, ".pi", "teams", "state", "runs", queuedManifest.runId),
|
|
178
|
-
`got: ${queuedManifest.stateRoot}`,
|
|
179
|
-
);
|
|
180
|
-
|
|
181
|
-
// ── Step 2: Spawn REAL background runner ──────────────────────────
|
|
182
|
-
console.log();
|
|
183
|
-
console.log("Step 2: spawnBackgroundTeamRun() — spawn REAL detached process");
|
|
184
|
-
console.log("-".repeat(72));
|
|
185
|
-
|
|
186
|
-
// Pass the queued manifest (background runner expects this format)
|
|
187
|
-
// We also need to write the manifest to disk so the runner can read it.
|
|
188
|
-
const stateDir = queuedManifest.stateRoot;
|
|
189
|
-
fs.mkdirSync(stateDir, { recursive: true });
|
|
190
|
-
fs.writeFileSync(path.join(stateDir, "manifest.json"), JSON.stringify(queuedManifest, null, 2));
|
|
191
|
-
|
|
192
|
-
const tStart = Date.now();
|
|
193
|
-
const spawnResult = await spawnBackgroundTeamRun(queuedManifest);
|
|
194
|
-
console.log(` Spawned background runner`);
|
|
195
|
-
console.log(` PID: ${spawnResult.pid ?? "(unknown)"}`);
|
|
196
|
-
console.log(` logPath: ${spawnResult.logPath}`);
|
|
197
|
-
console.log();
|
|
198
|
-
|
|
199
|
-
// ── Step 3: Verify the logPath is in the CORRECT location ─────────
|
|
200
|
-
// This is the path the background-runner.ts writes to (line 139).
|
|
201
|
-
check(
|
|
202
|
-
`logPath is under .pi/teams/state/runs/<runId>/background.log`,
|
|
203
|
-
spawnResult.logPath === path.join(stateDir, "background.log"),
|
|
204
|
-
`got: ${spawnResult.logPath}`,
|
|
205
|
-
);
|
|
206
|
-
check(`logPath is NOT under .crew/state/runs/...`, !spawnResult.logPath.includes(".crew/state/runs"));
|
|
207
|
-
|
|
208
|
-
// ── Step 4: Wait for the background runner to finish ──────────────
|
|
209
|
-
console.log();
|
|
210
|
-
console.log("Step 3: Wait for background runner to write logs and exit");
|
|
211
|
-
console.log("-".repeat(72));
|
|
212
|
-
|
|
213
|
-
// Wait for background.log to exist
|
|
214
|
-
const logWritten = await waitForFile(spawnResult.logPath, 30_000);
|
|
215
|
-
if (!logWritten) {
|
|
216
|
-
fail++;
|
|
217
|
-
failures.push("background.log was created within 30s");
|
|
218
|
-
console.error(" ✗ background.log was not created within 30s");
|
|
219
|
-
} else {
|
|
220
|
-
console.log(` ✓ background.log created at ${spawnResult.logPath}`);
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
// Wait for exit-code.txt (background-runner.ts:172 writes this on exit)
|
|
224
|
-
const exitCodePath = path.join(stateDir, "exit-code.txt");
|
|
225
|
-
const exitCodeWritten = await waitForFile(exitCodePath, 30_000);
|
|
226
|
-
if (!exitCodeWritten) {
|
|
227
|
-
fail++;
|
|
228
|
-
failures.push("exit-code.txt was created within 30s");
|
|
229
|
-
console.error(" ✗ exit-code.txt was not created within 30s");
|
|
230
|
-
} else {
|
|
231
|
-
const elapsed = Date.now() - tStart;
|
|
232
|
-
console.log(` ✓ exit-code.txt created in ${elapsed}ms`);
|
|
233
|
-
const exitCode = fs.readFileSync(exitCodePath, "utf-8").trim();
|
|
234
|
-
console.log(` Contents: ${JSON.stringify(exitCode.slice(0, 200))}`);
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
// Wait for the process to actually exit
|
|
238
|
-
let processExited = false;
|
|
239
|
-
for (let i = 0; i < 50; i++) {
|
|
240
|
-
if (spawnResult.pid !== undefined) {
|
|
241
|
-
try {
|
|
242
|
-
process.kill(spawnResult.pid, 0); // Check if alive
|
|
243
|
-
await sleep(200);
|
|
244
|
-
} catch {
|
|
245
|
-
processExited = true;
|
|
246
|
-
break;
|
|
247
|
-
}
|
|
248
|
-
} else {
|
|
249
|
-
// No PID — assume the runner already exited (synchronously)
|
|
250
|
-
processExited = true;
|
|
251
|
-
break;
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
check(`background runner process exited (PID ${spawnResult.pid})`, processExited);
|
|
255
|
-
|
|
256
|
-
// ── Step 5: Verify NO files leaked to .crew/ ──────────────────────
|
|
257
|
-
console.log();
|
|
258
|
-
console.log("Step 4: Verify no .crew/ files were created");
|
|
259
|
-
console.log("-".repeat(72));
|
|
260
|
-
|
|
261
|
-
const crewDir = path.join(tmpDir, ".crew");
|
|
262
|
-
check(`no .crew/ directory was created (would indicate fallback)`, !fs.existsSync(crewDir), `found at: ${crewDir}`);
|
|
263
|
-
|
|
264
|
-
// Also check that all the expected files are under .pi/teams/
|
|
265
|
-
const expectedFiles = ["manifest.json", "tasks.json", "events.jsonl", "background.log", "exit-code.txt"];
|
|
266
|
-
for (const f of expectedFiles) {
|
|
267
|
-
const p = path.join(stateDir, f);
|
|
268
|
-
check(`${f} exists at .pi/teams/state/runs/<runId>/${f}`, fs.existsSync(p));
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
// ── Step 6: Verify the runner wrote meaningful content ───────────
|
|
272
|
-
console.log();
|
|
273
|
-
console.log("Step 5: Verify background runner output");
|
|
274
|
-
console.log("-".repeat(72));
|
|
275
|
-
|
|
276
|
-
if (logWritten) {
|
|
277
|
-
const logContent = fs.readFileSync(spawnResult.logPath, "utf-8");
|
|
278
|
-
const lineCount = logContent.split("\n").filter((l) => l.trim()).length;
|
|
279
|
-
check(`background.log has content (${lineCount} non-empty lines)`, lineCount > 0);
|
|
280
|
-
console.log(` First 200 chars: ${JSON.stringify(logContent.slice(0, 200))}`);
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
// Verify the manifest was updated by the runner
|
|
284
|
-
const reloaded = loadRunManifestById(tmpDir, queuedManifest.runId);
|
|
285
|
-
check(`manifest can be reloaded after runner completed`, Boolean(reloaded));
|
|
286
|
-
if (reloaded) {
|
|
287
|
-
console.log(` Final status: ${reloaded.manifest.status}`);
|
|
288
|
-
check(
|
|
289
|
-
`final status is completed/failed/cancelled (not 'running')`,
|
|
290
|
-
["completed", "failed", "cancelled"].includes(reloaded.manifest.status),
|
|
291
|
-
`got: ${reloaded.manifest.status}`,
|
|
292
|
-
);
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
// ── Final verdict ───────────────────────────────────────────────────
|
|
296
|
-
console.log();
|
|
297
|
-
console.log("=".repeat(72));
|
|
298
|
-
console.log(`Results: ${pass} passed, ${fail} failed, ${crashed ? "1" : "0"} crashed`);
|
|
299
|
-
if (failures.length > 0) {
|
|
300
|
-
console.error("Failures:");
|
|
301
|
-
for (const f of failures) console.error(` - ${f}`);
|
|
302
|
-
}
|
|
303
|
-
if (crashed) {
|
|
304
|
-
console.error(`Crashed: ${crashError?.message}`);
|
|
305
|
-
}
|
|
306
|
-
console.log("=".repeat(72));
|
|
307
|
-
|
|
308
|
-
// Cleanup
|
|
309
|
-
try {
|
|
310
|
-
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
311
|
-
} catch {
|
|
312
|
-
/* ignore */
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
if (crashed || fail > 0) {
|
|
316
|
-
process.exit(1);
|
|
317
|
-
}
|
|
318
|
-
process.exit(0);
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
main().catch((error) => {
|
|
322
|
-
console.error("Test script itself crashed:");
|
|
323
|
-
console.error(error);
|
|
324
|
-
try {
|
|
325
|
-
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
326
|
-
} catch {
|
|
327
|
-
/* ignore */
|
|
328
|
-
}
|
|
329
|
-
process.exit(1);
|
|
330
|
-
});
|