pi-crew 0.9.16 → 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 +81 -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 +145 -47
- 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 +44 -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
|
@@ -134,11 +134,7 @@ export function resultDigest(toolName: string, content: string, isError?: boolea
|
|
|
134
134
|
const files = text.match(/(\d+)\s+files?/i) ?? text.match(/filesWithMatches["']?:\s*(\d+)/i);
|
|
135
135
|
const error = firstErrorLine(text);
|
|
136
136
|
return (
|
|
137
|
-
[
|
|
138
|
-
match ? `matches=${match[1]}` : undefined,
|
|
139
|
-
files ? `files=${files[1]}` : undefined,
|
|
140
|
-
error ? `error=${error}` : undefined,
|
|
141
|
-
]
|
|
137
|
+
[match ? `matches=${match[1]}` : undefined, files ? `files=${files[1]}` : undefined, error ? `error=${error}` : undefined]
|
|
142
138
|
.filter((part): part is string => part !== undefined)
|
|
143
139
|
.join("; ") || "search digest unavailable"
|
|
144
140
|
);
|
|
@@ -289,9 +285,7 @@ export function pruneToolOutputs(results: ToolResultEntry[], config: PruneConfig
|
|
|
289
285
|
// Staleness waives protected-tool immunity for overridable tools
|
|
290
286
|
// (e.g. a superseded `read`); the most recent result per target is
|
|
291
287
|
// never stale, so the latest read of each file stays protected.
|
|
292
|
-
const isProtected =
|
|
293
|
-
config.protectedTools.includes(entry.toolName) &&
|
|
294
|
-
!(isStale && staleOverridable.has(entry.toolName));
|
|
288
|
+
const isProtected = config.protectedTools.includes(entry.toolName) && !(isStale && staleOverridable.has(entry.toolName));
|
|
295
289
|
|
|
296
290
|
// Stale results are prunable even inside the recency protect window —
|
|
297
291
|
// they are superseded, so recency no longer implies relevance. They
|
|
@@ -325,7 +319,10 @@ export function pruneToolOutputs(results: ToolResultEntry[], config: PruneConfig
|
|
|
325
319
|
|
|
326
320
|
const prunedResults = [...results];
|
|
327
321
|
for (const candidate of candidates) {
|
|
328
|
-
prunedResults[candidate.index] = {
|
|
322
|
+
prunedResults[candidate.index] = {
|
|
323
|
+
...candidate.entry,
|
|
324
|
+
content: candidate.notice,
|
|
325
|
+
};
|
|
329
326
|
prunedIds.push(candidate.entry.id);
|
|
330
327
|
prunedCount++;
|
|
331
328
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Tool Progress Event System
|
|
3
|
-
*
|
|
3
|
+
*
|
|
4
4
|
* Provides real-time visibility into tool execution within child Pi workers.
|
|
5
|
-
*
|
|
5
|
+
*
|
|
6
6
|
* Event flow:
|
|
7
7
|
* 1. Child Pi emits JSON events on stdout (tool_execution_start, tool_execution_end, etc.)
|
|
8
8
|
* 2. child-pi.ts parses these events and passes them to onJsonEvent callback
|
|
@@ -165,9 +165,7 @@ export function formatToolProgress(progress: CrewAgentProgress, maxContextTokens
|
|
|
165
165
|
}));
|
|
166
166
|
|
|
167
167
|
// If there's a currentTool but no endedAt, it's still running
|
|
168
|
-
const currentRunning = progress.recentTools.find(
|
|
169
|
-
(t) => !t.endedAt && t.tool === progress.currentTool,
|
|
170
|
-
);
|
|
168
|
+
const currentRunning = progress.recentTools.find((t) => !t.endedAt && t.tool === progress.currentTool);
|
|
171
169
|
if (currentRunning && progress.currentTool) {
|
|
172
170
|
recentTools.push({
|
|
173
171
|
tool: progress.currentTool,
|
|
@@ -199,13 +197,13 @@ export function formatToolProgress(progress: CrewAgentProgress, maxContextTokens
|
|
|
199
197
|
*/
|
|
200
198
|
export function formatCurrentToolLine(progress: CrewAgentProgress): string {
|
|
201
199
|
if (!progress.currentTool) return "";
|
|
202
|
-
|
|
203
|
-
const args = progress.currentToolArgs
|
|
200
|
+
|
|
201
|
+
const args = progress.currentToolArgs
|
|
204
202
|
? ` ${progress.currentToolArgs.slice(0, 50)}${progress.currentToolArgs.length > 50 ? "..." : ""}`
|
|
205
203
|
: "";
|
|
206
|
-
|
|
204
|
+
|
|
207
205
|
const toolCount = progress.toolCount > 0 ? ` (${progress.toolCount})` : "";
|
|
208
|
-
|
|
206
|
+
|
|
209
207
|
return `${progress.currentTool}${args}${toolCount}`;
|
|
210
208
|
}
|
|
211
209
|
|
|
@@ -229,10 +227,7 @@ export interface ProgressBarOptions {
|
|
|
229
227
|
/**
|
|
230
228
|
* Render a progress bar for tool execution
|
|
231
229
|
*/
|
|
232
|
-
export function renderProgressBar(
|
|
233
|
-
progress: CrewAgentProgress,
|
|
234
|
-
options: ProgressBarOptions = {},
|
|
235
|
-
): string {
|
|
230
|
+
export function renderProgressBar(progress: CrewAgentProgress, options: ProgressBarOptions = {}): string {
|
|
236
231
|
const width = options.width ?? 20;
|
|
237
232
|
const showPercent = options.showPercent ?? true;
|
|
238
233
|
const showCount = options.showCount ?? true;
|
|
@@ -244,9 +239,7 @@ export function renderProgressBar(
|
|
|
244
239
|
|
|
245
240
|
const bar = "█".repeat(filled) + "░".repeat(empty);
|
|
246
241
|
const percent = showPercent ? ` ${progress.toolCount} tools` : "";
|
|
247
|
-
const tokens = progress.tokens
|
|
248
|
-
? ` | ${(progress.tokens / 1000).toFixed(1)}k tokens`
|
|
249
|
-
: "";
|
|
242
|
+
const tokens = progress.tokens ? ` | ${(progress.tokens / 1000).toFixed(1)}k tokens` : "";
|
|
250
243
|
|
|
251
244
|
return `[${bar}]${percent}${tokens}`;
|
|
252
245
|
}
|
|
@@ -270,10 +263,7 @@ export function filterToolEvents(events: ToolProgressEvent[]): ToolProgressEvent
|
|
|
270
263
|
/**
|
|
271
264
|
* Get events for a specific tool
|
|
272
265
|
*/
|
|
273
|
-
export function getEventsForTool(
|
|
274
|
-
events: ToolProgressEvent[],
|
|
275
|
-
toolName: string,
|
|
276
|
-
): ToolProgressEvent[] {
|
|
266
|
+
export function getEventsForTool(events: ToolProgressEvent[], toolName: string): ToolProgressEvent[] {
|
|
277
267
|
return events.filter((e) => {
|
|
278
268
|
if ("toolName" in e) return e.toolName === toolName;
|
|
279
269
|
return false;
|
|
@@ -285,4 +275,4 @@ export function getEventsForTool(
|
|
|
285
275
|
*/
|
|
286
276
|
export function hasError(events: ToolProgressEvent[]): boolean {
|
|
287
277
|
return events.some(isToolError);
|
|
288
|
-
}
|
|
278
|
+
}
|
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Verification Gates — ECC VERIFICATION_LOOP Pattern Implementation
|
|
3
|
-
*
|
|
3
|
+
*
|
|
4
4
|
* Implements RED/GREEN phase gates for task verification.
|
|
5
5
|
* Sequential execution: cannot skip to Phase N+1 without Phase N passing.
|
|
6
|
-
*
|
|
6
|
+
*
|
|
7
7
|
* Based on: docs/distillation/ECC-10-skills.md §2 (verification-loop)
|
|
8
|
-
*
|
|
8
|
+
*
|
|
9
9
|
* @module verification-gates
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import { spawn } from "node:child_process";
|
|
13
13
|
import * as fs from "node:fs";
|
|
14
14
|
import * as path from "node:path";
|
|
15
|
-
import { WINDOWS_ESSENTIAL_ENV_VARS } from "../utils/env-allowlist.ts";
|
|
16
15
|
import { writeArtifact } from "../state/artifact-store.ts";
|
|
17
|
-
import {
|
|
16
|
+
import type { ArtifactDescriptor, GreenLevel, VerificationCommandResult, VerificationContract } from "../state/types.ts";
|
|
17
|
+
import { WINDOWS_ESSENTIAL_ENV_VARS } from "../utils/env-allowlist.ts";
|
|
18
18
|
import { sanitizeEnvSecrets } from "../utils/env-filter.ts";
|
|
19
|
-
import
|
|
19
|
+
import { redactSecretString } from "../utils/redaction.ts";
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
22
|
* Phase 1.5 #1 (RFC 13 §6 info-disclosure mitigation): sanitize the env passed
|
|
@@ -78,9 +78,15 @@ function buildVerificationEnv(): Record<string, string> {
|
|
|
78
78
|
return { ...process.env, FORCE_COLOR: "0" };
|
|
79
79
|
}
|
|
80
80
|
const preserveRaw = process.env.PI_CREW_VERIFICATION_PRESERVE_ENV ?? process.env.PI_TEAMS_VERIFICATION_PRESERVE_ENV ?? "";
|
|
81
|
-
const preserve = preserveRaw
|
|
81
|
+
const preserve = preserveRaw
|
|
82
|
+
.split(",")
|
|
83
|
+
.map((s) => s.trim())
|
|
84
|
+
.filter((s) => s.length > 0);
|
|
82
85
|
const allowList = [...VERIFICATION_ENV_ALLOWLIST, ...preserve];
|
|
83
|
-
return {
|
|
86
|
+
return {
|
|
87
|
+
...sanitizeEnvSecrets(process.env, { allowList }),
|
|
88
|
+
FORCE_COLOR: "0",
|
|
89
|
+
};
|
|
84
90
|
}
|
|
85
91
|
|
|
86
92
|
export interface PhaseGateResult {
|
|
@@ -105,7 +111,11 @@ export interface PhaseGateBundle {
|
|
|
105
111
|
* Standard phase gate definitions for npm/TypeScript projects.
|
|
106
112
|
* Sequential enforcement: each phase must pass before proceeding.
|
|
107
113
|
*/
|
|
108
|
-
export const NPM_TYPESCRIPT_GATES: Array<{
|
|
114
|
+
export const NPM_TYPESCRIPT_GATES: Array<{
|
|
115
|
+
name: string;
|
|
116
|
+
command: string;
|
|
117
|
+
critical: boolean;
|
|
118
|
+
}> = [
|
|
109
119
|
{ name: "build", command: "npm run build 2>&1", critical: true },
|
|
110
120
|
{ name: "typecheck", command: "npx tsc --noEmit 2>&1", critical: true },
|
|
111
121
|
{ name: "lint", command: "npm run lint 2>&1", critical: false },
|
|
@@ -115,7 +125,11 @@ export const NPM_TYPESCRIPT_GATES: Array<{ name: string; command: string; critic
|
|
|
115
125
|
/**
|
|
116
126
|
* Cargo/Rust project phase gates.
|
|
117
127
|
*/
|
|
118
|
-
export const CARGO_RUST_GATES: Array<{
|
|
128
|
+
export const CARGO_RUST_GATES: Array<{
|
|
129
|
+
name: string;
|
|
130
|
+
command: string;
|
|
131
|
+
critical: boolean;
|
|
132
|
+
}> = [
|
|
119
133
|
{ name: "check", command: "cargo check 2>&1", critical: true },
|
|
120
134
|
{ name: "test", command: "cargo test 2>&1", critical: true },
|
|
121
135
|
{ name: "clippy", command: "cargo clippy 2>&1", critical: false },
|
|
@@ -160,15 +174,13 @@ function validateGateCommand(command: string): void {
|
|
|
160
174
|
);
|
|
161
175
|
}
|
|
162
176
|
const normalized = command
|
|
163
|
-
.replace(/\x1b\[[0-9;]*[a-zA-Z]/g,
|
|
164
|
-
.replace(/[\x00-\x08\x0b\x0c\x0e-\x1f]/g,
|
|
165
|
-
.replace(/\\\n/g,
|
|
166
|
-
.replace(/\s+/g,
|
|
177
|
+
.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, "") // ANSI escape sequences
|
|
178
|
+
.replace(/[\x00-\x08\x0b\x0c\x0e-\x1f]/g, "") // control chars
|
|
179
|
+
.replace(/\\\n/g, " ") // escaped newlines
|
|
180
|
+
.replace(/\s+/g, " ") // collapse whitespace
|
|
167
181
|
.trim();
|
|
168
182
|
if (DANGEROUS_SHELL_PATTERNS.test(normalized)) {
|
|
169
|
-
throw new Error(
|
|
170
|
-
`Security: verification gate command rejected (dangerous shell pattern): ${command}`,
|
|
171
|
-
);
|
|
183
|
+
throw new Error(`Security: verification gate command rejected (dangerous shell pattern): ${command}`);
|
|
172
184
|
}
|
|
173
185
|
}
|
|
174
186
|
|
|
@@ -237,7 +249,7 @@ async function executeCommand(
|
|
|
237
249
|
|
|
238
250
|
/**
|
|
239
251
|
* Run phase gates sequentially, stopping on first critical failure.
|
|
240
|
-
*
|
|
252
|
+
*
|
|
241
253
|
* @param gates - Array of phase gate definitions
|
|
242
254
|
* @param cwd - Working directory to execute commands in
|
|
243
255
|
* @param signal - Optional abort signal
|
|
@@ -312,7 +324,7 @@ export async function runPhaseGates(
|
|
|
312
324
|
/**
|
|
313
325
|
* Execute verification commands from a task's verification contract.
|
|
314
326
|
* Maps the contract commands to phase gates and runs them sequentially.
|
|
315
|
-
*
|
|
327
|
+
*
|
|
316
328
|
* @param contract - Verification contract with commands to execute
|
|
317
329
|
* @param cwd - Working directory
|
|
318
330
|
* @param runId - Run ID for artifact naming
|
|
@@ -413,15 +425,12 @@ export async function executeVerificationCommands(
|
|
|
413
425
|
/**
|
|
414
426
|
* Compute observed green level from verification results.
|
|
415
427
|
* Maps verification outcomes to green levels per ECC pattern.
|
|
416
|
-
*
|
|
428
|
+
*
|
|
417
429
|
* @param commands - Array of verification command results
|
|
418
430
|
* @param requiredLevel - Required green level from contract
|
|
419
431
|
* @returns Observed green level
|
|
420
432
|
*/
|
|
421
|
-
export function computeGreenLevelFromResults(
|
|
422
|
-
commands: VerificationCommandResult[],
|
|
423
|
-
requiredLevel: GreenLevel,
|
|
424
|
-
): GreenLevel {
|
|
433
|
+
export function computeGreenLevelFromResults(commands: VerificationCommandResult[], requiredLevel: GreenLevel): GreenLevel {
|
|
425
434
|
if (commands.length === 0) {
|
|
426
435
|
return "none";
|
|
427
436
|
}
|
|
@@ -88,10 +88,7 @@ export function snapshotManifests(cwd: string): Record<string, string> {
|
|
|
88
88
|
* @returns Sorted array of relative manifest paths that drifted. Identical
|
|
89
89
|
* snapshots yield `[]`.
|
|
90
90
|
*/
|
|
91
|
-
export function compareSnapshot(
|
|
92
|
-
a: Record<string, string>,
|
|
93
|
-
b: Record<string, string>,
|
|
94
|
-
): string[] {
|
|
91
|
+
export function compareSnapshot(a: Record<string, string>, b: Record<string, string>): string[] {
|
|
95
92
|
const drifted = new Set<string>();
|
|
96
93
|
for (const [key, hash] of Object.entries(a)) {
|
|
97
94
|
const other = b[key];
|
|
@@ -53,27 +53,57 @@ export function isWorktreeSandboxEnabled(): boolean {
|
|
|
53
53
|
*/
|
|
54
54
|
export function checkWorktreeSandboxAvailable(cwd: string): { available: true; commitSha: string } | { available: false; reason: string } {
|
|
55
55
|
if (!isWorktreeSandboxEnabled()) {
|
|
56
|
-
return {
|
|
56
|
+
return {
|
|
57
|
+
available: false,
|
|
58
|
+
reason: "PI_CREW_VERIFICATION_WORKTREE not set (opt-in)",
|
|
59
|
+
};
|
|
57
60
|
}
|
|
58
61
|
try {
|
|
59
62
|
// Is cwd inside a git repo? `git rev-parse --show-toplevel` errors out
|
|
60
63
|
// (non-zero exit) when not in a repo. execFileSync throws on non-zero.
|
|
61
|
-
const toplevel = execFileSync("git", ["rev-parse", "--show-toplevel"], {
|
|
62
|
-
|
|
64
|
+
const toplevel = execFileSync("git", ["rev-parse", "--show-toplevel"], {
|
|
65
|
+
cwd,
|
|
66
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
67
|
+
encoding: "utf-8",
|
|
68
|
+
}).trim();
|
|
69
|
+
if (!toplevel)
|
|
70
|
+
return {
|
|
71
|
+
available: false,
|
|
72
|
+
reason: "git rev-parse returned empty toplevel",
|
|
73
|
+
};
|
|
63
74
|
// Current commit SHA (this is what T_snap will pin to).
|
|
64
|
-
const commitSha = execFileSync("git", ["rev-parse", "HEAD"], {
|
|
65
|
-
|
|
75
|
+
const commitSha = execFileSync("git", ["rev-parse", "HEAD"], {
|
|
76
|
+
cwd,
|
|
77
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
78
|
+
encoding: "utf-8",
|
|
79
|
+
}).trim();
|
|
80
|
+
if (!commitSha)
|
|
81
|
+
return {
|
|
82
|
+
available: false,
|
|
83
|
+
reason: "git rev-parse HEAD returned empty SHA",
|
|
84
|
+
};
|
|
66
85
|
// Dirty index? `git status --porcelain` outputs non-empty if there are
|
|
67
86
|
// uncommitted changes. We refuse to sandbox a dirty workspace because
|
|
68
87
|
// the worktree would NOT contain the in-progress edits (T_snap would
|
|
69
88
|
// pin to a stale commit). Better to fall back + warn than silently
|
|
70
89
|
// verify against the wrong code.
|
|
71
|
-
const status = execFileSync("git", ["status", "--porcelain"], {
|
|
72
|
-
|
|
90
|
+
const status = execFileSync("git", ["status", "--porcelain"], {
|
|
91
|
+
cwd,
|
|
92
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
93
|
+
encoding: "utf-8",
|
|
94
|
+
}).trim();
|
|
95
|
+
if (status.length > 0)
|
|
96
|
+
return {
|
|
97
|
+
available: false,
|
|
98
|
+
reason: `dirty git index (${status.split("\n").length} changed files); refusing to sandbox — worktree would pin to stale commit`,
|
|
99
|
+
};
|
|
73
100
|
return { available: true, commitSha };
|
|
74
101
|
} catch (error) {
|
|
75
102
|
const msg = error instanceof Error ? error.message : String(error);
|
|
76
|
-
return {
|
|
103
|
+
return {
|
|
104
|
+
available: false,
|
|
105
|
+
reason: `git precondition check failed: ${msg.slice(0, 200)}`,
|
|
106
|
+
};
|
|
77
107
|
}
|
|
78
108
|
}
|
|
79
109
|
|
|
@@ -102,13 +132,29 @@ export function prepareVerificationWorktree(cwd: string, commitSha: string): Ver
|
|
|
102
132
|
execFileSync("git", ["worktree", "remove", "--force", worktreePath], { cwd, stdio: ["ignore", "pipe", "pipe"], timeout: 5000 });
|
|
103
133
|
} catch {
|
|
104
134
|
// Fall back to `git worktree prune` if remove fails (already gone).
|
|
105
|
-
try {
|
|
135
|
+
try {
|
|
136
|
+
execFileSync("git", ["worktree", "prune"], {
|
|
137
|
+
cwd,
|
|
138
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
139
|
+
timeout: 5000,
|
|
140
|
+
});
|
|
141
|
+
} catch {
|
|
142
|
+
/* best-effort */
|
|
143
|
+
}
|
|
106
144
|
}
|
|
107
145
|
// Remove the temp parent dir.
|
|
108
|
-
try {
|
|
146
|
+
try {
|
|
147
|
+
fs.rmSync(tmpRoot, { recursive: true, force: true });
|
|
148
|
+
} catch {
|
|
149
|
+
/* best-effort */
|
|
150
|
+
}
|
|
109
151
|
};
|
|
110
152
|
try {
|
|
111
|
-
execFileSync("git", ["worktree", "add", "--detach", worktreePath, commitSha], {
|
|
153
|
+
execFileSync("git", ["worktree", "add", "--detach", worktreePath, commitSha], {
|
|
154
|
+
cwd,
|
|
155
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
156
|
+
timeout: 30_000,
|
|
157
|
+
});
|
|
112
158
|
return { worktreePath, commitSha, cleanup };
|
|
113
159
|
} catch (error) {
|
|
114
160
|
cleanup();
|
|
@@ -126,7 +172,11 @@ export function prepareVerificationWorktree(cwd: string, commitSha: string): Ver
|
|
|
126
172
|
* If preparation fails, the function rethrows WITHOUT calling fn — caller
|
|
127
173
|
* must handle the prep failure (typically by falling back to non-sandboxed).
|
|
128
174
|
*/
|
|
129
|
-
export async function withVerificationWorktree<T>(
|
|
175
|
+
export async function withVerificationWorktree<T>(
|
|
176
|
+
cwd: string,
|
|
177
|
+
commitSha: string,
|
|
178
|
+
fn: (worktree: VerificationWorktree) => Promise<T> | T,
|
|
179
|
+
): Promise<T> {
|
|
130
180
|
const worktree = prepareVerificationWorktree(cwd, commitSha);
|
|
131
181
|
try {
|
|
132
182
|
return await fn(worktree);
|
|
@@ -12,7 +12,11 @@ export function createWorkerHeartbeat(workerId: string, pid?: number, now = new
|
|
|
12
12
|
return { workerId, pid, lastSeenAt: now.toISOString(), alive: true };
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
export function touchWorkerHeartbeat(
|
|
15
|
+
export function touchWorkerHeartbeat(
|
|
16
|
+
heartbeat: WorkerHeartbeatState,
|
|
17
|
+
updates: Partial<Omit<WorkerHeartbeatState, "workerId">> = {},
|
|
18
|
+
now = new Date(),
|
|
19
|
+
): WorkerHeartbeatState {
|
|
16
20
|
return { ...heartbeat, ...updates, lastSeenAt: now.toISOString() };
|
|
17
21
|
}
|
|
18
22
|
|
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
export type WorkerLifecycleState = "spawning" | "trust_required" | "ready_for_prompt" | "running" | "finished" | "failed";
|
|
2
|
-
export type StartupFailureClassification =
|
|
2
|
+
export type StartupFailureClassification =
|
|
3
|
+
| "trust_required"
|
|
4
|
+
| "prompt_misdelivery"
|
|
5
|
+
| "prompt_acceptance_timeout"
|
|
6
|
+
| "transport_dead"
|
|
7
|
+
| "worker_crashed"
|
|
8
|
+
| "rate_limited"
|
|
9
|
+
| "provider_error"
|
|
10
|
+
| "unknown";
|
|
3
11
|
|
|
4
12
|
export interface WorkerStartupEvidence {
|
|
5
13
|
lastLifecycleState: WorkerLifecycleState;
|
|
@@ -16,17 +24,28 @@ export interface WorkerStartupEvidence {
|
|
|
16
24
|
|
|
17
25
|
export function detectTrustPrompt(text: string): boolean {
|
|
18
26
|
const lowered = text.toLowerCase();
|
|
19
|
-
return
|
|
27
|
+
return (
|
|
28
|
+
lowered.includes("do you trust") ||
|
|
29
|
+
lowered.includes("trust this") ||
|
|
30
|
+
lowered.includes("untrusted") ||
|
|
31
|
+
lowered.includes("workspace trust") ||
|
|
32
|
+
lowered.includes("allow this folder")
|
|
33
|
+
);
|
|
20
34
|
}
|
|
21
35
|
|
|
22
36
|
export function classifyStartupFailure(evidence: Omit<WorkerStartupEvidence, "classification">): StartupFailureClassification {
|
|
23
37
|
if (evidence.stderrPreview && /429|rate.?limit/i.test(evidence.stderrPreview)) return "rate_limited";
|
|
24
|
-
if (evidence.stderrPreview && /5\d{2}|server.?error|internal.?error|provider.?error/i.test(evidence.stderrPreview))
|
|
38
|
+
if (evidence.stderrPreview && /5\d{2}|server.?error|internal.?error|provider.?error/i.test(evidence.stderrPreview))
|
|
39
|
+
return "provider_error";
|
|
25
40
|
if (!evidence.transportHealthy) return "transport_dead";
|
|
26
41
|
if (evidence.trustPromptDetected || evidence.lastLifecycleState === "trust_required") return "trust_required";
|
|
27
42
|
if (evidence.promptSentAt && !evidence.promptAccepted && evidence.childProcessAlive) return "prompt_acceptance_timeout";
|
|
28
43
|
if (evidence.promptSentAt && !evidence.promptAccepted && !evidence.childProcessAlive) return "worker_crashed";
|
|
29
|
-
if (
|
|
44
|
+
if (
|
|
45
|
+
evidence.stderrPreview?.toLowerCase().includes("command not found") ||
|
|
46
|
+
evidence.stderrPreview?.toLowerCase().includes("not recognized")
|
|
47
|
+
)
|
|
48
|
+
return "prompt_misdelivery";
|
|
30
49
|
if (!evidence.childProcessAlive && evidence.lastLifecycleState !== "finished") return "worker_crashed";
|
|
31
50
|
return "unknown";
|
|
32
51
|
}
|
|
@@ -45,7 +64,12 @@ export function createStartupEvidence(input: {
|
|
|
45
64
|
const trustPromptDetected = detectTrustPrompt(stderrPreview ?? "");
|
|
46
65
|
const childProcessAlive = input.exitCode === undefined || input.exitCode === null ? !input.finishedAt : false;
|
|
47
66
|
const base: Omit<WorkerStartupEvidence, "classification"> = {
|
|
48
|
-
lastLifecycleState:
|
|
67
|
+
lastLifecycleState:
|
|
68
|
+
input.error || (input.exitCode !== undefined && input.exitCode !== null && input.exitCode !== 0)
|
|
69
|
+
? "failed"
|
|
70
|
+
: input.finishedAt
|
|
71
|
+
? "finished"
|
|
72
|
+
: "running",
|
|
49
73
|
command: input.command,
|
|
50
74
|
promptSentAt: input.promptSentAt?.toISOString(),
|
|
51
75
|
promptAccepted: input.promptAccepted ?? !input.error,
|
|
@@ -84,20 +84,22 @@ export function getPhaseInputs(machine: WorkflowStateMachine, phaseIndex: number
|
|
|
84
84
|
* 3. All declared `inputs` for the target phase must appear in `completedArtifacts`.
|
|
85
85
|
* 4. If a custom `guard` is supplied on a transition, it must also approve.
|
|
86
86
|
*/
|
|
87
|
-
export function canTransitionToPhase(
|
|
88
|
-
machine: WorkflowStateMachine,
|
|
89
|
-
phaseIndex: number,
|
|
90
|
-
context: PhaseGuardContext,
|
|
91
|
-
): GuardResult {
|
|
87
|
+
export function canTransitionToPhase(machine: WorkflowStateMachine, phaseIndex: number, context: PhaseGuardContext): GuardResult {
|
|
92
88
|
if (phaseIndex < 0 || phaseIndex >= machine.phases.length) {
|
|
93
|
-
return {
|
|
89
|
+
return {
|
|
90
|
+
allowed: false,
|
|
91
|
+
reason: `Phase index ${phaseIndex} is out of bounds (0..${machine.phases.length - 1}).`,
|
|
92
|
+
};
|
|
94
93
|
}
|
|
95
94
|
|
|
96
95
|
// Previous phase must have completed (or been skipped).
|
|
97
96
|
if (phaseIndex > 0) {
|
|
98
97
|
const prevStatus = context.previousPhaseStatus;
|
|
99
98
|
if (prevStatus !== "completed" && prevStatus !== "skipped") {
|
|
100
|
-
return {
|
|
99
|
+
return {
|
|
100
|
+
allowed: false,
|
|
101
|
+
reason: `Previous phase status is '${prevStatus}'; expected 'completed' or 'skipped'.`,
|
|
102
|
+
};
|
|
101
103
|
}
|
|
102
104
|
}
|
|
103
105
|
|
|
@@ -107,7 +109,10 @@ export function canTransitionToPhase(
|
|
|
107
109
|
const artifactSet = new Set(context.completedArtifacts);
|
|
108
110
|
const missing = phase.inputs.filter((input) => !artifactSet.has(input));
|
|
109
111
|
if (missing.length > 0) {
|
|
110
|
-
return {
|
|
112
|
+
return {
|
|
113
|
+
allowed: false,
|
|
114
|
+
reason: `Missing required artifacts: ${missing.join(", ")}.`,
|
|
115
|
+
};
|
|
111
116
|
}
|
|
112
117
|
}
|
|
113
118
|
|
|
@@ -124,7 +129,10 @@ export function validatePhasePreconditions(
|
|
|
124
129
|
): { ready: boolean; blocking: string[] } {
|
|
125
130
|
const phase = machine.phases[machine.currentPhaseIndex];
|
|
126
131
|
if (!phase) {
|
|
127
|
-
return {
|
|
132
|
+
return {
|
|
133
|
+
ready: false,
|
|
134
|
+
blocking: [`No phase at index ${machine.currentPhaseIndex}.`],
|
|
135
|
+
};
|
|
128
136
|
}
|
|
129
137
|
|
|
130
138
|
const artifactSet = new Set(context.completedArtifacts);
|
|
@@ -31,17 +31,7 @@
|
|
|
31
31
|
*/
|
|
32
32
|
|
|
33
33
|
import { createHash } from "node:crypto";
|
|
34
|
-
import {
|
|
35
|
-
existsSync,
|
|
36
|
-
mkdirSync,
|
|
37
|
-
readFileSync,
|
|
38
|
-
readdirSync,
|
|
39
|
-
unlinkSync,
|
|
40
|
-
openSync,
|
|
41
|
-
closeSync,
|
|
42
|
-
statSync,
|
|
43
|
-
writeFileSync,
|
|
44
|
-
} from "node:fs";
|
|
34
|
+
import { closeSync, existsSync, mkdirSync, openSync, readdirSync, readFileSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
45
35
|
import * as path from "node:path";
|
|
46
36
|
import { atomicWriteJson } from "../state/atomic-write.ts";
|
|
47
37
|
import { projectCrewRoot, userCrewRoot } from "../utils/paths.ts";
|
|
@@ -69,7 +59,10 @@ export const defaultStartTimeResolver: StartTimeResolver = (pid: number): number
|
|
|
69
59
|
const stat = readFileSync(`/proc/${pid}/stat`, "utf-8");
|
|
70
60
|
const lastParen = stat.lastIndexOf(")");
|
|
71
61
|
if (lastParen === -1) return undefined;
|
|
72
|
-
const fieldsAfterComm = stat
|
|
62
|
+
const fieldsAfterComm = stat
|
|
63
|
+
.slice(lastParen + 1)
|
|
64
|
+
.trim()
|
|
65
|
+
.split(/\s+/);
|
|
73
66
|
// starttime is at index 19 (the 20th field after comm) of /proc/<pid>/stat.
|
|
74
67
|
const startTimeClockTicks = Number(fieldsAfterComm[19]);
|
|
75
68
|
if (!Number.isFinite(startTimeClockTicks)) return undefined;
|
|
@@ -202,7 +195,11 @@ function claimLock(lockPath: string, contents: WorkspaceLockContents, staleRecla
|
|
|
202
195
|
// Caller verified the existing lock is logically stale; remove it and claim. A concurrent
|
|
203
196
|
// reclaimer might re-create between our unlink and our open — that's fine, we lose the race
|
|
204
197
|
// and return false, falling through to the queue path.
|
|
205
|
-
try {
|
|
198
|
+
try {
|
|
199
|
+
unlinkSync(lockPath);
|
|
200
|
+
} catch {
|
|
201
|
+
/* best-effort */
|
|
202
|
+
}
|
|
206
203
|
return tryCreate();
|
|
207
204
|
}
|
|
208
205
|
if (tryCreate()) return true;
|
|
@@ -210,10 +207,16 @@ function claimLock(lockPath: string, contents: WorkspaceLockContents, staleRecla
|
|
|
210
207
|
try {
|
|
211
208
|
const stat = statSync(lockPath);
|
|
212
209
|
if (Date.now() - stat.mtimeMs > staleReclaimMs) {
|
|
213
|
-
try {
|
|
210
|
+
try {
|
|
211
|
+
unlinkSync(lockPath);
|
|
212
|
+
} catch {
|
|
213
|
+
/* fall through */
|
|
214
|
+
}
|
|
214
215
|
return tryCreate();
|
|
215
216
|
}
|
|
216
|
-
} catch {
|
|
217
|
+
} catch {
|
|
218
|
+
/* fall through to false */
|
|
219
|
+
}
|
|
217
220
|
return false;
|
|
218
221
|
}
|
|
219
222
|
|
|
@@ -281,9 +284,7 @@ export async function acquireWorkspaceLock(
|
|
|
281
284
|
while (true) {
|
|
282
285
|
// Poll-loop: re-check the lock each tick until free/stale or aborted.
|
|
283
286
|
if (opts.signal?.aborted) {
|
|
284
|
-
throw new Error(
|
|
285
|
-
`workspace lock acquisition aborted for goal ${goalId} (cwd=${cwd})`,
|
|
286
|
-
);
|
|
287
|
+
throw new Error(`workspace lock acquisition aborted for goal ${goalId} (cwd=${cwd})`);
|
|
287
288
|
}
|
|
288
289
|
const existing = readLock(lockPath);
|
|
289
290
|
// Classify the existing lock: "absent" / "stale" (PID recycled or heartbeat dead) / "live".
|
|
@@ -293,7 +294,9 @@ export async function acquireWorkspaceLock(
|
|
|
293
294
|
// acquireWorkspaceLock poll loop would re-queue forever = hang.
|
|
294
295
|
const existingKind: "absent" | "stale" | "live" = !existing
|
|
295
296
|
? "absent"
|
|
296
|
-
:
|
|
297
|
+
: isLockStale(existing, resolveStartTime, heartbeatStaleMs, now()).stale
|
|
298
|
+
? "stale"
|
|
299
|
+
: "live";
|
|
297
300
|
if (existingKind !== "live") {
|
|
298
301
|
// Claim the lock (covers both no-lock and stale-lock cases).
|
|
299
302
|
// Cold-review #3 NIT #N1 fix: claim via O_EXCL (claimLock), NOT temp+rename — two
|
|
@@ -324,9 +327,7 @@ export async function acquireWorkspaceLock(
|
|
|
324
327
|
}
|
|
325
328
|
// Lock is held and live.
|
|
326
329
|
if (opts.failOnWorkspaceBusy) {
|
|
327
|
-
throw new Error(
|
|
328
|
-
`workspace busy: cwd=${cwd} held by goalId=${existing!.goalId} (pid=${existing!.pid})`,
|
|
329
|
-
);
|
|
330
|
+
throw new Error(`workspace busy: cwd=${cwd} held by goalId=${existing!.goalId} (pid=${existing!.pid})`);
|
|
330
331
|
}
|
|
331
332
|
// Queue: wait for the next poll interval, then re-check.
|
|
332
333
|
await sleepOrAbort(pollMs, opts.signal);
|
|
@@ -338,20 +339,10 @@ export async function acquireWorkspaceLock(
|
|
|
338
339
|
* (goalId, pid, startTime). A stale handle (whose lock was reclaimed and
|
|
339
340
|
* re-acquired by another goal) must NOT delete the new owner's lock.
|
|
340
341
|
*/
|
|
341
|
-
function safeRelease(
|
|
342
|
-
lockPath: string,
|
|
343
|
-
goalId: string,
|
|
344
|
-
pid: number,
|
|
345
|
-
writtenStartTime: number | undefined,
|
|
346
|
-
): void {
|
|
342
|
+
function safeRelease(lockPath: string, goalId: string, pid: number, writtenStartTime: number | undefined): void {
|
|
347
343
|
try {
|
|
348
344
|
const current = readLock(lockPath);
|
|
349
|
-
if (
|
|
350
|
-
current &&
|
|
351
|
-
current.goalId === goalId &&
|
|
352
|
-
current.pid === pid &&
|
|
353
|
-
current.startTime === writtenStartTime
|
|
354
|
-
) {
|
|
345
|
+
if (current && current.goalId === goalId && current.pid === pid && current.startTime === writtenStartTime) {
|
|
355
346
|
unlinkSync(lockPath);
|
|
356
347
|
}
|
|
357
348
|
} catch {
|
|
@@ -390,7 +381,11 @@ function sleepOrAbort(ms: number, signal?: AbortSignal): Promise<void> {
|
|
|
390
381
|
*/
|
|
391
382
|
export function isWorkspaceBusy(
|
|
392
383
|
cwd: string,
|
|
393
|
-
opts: {
|
|
384
|
+
opts: {
|
|
385
|
+
startTimeResolver?: StartTimeResolver;
|
|
386
|
+
heartbeatStaleMs?: number;
|
|
387
|
+
now?: () => number;
|
|
388
|
+
} = {},
|
|
394
389
|
): string | undefined {
|
|
395
390
|
const lockPath = workspaceLockPath(cwd);
|
|
396
391
|
const resolveStartTime = opts.startTimeResolver ?? defaultStartTimeResolver;
|