pi-crew 0.9.15 → 0.9.17
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 +145 -0
- package/README.md +19 -0
- package/dist/build-meta.json +21790 -0
- package/dist/index.mjs +83070 -0
- package/dist/index.mjs.map +7 -0
- package/docs/A +358 -0
- package/docs/M-A +357 -0
- package/docs/REVIEW-FINDINGS-2026-06 +357 -0
- package/docs/Y +357 -0
- package/docs/a +357 -0
- package/docs/aA +358 -0
- package/docs/archive/README.md +91 -0
- package/docs/patterns/command-agent-skill.md +1 -1
- package/docs/skills/REFERENCE.md +0 -17
- package/docs//303/242mmaAAA/303/242 +357 -0
- package/index.bundle.ts +25 -0
- package/index.ts +95 -4
- package/package.json +15 -4
- package/skills/iterative-audit/SKILL.md +0 -1
- package/skills/widget-rendering/SKILL.md +17 -0
- package/src/adapters/claude-adapter.ts +1 -3
- package/src/adapters/export-util.ts +16 -16
- package/src/adapters/index.ts +5 -5
- package/src/agents/agent-config.ts +20 -14
- package/src/agents/agent-serializer.ts +1 -1
- package/src/agents/discover-agents.ts +83 -39
- package/src/benchmark/benchmark-runner.ts +51 -51
- package/src/benchmark/feedback-loop.ts +1 -1
- package/src/config/config.ts +166 -590
- package/src/config/defaults.ts +28 -2
- package/src/config/drift-detector.ts +11 -14
- package/src/config/markers.ts +201 -203
- package/src/config/resilient-parser.ts +14 -6
- package/src/config/role-tools.ts +3 -3
- package/src/config/suggestions.ts +5 -12
- package/src/config/types.ts +9 -25
- package/src/errors.ts +151 -157
- package/src/extension/action-suggestions.ts +54 -9
- package/src/extension/async-notifier.ts +43 -13
- package/src/extension/autonomous-policy.ts +77 -37
- package/src/extension/command-completions.ts +9 -8
- package/src/extension/context-status-injection.ts +14 -12
- package/src/extension/crew-autocomplete.ts +8 -16
- package/src/extension/crew-cleanup.ts +2 -1
- package/src/extension/crew-shortcuts.ts +9 -6
- package/src/extension/cross-extension-rpc.ts +136 -42
- package/src/extension/help.ts +1 -1
- package/src/extension/import-index.ts +10 -5
- package/src/extension/knowledge-injection.ts +88 -15
- package/src/extension/management.ts +89 -349
- package/src/extension/message-renderers.ts +3 -4
- package/src/extension/notification-router.ts +22 -5
- package/src/extension/notification-sink.ts +6 -3
- package/src/extension/pi-api.ts +17 -8
- package/src/extension/plan-orchestrate.ts +8 -27
- package/src/extension/project-init.ts +21 -6
- package/src/extension/register.ts +329 -975
- package/src/extension/registration/artifact-cleanup.ts +9 -4
- package/src/extension/registration/command-utils.ts +5 -1
- package/src/extension/registration/commands.ts +806 -314
- package/src/extension/registration/compaction-guard.ts +15 -15
- package/src/extension/registration/lifecycle.ts +259 -0
- package/src/extension/registration/observability.ts +293 -0
- package/src/extension/registration/subagent-helpers.ts +29 -7
- package/src/extension/registration/subagent-tools.ts +253 -44
- package/src/extension/registration/team-tool.ts +19 -84
- package/src/extension/registration/ui.ts +168 -0
- package/src/extension/registration/viewers.ts +64 -24
- package/src/extension/result-watcher.ts +18 -7
- package/src/extension/run-bundle-schema.ts +21 -6
- package/src/extension/run-export.ts +15 -6
- package/src/extension/run-import.ts +64 -29
- package/src/extension/run-index.ts +28 -9
- package/src/extension/run-maintenance.ts +32 -12
- package/src/extension/session-summary.ts +1 -1
- package/src/extension/team-manager-command.ts +64 -7
- package/src/extension/team-onboard.ts +151 -150
- package/src/extension/team-recommendation.ts +121 -21
- package/src/extension/team-tool/anchor.ts +31 -64
- package/src/extension/team-tool/api.ts +929 -141
- package/src/extension/team-tool/auto-summarize.ts +14 -26
- package/src/extension/team-tool/cache-control.ts +1 -5
- package/src/extension/team-tool/cancel.ts +138 -38
- package/src/extension/team-tool/chain-dispatch.ts +5 -13
- package/src/extension/team-tool/chain-executor.ts +29 -51
- package/src/extension/team-tool/config-patch.ts +1 -1
- package/src/extension/team-tool/context.ts +40 -20
- package/src/extension/team-tool/destructive-gate.ts +10 -5
- package/src/extension/team-tool/doctor.ts +170 -51
- package/src/extension/team-tool/explain.ts +228 -214
- package/src/extension/team-tool/failure-patterns.ts +3 -9
- package/src/extension/team-tool/goal-wrap.ts +71 -29
- package/src/extension/team-tool/goal.ts +185 -35
- package/src/extension/team-tool/handle-schedule.ts +34 -15
- package/src/extension/team-tool/handle-settings.ts +94 -56
- package/src/extension/team-tool/health-monitor.ts +27 -108
- package/src/extension/team-tool/inspect.ts +42 -9
- package/src/extension/team-tool/intent-policy.ts +4 -1
- package/src/extension/team-tool/lifecycle-actions.ts +244 -48
- package/src/extension/team-tool/orchestrate.ts +7 -18
- package/src/extension/team-tool/parallel-dispatch.ts +51 -29
- package/src/extension/team-tool/plan.ts +29 -5
- package/src/extension/team-tool/respond.ts +49 -16
- package/src/extension/team-tool/run-not-found.ts +3 -8
- package/src/extension/team-tool/run.ts +80 -317
- package/src/extension/team-tool/status.ts +135 -43
- package/src/extension/team-tool/workflow-manage.ts +92 -25
- package/src/extension/team-tool-types.ts +8 -1
- package/src/extension/team-tool.ts +142 -551
- package/src/extension/validate-resources.ts +39 -8
- package/src/hooks/registry.ts +66 -17
- package/src/hooks/types.ts +1 -1
- package/src/i18n.ts +25 -11
- package/src/observability/correlation.ts +17 -3
- package/src/observability/event-bus.ts +51 -56
- package/src/observability/event-to-metric.ts +117 -15
- package/src/observability/exporters/otlp-exporter.ts +56 -17
- package/src/observability/exporters/prometheus-exporter.ts +1 -1
- package/src/observability/metric-registry.ts +16 -4
- package/src/observability/metric-retention.ts +8 -2
- package/src/observability/metric-sink.ts +11 -3
- package/src/observability/metrics-primitives.ts +42 -9
- package/src/plugins/plugin-define.ts +2 -2
- package/src/plugins/plugin-registry.ts +25 -25
- package/src/plugins/plugins/index.ts +1 -1
- package/src/plugins/plugins/nextjs.ts +16 -16
- package/src/plugins/plugins/vite.ts +7 -11
- package/src/plugins/plugins/vitest.ts +6 -11
- package/src/runtime/adaptive-plan.ts +225 -40
- package/src/runtime/agent-control.ts +58 -15
- package/src/runtime/agent-memory.ts +15 -9
- package/src/runtime/agent-observability.ts +10 -2
- package/src/runtime/anchor-manager.ts +27 -25
- package/src/runtime/async-marker.ts +7 -1
- package/src/runtime/async-runner.ts +27 -23
- package/src/runtime/auto-summarize.ts +7 -13
- package/src/runtime/background-runner.ts +189 -251
- package/src/runtime/batch-barrier.ts +18 -16
- package/src/runtime/cancellation-token.ts +16 -6
- package/src/runtime/cancellation.ts +46 -8
- package/src/runtime/capability-inventory.ts +6 -4
- package/src/runtime/chain-parser.ts +44 -11
- package/src/runtime/chain-runner.ts +63 -69
- package/src/runtime/checkpoint.ts +12 -56
- package/src/runtime/child-pi.ts +343 -78
- package/src/runtime/coalesce-tasks.ts +268 -0
- package/src/runtime/code-summary.ts +71 -26
- package/src/runtime/compact-stages/index.ts +15 -5
- package/src/runtime/compact-stages/tail-capture-stage.ts +7 -2
- package/src/runtime/compact-stages/truncation-stage.ts +4 -1
- package/src/runtime/compaction-summary.ts +17 -10
- package/src/runtime/completion-guard.ts +33 -16
- package/src/runtime/crash-classification.ts +28 -7
- package/src/runtime/crash-recovery.ts +177 -37
- package/src/runtime/crew-agent-records.ts +193 -40
- package/src/runtime/crew-hooks.ts +9 -24
- package/src/runtime/cross-extension-rpc.ts +25 -23
- package/src/runtime/custom-tools/irc-tool.ts +43 -15
- package/src/runtime/custom-tools/submit-result-tool.ts +16 -5
- package/src/runtime/delivery-coordinator.ts +34 -7
- package/src/runtime/delta-conflict.ts +9 -21
- package/src/runtime/deterministic-ast.ts +1 -1
- package/src/runtime/diagnostic-export.ts +53 -20
- package/src/runtime/direct-run.ts +12 -2
- package/src/runtime/dwf-state-store.ts +1 -1
- package/src/runtime/dynamic-workflow-context.ts +186 -58
- package/src/runtime/dynamic-workflow-runner.ts +42 -20
- package/src/runtime/effectiveness.ts +16 -8
- package/src/runtime/errors/crew-errors.ts +7 -11
- package/src/runtime/event-stream-bridge.ts +9 -3
- package/src/runtime/foreground-control.ts +54 -14
- package/src/runtime/foreground-watchdog.ts +9 -6
- package/src/runtime/goal-achievement.ts +27 -10
- package/src/runtime/goal-evaluator.ts +54 -19
- package/src/runtime/goal-loop-runner.ts +280 -99
- package/src/runtime/goal-state-store.ts +27 -17
- package/src/runtime/green-contract.ts +11 -2
- package/src/runtime/group-join.ts +64 -31
- package/src/runtime/handoff-manager.ts +37 -42
- package/src/runtime/heartbeat-gradient.ts +10 -2
- package/src/runtime/heartbeat-watcher.ts +32 -6
- package/src/runtime/hidden-handoff.ts +12 -30
- package/src/runtime/important-line-classifier.ts +12 -2
- package/src/runtime/iteration-hooks.ts +25 -15
- package/src/runtime/live-agent-control.ts +55 -7
- package/src/runtime/live-agent-manager.ts +130 -27
- package/src/runtime/live-control-realtime.ts +23 -3
- package/src/runtime/live-irc.ts +4 -1
- package/src/runtime/live-session-health.ts +12 -4
- package/src/runtime/live-session-runtime.ts +379 -117
- package/src/runtime/manifest-cache.ts +28 -12
- package/src/runtime/mcp-proxy.ts +8 -16
- package/src/runtime/metric-parser.ts +1 -5
- package/src/runtime/model-fallback.ts +55 -18
- package/src/runtime/model-resolver.ts +2 -6
- package/src/runtime/model-scope.ts +19 -3
- package/src/runtime/notebook-helpers.ts +60 -62
- package/src/runtime/orphan-worker-registry.ts +37 -26
- package/src/runtime/output-validator.ts +18 -3
- package/src/runtime/overflow-recovery.ts +5 -4
- package/src/runtime/parallel-research.ts +29 -5
- package/src/runtime/parallel-utils.ts +9 -11
- package/src/runtime/path-overlap.ts +150 -0
- package/src/runtime/peer-dep.ts +12 -17
- package/src/runtime/per-write-validator.ts +1 -3
- package/src/runtime/phase-tracker.ts +342 -330
- package/src/runtime/pi-args.ts +26 -8
- package/src/runtime/pi-json-output.ts +1 -1
- package/src/runtime/pi-spawn.ts +30 -19
- package/src/runtime/pipeline-runner.ts +56 -55
- package/src/runtime/plan-templates.ts +5 -6
- package/src/runtime/policy-engine.ts +43 -7
- package/src/runtime/post-checks.ts +12 -4
- package/src/runtime/post-exit-stdio-guard.ts +2 -2
- package/src/runtime/process-lifecycle.ts +20 -10
- package/src/runtime/process-status.ts +16 -5
- package/src/runtime/progress-event-coalescer.ts +2 -1
- package/src/runtime/progress-tracker.ts +103 -103
- package/src/runtime/prose-compressor.ts +9 -11
- package/src/runtime/recovery-recipes.ts +118 -24
- package/src/runtime/replace.ts +25 -10
- package/src/runtime/resilient-edit.ts +10 -25
- package/src/runtime/result-extractor.ts +1 -2
- package/src/runtime/retry-executor.ts +20 -4
- package/src/runtime/retry-runner.ts +26 -50
- package/src/runtime/role-permission.ts +6 -1
- package/src/runtime/run-coalesced-task-group.ts +256 -0
- package/src/runtime/run-drift.ts +14 -15
- package/src/runtime/run-tracker.ts +6 -28
- package/src/runtime/runtime-policy.ts +1 -6
- package/src/runtime/runtime-resolver.ts +80 -15
- package/src/runtime/scheduler.ts +47 -18
- package/src/runtime/semaphore.ts +5 -7
- package/src/runtime/sensitive-paths.ts +2 -1
- package/src/runtime/session-usage.ts +1 -1
- package/src/runtime/settings-store.ts +16 -12
- package/src/runtime/sidechain-output.ts +6 -2
- package/src/runtime/single-agent-compose.ts +1 -4
- package/src/runtime/skill-effectiveness.ts +29 -97
- package/src/runtime/skill-instructions.ts +40 -83
- package/src/runtime/stale-reconciler.ts +73 -197
- package/src/runtime/stream-preview.ts +1 -1
- package/src/runtime/streaming-output.ts +1 -1
- package/src/runtime/subagent-manager.ts +86 -169
- package/src/runtime/subprocess-tool-registry.ts +4 -1
- package/src/runtime/supervisor-contact.ts +10 -5
- package/src/runtime/task-display.ts +19 -5
- package/src/runtime/task-graph-scheduler.ts +95 -21
- package/src/runtime/task-graph.ts +5 -11
- package/src/runtime/task-health.ts +54 -47
- package/src/runtime/task-id.ts +11 -18
- package/src/runtime/task-output-context.ts +265 -81
- package/src/runtime/task-packet.ts +12 -20
- package/src/runtime/task-quality.ts +6 -14
- package/src/runtime/task-runner/context-retrieval.ts +4 -13
- package/src/runtime/task-runner/live-executor.ts +114 -36
- package/src/runtime/task-runner/output-splitter.ts +152 -0
- package/src/runtime/task-runner/progress.ts +50 -12
- package/src/runtime/task-runner/prompt-builder.ts +31 -7
- package/src/runtime/task-runner/prompt-pipeline.ts +31 -7
- package/src/runtime/task-runner/result-utils.ts +3 -1
- package/src/runtime/task-runner/retrieval-orchestrator.ts +310 -0
- package/src/runtime/task-runner/run-projection.ts +27 -8
- package/src/runtime/task-runner/state-helpers.ts +29 -8
- package/src/runtime/task-runner/tail-read.ts +2 -6
- package/src/runtime/task-runner.ts +180 -326
- package/src/runtime/team-runner-artifacts.ts +13 -0
- package/src/runtime/team-runner.ts +456 -974
- package/src/runtime/tool-output-pruner.ts +6 -9
- package/src/runtime/tool-progress.ts +11 -21
- package/src/runtime/verification-gates.ts +33 -24
- package/src/runtime/verification-integrity.ts +1 -4
- package/src/runtime/verification-worktree.ts +62 -12
- package/src/runtime/worker-heartbeat.ts +5 -1
- package/src/runtime/worker-startup.ts +29 -5
- package/src/runtime/workflow-state.ts +17 -9
- package/src/runtime/workspace-lock.ts +30 -35
- package/src/runtime/workspace-tree.ts +20 -32
- package/src/runtime/yield-handler.ts +43 -9
- package/src/runtime/zombie-scanner.ts +5 -4
- package/src/schema/config-schema.ts +266 -172
- package/src/schema/team-tool-schema.ts +29 -74
- package/src/schema/validation-types.ts +25 -17
- package/src/skills/discover-skills.ts +35 -10
- package/src/skills/skill-templates.ts +109 -27
- package/src/skills/validate.ts +26 -8
- package/src/state/active-run-registry.ts +87 -28
- package/src/state/artifact-store.ts +9 -5
- package/src/state/atomic-write-v2.ts +85 -63
- package/src/state/atomic-write.ts +105 -26
- package/src/state/blob-store.ts +47 -20
- package/src/state/contracts.ts +20 -5
- package/src/state/crew-init.ts +5 -22
- package/src/state/decision-ledger.ts +19 -73
- package/src/state/event-log-rotation.ts +41 -15
- package/src/state/event-log.ts +168 -53
- package/src/state/event-reconstructor.ts +11 -1
- package/src/state/gitignore-manager.ts +2 -8
- package/src/state/health-store.ts +57 -57
- package/src/state/hook-instinct-bridge.ts +1 -1
- package/src/state/hook-integrations.ts +1 -1
- package/src/state/instinct-store.ts +17 -5
- package/src/state/jsonl-writer.ts +1 -1
- package/src/state/locks.ts +23 -9
- package/src/state/mailbox.ts +151 -28
- package/src/state/observation-store.ts +20 -14
- package/src/state/run-cache.ts +142 -135
- package/src/state/run-graph.ts +6 -15
- package/src/state/run-metrics.ts +5 -18
- package/src/state/schedule.ts +15 -9
- package/src/state/state-store.ts +129 -45
- package/src/state/task-claims.ts +12 -2
- package/src/state/tiered-eval.ts +52 -43
- package/src/state/types-eval.ts +2 -2
- package/src/state/types.ts +24 -20
- package/src/state/usage.ts +20 -4
- package/src/state/worker-atomic-writer.ts +6 -3
- package/src/subagents/index.ts +2 -2
- package/src/teams/discover-teams.ts +21 -6
- package/src/tools/safe-bash-extension.ts +5 -6
- package/src/tools/safe-bash.ts +10 -11
- package/src/types/new-api-types.ts +6 -10
- package/src/ui/agent-management-overlay.ts +52 -40
- package/src/ui/card-colors.ts +13 -4
- package/src/ui/crew-footer.ts +8 -7
- package/src/ui/crew-select-list.ts +1 -1
- package/src/ui/dashboard-panes/agents-pane.ts +41 -25
- package/src/ui/dashboard-panes/cancellation-pane.ts +1 -1
- package/src/ui/dashboard-panes/capability-pane.ts +32 -15
- package/src/ui/dashboard-panes/health-pane.ts +2 -1
- package/src/ui/dashboard-panes/metrics-pane.ts +4 -1
- package/src/ui/dashboard-panes/progress-pane.ts +12 -9
- package/src/ui/heartbeat-aggregator.ts +15 -4
- package/src/ui/keybinding-map.ts +40 -7
- package/src/ui/live-conversation-overlay.ts +36 -20
- package/src/ui/live-duration.ts +1 -4
- package/src/ui/live-run-sidebar.ts +88 -25
- package/src/ui/loaders.ts +2 -8
- package/src/ui/mascot.ts +20 -36
- package/src/ui/overlays/agent-picker-overlay.ts +11 -3
- package/src/ui/overlays/confirm-overlay.ts +4 -2
- package/src/ui/overlays/help-overlay.ts +25 -14
- package/src/ui/overlays/mailbox-compose-overlay.ts +48 -11
- package/src/ui/overlays/mailbox-compose-preview.ts +20 -5
- package/src/ui/overlays/mailbox-detail-overlay.ts +27 -6
- package/src/ui/pi-ui-compat.ts +7 -7
- package/src/ui/powerbar-publisher.ts +120 -42
- package/src/ui/render-diff.ts +10 -3
- package/src/ui/render-scheduler.ts +12 -4
- package/src/ui/run-action-dispatcher.ts +81 -18
- package/src/ui/run-dashboard.ts +172 -76
- package/src/ui/run-event-bus.ts +59 -37
- package/src/ui/run-snapshot-cache.ts +276 -86
- package/src/ui/settings-overlay.ts +361 -74
- package/src/ui/status-colors.ts +12 -1
- package/src/ui/syntax-highlight.ts +1 -1
- package/src/ui/theme-adapter.ts +16 -14
- package/src/ui/theme-discovery.ts +13 -2
- package/src/ui/tool-progress-formatter.ts +7 -7
- package/src/ui/tool-render.ts +128 -56
- package/src/ui/tool-renderers/brief-mode.ts +45 -27
- package/src/ui/tool-renderers/index.ts +109 -43
- package/src/ui/transcript-cache.ts +32 -6
- package/src/ui/transcript-entries.ts +25 -23
- package/src/ui/transcript-viewer.ts +78 -29
- package/src/ui/widget/index.ts +99 -40
- package/src/ui/widget/widget-formatters.ts +13 -6
- package/src/ui/widget/widget-model.ts +19 -8
- package/src/ui/widget/widget-renderer.ts +23 -13
- package/src/ui/widget/widget-types.ts +1 -1
- package/src/utils/bm25-search.ts +199 -199
- package/src/utils/conflict-detect.ts +22 -21
- package/src/utils/env-filter.ts +14 -7
- package/src/utils/file-coalescer.ts +5 -1
- package/src/utils/fingerprint.ts +3 -6
- package/src/utils/frontmatter.ts +3 -1
- package/src/utils/fs-watch.ts +2 -6
- package/src/utils/gh-protocol.ts +119 -44
- package/src/utils/git.ts +13 -15
- package/src/utils/guards.ts +2 -5
- package/src/utils/ids.ts +9 -2
- package/src/utils/incremental-reader.ts +14 -3
- package/src/utils/internal-error.ts +2 -1
- package/src/utils/names.ts +12 -3
- package/src/utils/paths.ts +49 -5
- package/src/utils/project-detector.ts +2 -2
- package/src/utils/redaction.ts +29 -19
- package/src/utils/resolve-shell.ts +9 -7
- package/src/utils/run-watcher-registry.ts +19 -31
- package/src/utils/safe-paths.ts +45 -24
- package/src/utils/scan-cache.ts +9 -2
- package/src/utils/session-utils.ts +2 -4
- package/src/utils/sleep.ts +12 -5
- package/src/utils/sse-parser.ts +5 -17
- package/src/utils/visual.ts +52 -32
- package/src/workflows/discover-workflows.ts +41 -109
- package/src/workflows/intermediate-store.ts +5 -21
- package/src/workflows/preflight-validator.ts +6 -33
- package/src/workflows/topology-analyzer.ts +5 -21
- package/src/workflows/workflow-config.ts +7 -6
- package/src/worktree/branch-freshness.ts +66 -8
- package/src/worktree/cleanup.ts +130 -20
- package/src/worktree/worktree-manager.ts +212 -69
- package/skills/artifact-analysis-loop/SKILL.md +0 -303
- package/skills/detection-pipeline-design/SKILL.md +0 -286
- package/skills/hunting-investigation-loop/SKILL.md +0 -402
- package/skills/incident-playbook-construction/SKILL.md +0 -384
- package/skills/security-review/SKILL.md +0 -561
- package/skills/threat-hypothesis-framework/SKILL.md +0 -176
- package/skills/ui-render-performance/SKILL.md +0 -58
- /package/docs/{followup-review-round3-2026-05-12.md → archive/followup-review-round3-2026-05-12.md} +0 -0
- /package/docs/{followup-review-round4-2026-05-13.md → archive/followup-review-round4-2026-05-13.md} +0 -0
- /package/docs/{pi-crew-bugs.md → archive/pi-crew-bugs.md} +0 -0
- /package/docs/{pi-crew-test-final.md → archive/pi-crew-test-final.md} +0 -0
- /package/docs/{pi-crew-test-results.md → archive/pi-crew-test-results.md} +0 -0
- /package/docs/{pi-crew-test-round2.md → archive/pi-crew-test-round2.md} +0 -0
- /package/docs/{pi-crew-test-round4.md → archive/pi-crew-test-round4.md} +0 -0
- /package/docs/{pi-crew-test-round5.md → archive/pi-crew-test-round5.md} +0 -0
- /package/docs/{pi-crew-test-round6.md → archive/pi-crew-test-round6.md} +0 -0
- /package/docs/{pi-crew-v0.5.10-audit-fix-plan.md → archive/pi-crew-v0.5.10-audit-fix-plan.md} +0 -0
- /package/docs/{pi-crew-v0.5.11-audit-fix-plan.md → archive/pi-crew-v0.5.11-audit-fix-plan.md} +0 -0
- /package/docs/{pi-crew-v0.5.12-audit-fix-plan.md → archive/pi-crew-v0.5.12-audit-fix-plan.md} +0 -0
- /package/docs/{pi-crew-v0.5.13-audit-fix-plan.md → archive/pi-crew-v0.5.13-audit-fix-plan.md} +0 -0
- /package/docs/{pi-crew-v0.5.14-audit-fix-plan.md → archive/pi-crew-v0.5.14-audit-fix-plan.md} +0 -0
- /package/docs/{pi-crew-v0.5.16-audit-fix-plan.md → archive/pi-crew-v0.5.16-audit-fix-plan.md} +0 -0
- /package/docs/{pi-crew-v0.5.17-audit-fix-plan.md → archive/pi-crew-v0.5.17-audit-fix-plan.md} +0 -0
- /package/docs/{pi-crew-v0.5.5-audit-fix-plan.md → archive/pi-crew-v0.5.5-audit-fix-plan.md} +0 -0
- /package/docs/{pi-crew-v0.5.9-audit-fix-plan.md → archive/pi-crew-v0.5.9-audit-fix-plan.md} +0 -0
- /package/docs/{pi-mono-opportunities.md → archive/pi-mono-opportunities.md} +0 -0
- /package/docs/{pi-mono-review.md → archive/pi-mono-review.md} +0 -0
- /package/docs/{pi-subagent4-comparison.md → archive/pi-subagent4-comparison.md} +0 -0
- /package/docs/{pi-subagents3-deep-analysis.md → archive/pi-subagents3-deep-analysis.md} +0 -0
package/src/runtime/child-pi.ts
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { type ChildProcess, type SpawnOptions, spawn } from "node:child_process";
|
|
2
2
|
import * as fs from "node:fs";
|
|
3
3
|
import * as os from "node:os";
|
|
4
4
|
import * as path from "node:path";
|
|
5
|
-
import { WINDOWS_ESSENTIAL_ENV_VARS } from "../utils/env-allowlist.ts";
|
|
6
5
|
import type { AgentConfig } from "../agents/agent-config.ts";
|
|
7
|
-
import type { WorkerExitStatus } from "../state/types.ts";
|
|
8
|
-
import { buildPiWorkerArgs, checkCrewDepth, cleanupTempDir } from "./pi-args.ts";
|
|
9
|
-
import { getPiSpawnCommand } from "./pi-spawn.ts";
|
|
10
6
|
import { DEFAULT_CHILD_PI } from "../config/defaults.ts";
|
|
7
|
+
import { registerChildProcess, unregisterChildProcess } from "../extension/crew-cleanup.ts";
|
|
8
|
+
import type { WorkerExitStatus } from "../state/types.ts";
|
|
9
|
+
import { WINDOWS_ESSENTIAL_ENV_VARS } from "../utils/env-allowlist.ts";
|
|
10
|
+
import { sanitizeEnvSecrets } from "../utils/env-filter.ts";
|
|
11
11
|
import { logInternalError } from "../utils/internal-error.ts";
|
|
12
|
-
import { attachPostExitStdioGuard, trySignalChild } from "./post-exit-stdio-guard.ts";
|
|
13
12
|
import { redactJsonLine, redactSecretString } from "../utils/redaction.ts";
|
|
13
|
+
import { resolveRealContainedPath } from "../utils/safe-paths.ts";
|
|
14
14
|
import { applyCompactPipeline } from "./compact-pipeline.ts";
|
|
15
|
-
import {
|
|
16
|
-
import { sanitizeEnvSecrets } from "../utils/env-filter.ts";
|
|
17
|
-
import { registerChildProcess, unregisterChildProcess } from "../extension/crew-cleanup.ts";
|
|
15
|
+
import { TailCaptureStage, TruncationStage } from "./compact-stages/index.ts";
|
|
18
16
|
import { classifyProcessCrash } from "./crash-classification.ts";
|
|
19
|
-
import {
|
|
17
|
+
import { buildPiWorkerArgs, checkCrewDepth, cleanupTempDir } from "./pi-args.ts";
|
|
20
18
|
import { extractText } from "./pi-json-output.ts";
|
|
19
|
+
import { getPiSpawnCommand } from "./pi-spawn.ts";
|
|
20
|
+
import { attachPostExitStdioGuard, trySignalChild } from "./post-exit-stdio-guard.ts";
|
|
21
21
|
|
|
22
22
|
const POST_EXIT_STDIO_GUARD_MS = DEFAULT_CHILD_PI.postExitStdioGuardMs;
|
|
23
23
|
const FINAL_DRAIN_MS = DEFAULT_CHILD_PI.finalDrainMs;
|
|
@@ -75,13 +75,27 @@ export function killProcessPid(pid: number): void {
|
|
|
75
75
|
// 3.8: Windows path uses taskkill /T /F (force kill the entire tree).
|
|
76
76
|
// taskkill itself can silently fail (PID gone, permission denied, etc.)
|
|
77
77
|
// so verify after 2s and log a warning if the process is still alive.
|
|
78
|
-
spawn("taskkill", ["/pid", String(pid), "/t", "/f"], {
|
|
78
|
+
spawn("taskkill", ["/pid", String(pid), "/t", "/f"], {
|
|
79
|
+
stdio: "ignore",
|
|
80
|
+
windowsHide: true,
|
|
81
|
+
});
|
|
79
82
|
const verifyTimer = setTimeout(() => {
|
|
80
83
|
try {
|
|
81
84
|
process.kill(pid, 0); // throws ESRCH when dead
|
|
82
85
|
// Still alive — log and retry once.
|
|
83
|
-
logInternalError(
|
|
84
|
-
|
|
86
|
+
logInternalError(
|
|
87
|
+
"child-pi.taskkill-stuck",
|
|
88
|
+
new Error(`process ${pid} still alive 2s after taskkill /T /F; retrying`),
|
|
89
|
+
`pid=${pid}`,
|
|
90
|
+
);
|
|
91
|
+
try {
|
|
92
|
+
spawn("taskkill", ["/pid", String(pid), "/t", "/f"], {
|
|
93
|
+
stdio: "ignore",
|
|
94
|
+
windowsHide: true,
|
|
95
|
+
});
|
|
96
|
+
} catch {
|
|
97
|
+
/* best-effort */
|
|
98
|
+
}
|
|
85
99
|
} catch {
|
|
86
100
|
// ESRCH or EPERM — process is gone. OK.
|
|
87
101
|
}
|
|
@@ -131,7 +145,9 @@ function killProcessTree(pid: number | undefined, child?: ChildProcess): void {
|
|
|
131
145
|
new Error(`pid=${pid} called from:\n${callerStack.split("\n").slice(0, 8).join("\n")}`),
|
|
132
146
|
`pid=${pid}`,
|
|
133
147
|
);
|
|
134
|
-
} catch {
|
|
148
|
+
} catch {
|
|
149
|
+
/* diagnostic best-effort */
|
|
150
|
+
}
|
|
135
151
|
if (!pid || !Number.isInteger(pid) || pid <= 0) return;
|
|
136
152
|
if (child && child.exitCode !== null) return;
|
|
137
153
|
killProcessPid(pid);
|
|
@@ -144,7 +160,6 @@ export function terminateActiveChildPiProcesses(): number {
|
|
|
144
160
|
return entries.length;
|
|
145
161
|
}
|
|
146
162
|
|
|
147
|
-
|
|
148
163
|
/** Structured lifecycle event emitted by child-pi for critical transitions. */
|
|
149
164
|
export interface ChildPiLifecycleEvent {
|
|
150
165
|
/** Event discriminator. */
|
|
@@ -417,11 +432,7 @@ function appendTranscript(input: ChildPiRunInput, line: string): void {
|
|
|
417
432
|
}
|
|
418
433
|
}
|
|
419
434
|
|
|
420
|
-
export function compactString(
|
|
421
|
-
value: string,
|
|
422
|
-
maxChars = MAX_COMPACT_CONTENT_CHARS,
|
|
423
|
-
opts: { preserveImportant?: boolean } = {},
|
|
424
|
-
): string {
|
|
435
|
+
export function compactString(value: string, maxChars = MAX_COMPACT_CONTENT_CHARS, opts: { preserveImportant?: boolean } = {}): string {
|
|
425
436
|
if (value.length <= maxChars) return value;
|
|
426
437
|
// L4: head + tail instead of head-only. Keeps closing markdown structure
|
|
427
438
|
// (code fences, headings, list tails) instead of dropping them — the old
|
|
@@ -438,7 +449,11 @@ export function compactString(
|
|
|
438
449
|
// P0-B: the TruncationStage scans the middle slice for important diagnostic
|
|
439
450
|
// lines (error, file:line, HTTP 4xx/5xx, compiler codes) and preserves them
|
|
440
451
|
// within a 15% slack budget. The `preserveImportant` opt propagates here.
|
|
441
|
-
const result = applyCompactPipeline(value, [
|
|
452
|
+
const result = applyCompactPipeline(value, [
|
|
453
|
+
new TruncationStage(maxChars, {
|
|
454
|
+
preserveImportant: opts.preserveImportant,
|
|
455
|
+
}),
|
|
456
|
+
]);
|
|
442
457
|
return result.text;
|
|
443
458
|
}
|
|
444
459
|
|
|
@@ -466,9 +481,30 @@ export function compactValue(value: unknown): unknown {
|
|
|
466
481
|
function compactContentPart(part: unknown): unknown | undefined {
|
|
467
482
|
const record = asRecord(part);
|
|
468
483
|
if (!record) return undefined;
|
|
469
|
-
if (record.type === "text")
|
|
470
|
-
|
|
471
|
-
|
|
484
|
+
if (record.type === "text")
|
|
485
|
+
return {
|
|
486
|
+
type: "text",
|
|
487
|
+
text:
|
|
488
|
+
typeof record.text === "string"
|
|
489
|
+
? compactString(record.text, MAX_ASSISTANT_TEXT_CHARS, {
|
|
490
|
+
preserveImportant: false,
|
|
491
|
+
})
|
|
492
|
+
: "",
|
|
493
|
+
};
|
|
494
|
+
if (record.type === "toolCall")
|
|
495
|
+
return {
|
|
496
|
+
type: "toolCall",
|
|
497
|
+
name: record.name,
|
|
498
|
+
input: compactValue(typeof record.input === "string" ? compactString(record.input, MAX_TOOL_INPUT_CHARS) : record.input),
|
|
499
|
+
};
|
|
500
|
+
if (record.type === "toolResult")
|
|
501
|
+
return {
|
|
502
|
+
type: "toolResult",
|
|
503
|
+
name: record.name,
|
|
504
|
+
content: compactValue(
|
|
505
|
+
typeof record.content === "string" ? compactString(record.content, MAX_TOOL_RESULT_CHARS) : record.content,
|
|
506
|
+
),
|
|
507
|
+
};
|
|
472
508
|
return undefined;
|
|
473
509
|
}
|
|
474
510
|
|
|
@@ -477,16 +513,33 @@ function compactChildPiEvent(event: unknown): unknown | undefined {
|
|
|
477
513
|
if (!record) return undefined;
|
|
478
514
|
if (record.type === "message_update") return undefined;
|
|
479
515
|
if (record.type === "tool_execution_start" || record.type === "tool_execution_end") {
|
|
480
|
-
return {
|
|
516
|
+
return {
|
|
517
|
+
type: record.type,
|
|
518
|
+
toolName: record.toolName,
|
|
519
|
+
args: record.args,
|
|
520
|
+
};
|
|
481
521
|
}
|
|
482
522
|
if (record.type === "tool_result_end" || record.type === "message_end" || record.type === "message") {
|
|
483
523
|
const message = asRecord(record.message);
|
|
484
524
|
if (message?.role === "user" || message?.role === "system") return undefined;
|
|
485
|
-
const content = Array.isArray(message?.content)
|
|
525
|
+
const content = Array.isArray(message?.content)
|
|
526
|
+
? message.content.map(compactContentPart).filter((part) => part !== undefined)
|
|
527
|
+
: undefined;
|
|
486
528
|
return {
|
|
487
529
|
type: record.type,
|
|
488
530
|
...(typeof record.text === "string" ? { text: record.text } : {}),
|
|
489
|
-
...(message
|
|
531
|
+
...(message
|
|
532
|
+
? {
|
|
533
|
+
message: {
|
|
534
|
+
role: message.role,
|
|
535
|
+
...(content ? { content } : {}),
|
|
536
|
+
usage: message.usage,
|
|
537
|
+
model: message.model,
|
|
538
|
+
errorMessage: message.errorMessage,
|
|
539
|
+
stopReason: message.stopReason,
|
|
540
|
+
},
|
|
541
|
+
}
|
|
542
|
+
: {}),
|
|
490
543
|
usage: record.usage,
|
|
491
544
|
model: record.model,
|
|
492
545
|
provider: record.provider,
|
|
@@ -506,18 +559,31 @@ function displayTextFromCompactEvent(event: unknown): string | undefined {
|
|
|
506
559
|
const message = asRecord(record.message);
|
|
507
560
|
if (message?.role !== undefined && message.role !== "assistant") return undefined;
|
|
508
561
|
const content = Array.isArray(message?.content) ? message.content : [];
|
|
509
|
-
const text = content
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
562
|
+
const text = content
|
|
563
|
+
.flatMap((part) => {
|
|
564
|
+
const item = asRecord(part);
|
|
565
|
+
return item?.type === "text" && typeof item.text === "string" ? [item.text] : [];
|
|
566
|
+
})
|
|
567
|
+
.join("\n")
|
|
568
|
+
.trim();
|
|
513
569
|
return text || (typeof record.text === "string" ? record.text : undefined);
|
|
514
570
|
}
|
|
515
571
|
|
|
516
|
-
function compactChildPiLine(line: string): {
|
|
572
|
+
function compactChildPiLine(line: string): {
|
|
573
|
+
persistedLine: string;
|
|
574
|
+
event?: unknown;
|
|
575
|
+
displayLine?: string;
|
|
576
|
+
json: boolean;
|
|
577
|
+
} {
|
|
517
578
|
try {
|
|
518
579
|
const parsed = JSON.parse(line);
|
|
519
580
|
const compact = compactChildPiEvent(parsed);
|
|
520
|
-
return {
|
|
581
|
+
return {
|
|
582
|
+
json: true,
|
|
583
|
+
event: compact,
|
|
584
|
+
persistedLine: compact ? JSON.stringify(compact) : "",
|
|
585
|
+
displayLine: displayTextFromCompactEvent(compact),
|
|
586
|
+
};
|
|
521
587
|
} catch {
|
|
522
588
|
return { json: false, persistedLine: line, displayLine: line };
|
|
523
589
|
}
|
|
@@ -632,7 +698,7 @@ function observeStdoutChunk(input: ChildPiRunInput, text: string): void {
|
|
|
632
698
|
}
|
|
633
699
|
|
|
634
700
|
function asRecord(value: unknown): Record<string, unknown> | undefined {
|
|
635
|
-
return value && typeof value === "object" && !Array.isArray(value) ? value as Record<string, unknown> : undefined;
|
|
701
|
+
return value && typeof value === "object" && !Array.isArray(value) ? (value as Record<string, unknown>) : undefined;
|
|
636
702
|
}
|
|
637
703
|
|
|
638
704
|
function isFinalAssistantEvent(event: unknown): boolean {
|
|
@@ -641,7 +707,8 @@ function isFinalAssistantEvent(event: unknown): boolean {
|
|
|
641
707
|
const message = asRecord(obj.message);
|
|
642
708
|
const role = message?.role;
|
|
643
709
|
if (role !== undefined && role !== "assistant") return false;
|
|
644
|
-
const stopReason =
|
|
710
|
+
const stopReason =
|
|
711
|
+
typeof message?.stopReason === "string" ? message.stopReason : typeof obj.stopReason === "string" ? obj.stopReason : undefined;
|
|
645
712
|
if (stopReason !== undefined && stopReason !== "stop") return false;
|
|
646
713
|
const content = Array.isArray(message?.content) ? message.content : [];
|
|
647
714
|
return !content.some((part) => asRecord(part)?.type === "toolCall");
|
|
@@ -651,11 +718,17 @@ export async function runChildPi(input: ChildPiRunInput): Promise<ChildPiRunResu
|
|
|
651
718
|
// Phase 1 (live-session parity): prepend parent context when inheritContext is true.
|
|
652
719
|
// This mirrors the effectivePrompt logic in live-session-runtime.ts so that
|
|
653
720
|
// child-process workers receive the same inherited-context treatment.
|
|
654
|
-
const effectiveTask =
|
|
655
|
-
|
|
656
|
-
|
|
721
|
+
const effectiveTask =
|
|
722
|
+
input.inheritContext === true && input.parentContext
|
|
723
|
+
? `${input.parentContext}\n\n---\n# Child Worker Task\n${input.task}`
|
|
724
|
+
: input.task;
|
|
657
725
|
const depth = checkCrewDepth(input.maxDepth);
|
|
658
|
-
if (depth.blocked)
|
|
726
|
+
if (depth.blocked)
|
|
727
|
+
return {
|
|
728
|
+
exitCode: 1,
|
|
729
|
+
stdout: "",
|
|
730
|
+
stderr: `pi-crew depth guard blocked child worker: depth ${depth.depth} >= max ${depth.maxDepth}`,
|
|
731
|
+
};
|
|
659
732
|
const mock = process.env.PI_TEAMS_MOCK_CHILD_PI;
|
|
660
733
|
if (mock) {
|
|
661
734
|
// SECURITY (Issue #2): Mock mode security model is intentionally asymmetric.
|
|
@@ -671,7 +744,11 @@ export async function runChildPi(input: ChildPiRunInput): Promise<ChildPiRunResu
|
|
|
671
744
|
// Setup hooks cannot inject PI_CREW_ALLOW_MOCK into the parent's env.
|
|
672
745
|
const allowMock = process.env.PI_CREW_ALLOW_MOCK === "1" || process.env.PI_CREW_ALLOW_MOCK === "true";
|
|
673
746
|
if (!allowMock) {
|
|
674
|
-
return {
|
|
747
|
+
return {
|
|
748
|
+
exitCode: 1,
|
|
749
|
+
stdout: "",
|
|
750
|
+
stderr: "Mock mode requires PI_CREW_ALLOW_MOCK=1",
|
|
751
|
+
};
|
|
675
752
|
}
|
|
676
753
|
// SECURITY: Log mock mode activation prominently for audit trail
|
|
677
754
|
logInternalError("child-pi.mock", new Error(`Mock mode active: ${mock}`), "NOT running real agents");
|
|
@@ -681,14 +758,66 @@ export async function runChildPi(input: ChildPiRunInput): Promise<ChildPiRunResu
|
|
|
681
758
|
return { exitCode: 0, stdout, stderr: "" };
|
|
682
759
|
}
|
|
683
760
|
if (mock === "json-success" || mock === "adaptive-plan") {
|
|
684
|
-
const text =
|
|
685
|
-
|
|
686
|
-
|
|
761
|
+
const text =
|
|
762
|
+
mock === "adaptive-plan" && effectiveTask.includes("ADAPTIVE_PLAN_JSON_START")
|
|
763
|
+
? `[MOCK] Adaptive plan\nADAPTIVE_PLAN_JSON_START\n${JSON.stringify({
|
|
764
|
+
phases: [
|
|
765
|
+
{
|
|
766
|
+
name: "research",
|
|
767
|
+
tasks: [
|
|
768
|
+
{
|
|
769
|
+
role: "explorer",
|
|
770
|
+
task: "Explore adaptive target",
|
|
771
|
+
},
|
|
772
|
+
{
|
|
773
|
+
role: "analyst",
|
|
774
|
+
task: "Analyze adaptive target",
|
|
775
|
+
},
|
|
776
|
+
{
|
|
777
|
+
role: "planner",
|
|
778
|
+
task: "Plan adaptive target",
|
|
779
|
+
},
|
|
780
|
+
],
|
|
781
|
+
},
|
|
782
|
+
{
|
|
783
|
+
name: "build",
|
|
784
|
+
tasks: [
|
|
785
|
+
{
|
|
786
|
+
role: "executor",
|
|
787
|
+
task: "Implement adaptive target",
|
|
788
|
+
},
|
|
789
|
+
],
|
|
790
|
+
},
|
|
791
|
+
{
|
|
792
|
+
name: "check",
|
|
793
|
+
tasks: [
|
|
794
|
+
{
|
|
795
|
+
role: "reviewer",
|
|
796
|
+
task: "Review adaptive target",
|
|
797
|
+
},
|
|
798
|
+
{
|
|
799
|
+
role: "test-engineer",
|
|
800
|
+
task: "Test adaptive target",
|
|
801
|
+
},
|
|
802
|
+
{
|
|
803
|
+
role: "writer",
|
|
804
|
+
task: "Summarize adaptive target",
|
|
805
|
+
},
|
|
806
|
+
],
|
|
807
|
+
},
|
|
808
|
+
],
|
|
809
|
+
})}\nADAPTIVE_PLAN_JSON_END`
|
|
810
|
+
: `[MOCK] JSON success for ${input.agent.name}`;
|
|
687
811
|
const stdout = `${JSON.stringify({ type: "message", message: { role: "assistant", content: [{ type: "text", text }] } })}\n${JSON.stringify({ type: "message_end", usage: { input: 10, output: 5, cost: 0.001, turns: 1 } })}\n`;
|
|
688
812
|
observeStdoutChunk(input, stdout);
|
|
689
813
|
return { exitCode: 0, stdout, stderr: "" };
|
|
690
814
|
}
|
|
691
|
-
if (mock === "retryable-failure")
|
|
815
|
+
if (mock === "retryable-failure")
|
|
816
|
+
return {
|
|
817
|
+
exitCode: 1,
|
|
818
|
+
stdout: "",
|
|
819
|
+
stderr: "[MOCK] rate limit: mock failure",
|
|
820
|
+
};
|
|
692
821
|
// E2E fallback-chain fixture: invocation #1 returns a SILENT retryable
|
|
693
822
|
// failure (exit code 0, no real assistant text, message_end carries a
|
|
694
823
|
// retryable-pattern errorMessage). Invocation #2+ delegates to the
|
|
@@ -740,7 +869,15 @@ export async function runChildPi(input: ChildPiRunInput): Promise<ChildPiRunResu
|
|
|
740
869
|
}
|
|
741
870
|
return { exitCode: 1, stdout: "", stderr: `[MOCK] failure: ${mock}` };
|
|
742
871
|
}
|
|
743
|
-
const built = buildPiWorkerArgs({
|
|
872
|
+
const built = buildPiWorkerArgs({
|
|
873
|
+
task: effectiveTask,
|
|
874
|
+
agent: input.agent,
|
|
875
|
+
model: input.model,
|
|
876
|
+
sessionEnabled: true,
|
|
877
|
+
maxDepth: input.maxDepth,
|
|
878
|
+
skillPaths: input.skillPaths,
|
|
879
|
+
role: input.role,
|
|
880
|
+
});
|
|
744
881
|
const spawnSpec = getPiSpawnCommand(built.args);
|
|
745
882
|
try {
|
|
746
883
|
return await new Promise<ChildPiRunResult>((resolve) => {
|
|
@@ -755,20 +892,37 @@ export async function runChildPi(input: ChildPiRunInput): Promise<ChildPiRunResu
|
|
|
755
892
|
// built.env, the assertion will throw before the secret reaches the child process.
|
|
756
893
|
for (const key of Object.keys(built.env)) {
|
|
757
894
|
if (!key.startsWith("PI_CREW_") && !key.startsWith("PI_TEAMS_")) {
|
|
758
|
-
throw new Error(
|
|
895
|
+
throw new Error(
|
|
896
|
+
`SECURITY: built.env contains unexpected key "${key}"; expected only PI_CREW_* or PI_TEAMS_* execution-control vars`,
|
|
897
|
+
);
|
|
759
898
|
}
|
|
760
899
|
}
|
|
761
|
-
const child = spawn(
|
|
900
|
+
const child = spawn(
|
|
901
|
+
spawnSpec.command,
|
|
902
|
+
spawnSpec.args,
|
|
903
|
+
buildChildPiSpawnOptions(input.cwd, {
|
|
904
|
+
...process.env,
|
|
905
|
+
...built.env,
|
|
906
|
+
}),
|
|
907
|
+
);
|
|
762
908
|
if (child.pid) {
|
|
763
909
|
activeChildProcesses.set(child.pid, child);
|
|
764
910
|
input.onSpawn?.(child.pid);
|
|
765
|
-
input.onLifecycleEvent?.({
|
|
911
|
+
input.onLifecycleEvent?.({
|
|
912
|
+
type: "spawned",
|
|
913
|
+
pid: child.pid,
|
|
914
|
+
ts: new Date().toISOString(),
|
|
915
|
+
});
|
|
766
916
|
// Register with cleanup handler for graceful shutdown
|
|
767
917
|
if (input.runId && input.agentId) {
|
|
768
918
|
registerChildProcess(child.pid, input.runId, input.agentId);
|
|
769
919
|
}
|
|
770
920
|
} else {
|
|
771
|
-
input.onLifecycleEvent?.({
|
|
921
|
+
input.onLifecycleEvent?.({
|
|
922
|
+
type: "spawn_error",
|
|
923
|
+
error: "spawn returned no pid",
|
|
924
|
+
ts: new Date().toISOString(),
|
|
925
|
+
});
|
|
772
926
|
}
|
|
773
927
|
let stdout = "";
|
|
774
928
|
let stderr = "";
|
|
@@ -797,8 +951,11 @@ export async function runChildPi(input: ChildPiRunInput): Promise<ChildPiRunResu
|
|
|
797
951
|
const RESPONSE_TIMEOUT_MIN_MS = 1_000;
|
|
798
952
|
const RESPONSE_TIMEOUT_MAX_MS = 3_600_000;
|
|
799
953
|
const responseTimeoutEnv = Number.parseInt(process.env.PI_TEAMS_CHILD_RESPONSE_TIMEOUT_MS ?? "", 10);
|
|
800
|
-
const envInRange =
|
|
801
|
-
|
|
954
|
+
const envInRange =
|
|
955
|
+
Number.isFinite(responseTimeoutEnv) &&
|
|
956
|
+
responseTimeoutEnv >= RESPONSE_TIMEOUT_MIN_MS &&
|
|
957
|
+
responseTimeoutEnv <= RESPONSE_TIMEOUT_MAX_MS;
|
|
958
|
+
const responseTimeoutMs = envInRange ? responseTimeoutEnv : (input.responseTimeoutMs ?? RESPONSE_TIMEOUT_MS);
|
|
802
959
|
let responseTimeoutHit = false;
|
|
803
960
|
let forcedFinalDrain = false;
|
|
804
961
|
let abortRequested = input.signal?.aborted === true;
|
|
@@ -849,8 +1006,12 @@ export async function runChildPi(input: ChildPiRunInput): Promise<ChildPiRunResu
|
|
|
849
1006
|
// MaxListenersExceededWarning and each leaked listener pinned the task's
|
|
850
1007
|
// stack frame (abortDueToParentSignal closure) in memory. { once: true }
|
|
851
1008
|
// only auto-removes AFTER the signal fires; on normal completion it leaks.
|
|
852
|
-
const onParentAbort = (): void => {
|
|
853
|
-
|
|
1009
|
+
const onParentAbort = (): void => {
|
|
1010
|
+
abortDueToParentSignal = true;
|
|
1011
|
+
};
|
|
1012
|
+
input.signal?.addEventListener("abort", onParentAbort, {
|
|
1013
|
+
once: true,
|
|
1014
|
+
});
|
|
854
1015
|
const restartNoResponseTimer = (): void => {
|
|
855
1016
|
if (responseTimeoutMs <= 0) return;
|
|
856
1017
|
if (noResponseTimer) clearTimeout(noResponseTimer);
|
|
@@ -860,7 +1021,13 @@ export async function runChildPi(input: ChildPiRunInput): Promise<ChildPiRunResu
|
|
|
860
1021
|
// SEC-1: redact secrets before embedding in lifecycle event so
|
|
861
1022
|
// worker-emitted secrets (API keys etc.) don't bypass redaction.
|
|
862
1023
|
const timeoutStderr = redactStderrExcerpt(stderr, 1024); // Last 1KB of stderr (redacted, SEC-1)
|
|
863
|
-
input.onLifecycleEvent?.({
|
|
1024
|
+
input.onLifecycleEvent?.({
|
|
1025
|
+
type: "response_timeout",
|
|
1026
|
+
pid: child.pid,
|
|
1027
|
+
error: `No output for ${responseTimeoutMs}ms`,
|
|
1028
|
+
ts: new Date().toISOString(),
|
|
1029
|
+
stderr: timeoutStderr || undefined,
|
|
1030
|
+
});
|
|
864
1031
|
killProcessTree(child.pid, child);
|
|
865
1032
|
try {
|
|
866
1033
|
child.kill(process.platform === "win32" ? undefined : "SIGTERM");
|
|
@@ -946,24 +1113,45 @@ export async function runChildPi(input: ChildPiRunInput): Promise<ChildPiRunResu
|
|
|
946
1113
|
// disableTools/maxTurns:1 exit-null bug). Just log + let the hard-abort
|
|
947
1114
|
// path handle a genuinely runaway worker.
|
|
948
1115
|
if (child.stdin?.writable) {
|
|
949
|
-
const steerPayload =
|
|
1116
|
+
const steerPayload =
|
|
1117
|
+
JSON.stringify({
|
|
1118
|
+
type: "steer",
|
|
1119
|
+
message:
|
|
1120
|
+
"You have reached your turn limit. Wrap up immediately — provide your final answer now.",
|
|
1121
|
+
}) + "\n";
|
|
950
1122
|
const writeSucceeded = child.stdin.write(steerPayload);
|
|
951
1123
|
if (!writeSucceeded) {
|
|
952
1124
|
// Normal Node backpressure: the payload is buffered and will flush on
|
|
953
1125
|
// 'drain'. NOT a failure — do NOT kill the worker. The steer is
|
|
954
1126
|
// advisory; if the worker ignores it and runs past maxTurns +
|
|
955
1127
|
// graceTurns, the hard-abort below terminates it.
|
|
956
|
-
logInternalError(
|
|
1128
|
+
logInternalError(
|
|
1129
|
+
"child-pi.steer-backpressure",
|
|
1130
|
+
new Error(
|
|
1131
|
+
"stdin write returned false (normal backpressure); steer buffered, worker NOT killed",
|
|
1132
|
+
),
|
|
1133
|
+
`pid=${child.pid}`,
|
|
1134
|
+
);
|
|
957
1135
|
}
|
|
958
1136
|
} else {
|
|
959
1137
|
// stdin closed (worker already finished) or otherwise unwritable.
|
|
960
1138
|
// Also advisory — the worker is done or nearly done; let it exit
|
|
961
1139
|
// naturally. Hard-abort remains the safety net for true runaways.
|
|
962
|
-
logInternalError(
|
|
1140
|
+
logInternalError(
|
|
1141
|
+
"child-pi.steer-not-writable",
|
|
1142
|
+
new Error(
|
|
1143
|
+
"stdin not writable when attempting steer injection (worker may be done); worker NOT killed",
|
|
1144
|
+
),
|
|
1145
|
+
`pid=${child.pid}`,
|
|
1146
|
+
);
|
|
963
1147
|
}
|
|
964
1148
|
} else if (maxTurns !== undefined && softLimitReached && turnCount >= maxTurns + (graceTurns ?? 5)) {
|
|
965
1149
|
// Hard abort — terminate after grace turns
|
|
966
|
-
try {
|
|
1150
|
+
try {
|
|
1151
|
+
child.kill(process.platform === "win32" ? undefined : "SIGTERM");
|
|
1152
|
+
} catch {
|
|
1153
|
+
/* best-effort */
|
|
1154
|
+
}
|
|
967
1155
|
}
|
|
968
1156
|
}
|
|
969
1157
|
}
|
|
@@ -980,7 +1168,11 @@ export async function runChildPi(input: ChildPiRunInput): Promise<ChildPiRunResu
|
|
|
980
1168
|
if (settled || childExited) return;
|
|
981
1169
|
forcedFinalDrain = true;
|
|
982
1170
|
finalDrainFiredMonotonicMs = performance.now(); // Phase-0 diagnostic: race timing.
|
|
983
|
-
input.onLifecycleEvent?.({
|
|
1171
|
+
input.onLifecycleEvent?.({
|
|
1172
|
+
type: "final_drain",
|
|
1173
|
+
pid: child.pid,
|
|
1174
|
+
ts: new Date().toISOString(),
|
|
1175
|
+
});
|
|
984
1176
|
try {
|
|
985
1177
|
child.kill(process.platform === "win32" ? undefined : "SIGTERM");
|
|
986
1178
|
} catch (error) {
|
|
@@ -990,7 +1182,11 @@ export async function runChildPi(input: ChildPiRunInput): Promise<ChildPiRunResu
|
|
|
990
1182
|
if (settled || childExited) return;
|
|
991
1183
|
try {
|
|
992
1184
|
hardKilled = true;
|
|
993
|
-
input.onLifecycleEvent?.({
|
|
1185
|
+
input.onLifecycleEvent?.({
|
|
1186
|
+
type: "hard_kill",
|
|
1187
|
+
pid: child.pid,
|
|
1188
|
+
ts: new Date().toISOString(),
|
|
1189
|
+
});
|
|
994
1190
|
child.kill(process.platform === "win32" ? undefined : "SIGKILL");
|
|
995
1191
|
} catch (error) {
|
|
996
1192
|
logInternalError("child-pi.final-drain-kill", error, `pid=${child.pid}`);
|
|
@@ -1051,7 +1247,7 @@ export async function runChildPi(input: ChildPiRunInput): Promise<ChildPiRunResu
|
|
|
1051
1247
|
finalDrainArmed,
|
|
1052
1248
|
forcedFinalDrain,
|
|
1053
1249
|
finalDrainFiredMonotonicMs,
|
|
1054
|
-
|
|
1250
|
+
}
|
|
1055
1251
|
: {}),
|
|
1056
1252
|
cleanupErrors,
|
|
1057
1253
|
finalDrainMs,
|
|
@@ -1099,7 +1295,11 @@ export async function runChildPi(input: ChildPiRunInput): Promise<ChildPiRunResu
|
|
|
1099
1295
|
let backpressureBytes = 0;
|
|
1100
1296
|
const releaseBackpressure = (): void => {
|
|
1101
1297
|
backpressureBytes = 0;
|
|
1102
|
-
try {
|
|
1298
|
+
try {
|
|
1299
|
+
child.stdout?.resume();
|
|
1300
|
+
} catch {
|
|
1301
|
+
/* ignore */
|
|
1302
|
+
}
|
|
1103
1303
|
};
|
|
1104
1304
|
child.stdout?.on("data", (chunk: Buffer) => {
|
|
1105
1305
|
restartNoResponseTimer();
|
|
@@ -1111,7 +1311,11 @@ export async function runChildPi(input: ChildPiRunInput): Promise<ChildPiRunResu
|
|
|
1111
1311
|
logInternalError("child-pi.line-observer-observe", err, `text=${text.slice(0, 100)}`);
|
|
1112
1312
|
}
|
|
1113
1313
|
if (backpressureBytes > BACKPRESSURE_HIGH && child.stdout && !child.stdout.isPaused()) {
|
|
1114
|
-
try {
|
|
1314
|
+
try {
|
|
1315
|
+
child.stdout.pause();
|
|
1316
|
+
} catch {
|
|
1317
|
+
/* ignore */
|
|
1318
|
+
}
|
|
1115
1319
|
const timer = setTimeout(releaseBackpressure, 50);
|
|
1116
1320
|
timer.unref();
|
|
1117
1321
|
}
|
|
@@ -1128,11 +1332,37 @@ export async function runChildPi(input: ChildPiRunInput): Promise<ChildPiRunResu
|
|
|
1128
1332
|
);
|
|
1129
1333
|
rejectPendingOperations(processError);
|
|
1130
1334
|
try {
|
|
1131
|
-
input.onLifecycleEvent?.({
|
|
1335
|
+
input.onLifecycleEvent?.({
|
|
1336
|
+
type: "spawn_error",
|
|
1337
|
+
pid: child.pid,
|
|
1338
|
+
error: processError.message,
|
|
1339
|
+
ts: new Date().toISOString(),
|
|
1340
|
+
stderrExcerpt: redactStderrExcerpt(stderr, 500) || undefined,
|
|
1341
|
+
});
|
|
1132
1342
|
} catch (err) {
|
|
1133
1343
|
logInternalError("child-pi.on-lifecycle-event", err, `event=error, pid=${child.pid}`);
|
|
1134
1344
|
}
|
|
1135
|
-
settle({
|
|
1345
|
+
settle({
|
|
1346
|
+
exitCode: null,
|
|
1347
|
+
stdout,
|
|
1348
|
+
stderr,
|
|
1349
|
+
error: processError.message,
|
|
1350
|
+
exitStatus: {
|
|
1351
|
+
exitCode: null,
|
|
1352
|
+
cancelled: abortRequested,
|
|
1353
|
+
timedOut: responseTimeoutHit,
|
|
1354
|
+
killed: false,
|
|
1355
|
+
cleanupErrors,
|
|
1356
|
+
finalDrainMs,
|
|
1357
|
+
crashClass: classifyProcessCrash({
|
|
1358
|
+
exitCode: null,
|
|
1359
|
+
cancelled: abortRequested,
|
|
1360
|
+
timedOut: responseTimeoutHit,
|
|
1361
|
+
spawnError: error,
|
|
1362
|
+
stderrSnippet: stderr ? redactStderrExcerpt(stderr, 1000) : undefined,
|
|
1363
|
+
}).crashClass,
|
|
1364
|
+
},
|
|
1365
|
+
});
|
|
1136
1366
|
});
|
|
1137
1367
|
child.on("exit", (code, signal) => {
|
|
1138
1368
|
if (child.pid) {
|
|
@@ -1150,9 +1380,9 @@ export async function runChildPi(input: ChildPiRunInput): Promise<ChildPiRunResu
|
|
|
1150
1380
|
const isUnexpectedExit = !childExited && !settled && !responseTimeoutHit && !abortRequested && abnormalExit;
|
|
1151
1381
|
const exitError = isUnexpectedExit
|
|
1152
1382
|
? new Error(
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1383
|
+
`Child Pi process exited unexpectedly (code=${code ?? "null"} signal=${signal ?? "null"}). ` +
|
|
1384
|
+
`Stderr: ${redactStderrExcerpt(stderr, 1000) || "(none)"}`,
|
|
1385
|
+
)
|
|
1156
1386
|
: null;
|
|
1157
1387
|
if (exitError) {
|
|
1158
1388
|
rejectPendingOperations(exitError);
|
|
@@ -1179,7 +1409,7 @@ export async function runChildPi(input: ChildPiRunInput): Promise<ChildPiRunResu
|
|
|
1179
1409
|
finalAssistantEventMonotonicMs,
|
|
1180
1410
|
exitMonotonicMs: performance.now() - spawnMonotonicMs,
|
|
1181
1411
|
},
|
|
1182
|
-
|
|
1412
|
+
}
|
|
1183
1413
|
: {}),
|
|
1184
1414
|
});
|
|
1185
1415
|
} catch (err) {
|
|
@@ -1203,18 +1433,36 @@ export async function runChildPi(input: ChildPiRunInput): Promise<ChildPiRunResu
|
|
|
1203
1433
|
unregisterChildProcess(child.pid);
|
|
1204
1434
|
}
|
|
1205
1435
|
try {
|
|
1206
|
-
input.onLifecycleEvent?.({
|
|
1436
|
+
input.onLifecycleEvent?.({
|
|
1437
|
+
type: "close",
|
|
1438
|
+
pid: child.pid,
|
|
1439
|
+
exitCode,
|
|
1440
|
+
ts: new Date().toISOString(),
|
|
1441
|
+
});
|
|
1207
1442
|
} catch (err) {
|
|
1208
1443
|
logInternalError("child-pi.on-lifecycle-event", err, `event=close, pid=${child.pid}`);
|
|
1209
1444
|
}
|
|
1210
|
-
const timeoutError =
|
|
1445
|
+
const timeoutError =
|
|
1446
|
+
responseTimeoutHit && !stderr.trim()
|
|
1447
|
+
? {
|
|
1448
|
+
error: `Child Pi produced no new output for ${responseTimeoutMs}ms; process was terminated as unresponsive.`,
|
|
1449
|
+
}
|
|
1450
|
+
: responseTimeoutHit && stderr.trim()
|
|
1451
|
+
? {
|
|
1452
|
+
error: `Child Pi timed out after ${responseTimeoutMs}ms with stderr: ${redactStderrExcerpt(stderr, 500)}`,
|
|
1453
|
+
}
|
|
1454
|
+
: undefined;
|
|
1211
1455
|
// M6 fix: log when forced final drain converts non-zero exit to 0.
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1456
|
+
// This is expected in normal operation (child finished cleanly but linger was killed),
|
|
1457
|
+
// but the telemetry helps detect regressions where crashes are hidden.
|
|
1458
|
+
if (forcedFinalDrain && !timeoutError && exitCode !== 0) {
|
|
1459
|
+
logInternalError(
|
|
1460
|
+
"child-pi.final-drain-zero-exit",
|
|
1461
|
+
new Error(`Child exit code overridden to 0 after forced final drain (original=${exitCode})`),
|
|
1462
|
+
`pid=${child.pid}, finalDrainMs=${finalDrainMs}`,
|
|
1463
|
+
);
|
|
1464
|
+
}
|
|
1465
|
+
const finalExitCode = forcedFinalDrain && !timeoutError ? 0 : exitCode;
|
|
1218
1466
|
const wasGraceAborted = softLimitReached && turnCount >= (maxTurns ?? 0) + (graceTurns ?? 5);
|
|
1219
1467
|
const wasParentAborted = abortDueToParentSignal && !wasGraceAborted;
|
|
1220
1468
|
// steerInjectionFailed is now always false (Phase-1 fix: steer backpressure
|
|
@@ -1233,7 +1481,24 @@ export async function runChildPi(input: ChildPiRunInput): Promise<ChildPiRunResu
|
|
|
1233
1481
|
spawnError: undefined,
|
|
1234
1482
|
stderrSnippet: stderr ? redactStderrExcerpt(stderr, 1000) : undefined,
|
|
1235
1483
|
});
|
|
1236
|
-
settle({
|
|
1484
|
+
settle({
|
|
1485
|
+
exitCode: finalExitCode,
|
|
1486
|
+
stdout,
|
|
1487
|
+
stderr,
|
|
1488
|
+
...(timeoutError ? { error: timeoutError.error } : {}),
|
|
1489
|
+
...(steerError ? { error: steerError } : {}),
|
|
1490
|
+
aborted: wasGraceAborted || wasParentAborted,
|
|
1491
|
+
steered: softLimitReached && !wasGraceAborted,
|
|
1492
|
+
exitStatus: {
|
|
1493
|
+
exitCode: finalExitCode,
|
|
1494
|
+
cancelled: abortRequested,
|
|
1495
|
+
timedOut: responseTimeoutHit,
|
|
1496
|
+
killed: hardKilled,
|
|
1497
|
+
cleanupErrors,
|
|
1498
|
+
finalDrainMs,
|
|
1499
|
+
crashClass: crashClassification.crashClass,
|
|
1500
|
+
},
|
|
1501
|
+
});
|
|
1237
1502
|
});
|
|
1238
1503
|
});
|
|
1239
1504
|
} finally {
|