pi-crew 0.9.16 → 0.9.18
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 +130 -0
- package/README.md +19 -0
- package/dist/build-meta.json +21784 -0
- package/dist/index.mjs +83169 -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/migration/atomic-write-v2-migration.md +297 -0
- package/docs/patterns/command-agent-skill.md +1 -1
- package/docs/perf/performance-review-2026-07.md +287 -0
- 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 +303 -591
- 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 +149 -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 +39 -24
- 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 +344 -79
- 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 +187 -59
- 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 +13 -31
- 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 +10 -18
- 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 +2 -6
- package/src/runtime/retry-executor.ts +20 -4
- package/src/runtime/retry-runner.ts +27 -51
- 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 +12 -19
- 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 +50 -14
- 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 +2 -7
- 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 +106 -27
- 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 +137 -47
- 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/terminal-status.ts +1 -3
- 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 +46 -29
- 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
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* M3: Iterative file-retrieval orchestrator.
|
|
3
|
+
*
|
|
4
|
+
* Pattern: workers progressively discover relevant context files
|
|
5
|
+
* (e.g. "which source file handles X?") using ripgrep-driven keyword
|
|
6
|
+
* search + the existing context-retrieval.ts scoring/convergence
|
|
7
|
+
* helpers. Max 3 cycles, fall back to in-memory heuristic when
|
|
8
|
+
* ripgrep is not available (e.g. minimal Windows CI runners).
|
|
9
|
+
*
|
|
10
|
+
* Signal flow:
|
|
11
|
+
* renderTaskPrompt (in prompt-builder.ts)
|
|
12
|
+
* → runRetrievalCycle(task, goal, cwd)
|
|
13
|
+
* → cycle 1: rg --files, then score each file
|
|
14
|
+
* → cycle 2: refine query, rg --json for keyword filter
|
|
15
|
+
* → cycle 3: same; if !shouldContinue, stop early
|
|
16
|
+
* → returns top-N files (5..10)
|
|
17
|
+
* renderTaskPrompt injects "Suggested files to read (top-N by
|
|
18
|
+
* retrieval score):" section before final prompt assembly.
|
|
19
|
+
*
|
|
20
|
+
* Production code path — not @experimental. Fallback is mandatory so
|
|
21
|
+
* the prompt is never blocked by a missing ripgrep binary.
|
|
22
|
+
*/
|
|
23
|
+
import { spawn } from "node:child_process";
|
|
24
|
+
import * as fs from "node:fs";
|
|
25
|
+
import * as path from "node:path";
|
|
26
|
+
import type { RelevanceEvaluation } from "./context-retrieval.ts";
|
|
27
|
+
import { hasConverged, scoreRelevance, shouldContinue } from "./context-retrieval.ts";
|
|
28
|
+
|
|
29
|
+
/** Max retrieval cycles per prompt render. Matches context-retrieval.MAX_CYCLES. */
|
|
30
|
+
export const MAX_CYCLES = 3;
|
|
31
|
+
|
|
32
|
+
/** Hard cap on suggested files injected into the worker prompt. */
|
|
33
|
+
export const MAX_SUGGESTED_FILES = 10;
|
|
34
|
+
|
|
35
|
+
/** Minimum files suggested when retrieval returns anything. */
|
|
36
|
+
export const MIN_SUGGESTED_FILES = 5;
|
|
37
|
+
|
|
38
|
+
/** Stopwords dropped during keyword tokenization (lowercase comparison). */
|
|
39
|
+
const STOPWORDS: ReadonlySet<string> = new Set(["the", "a", "an", "and", "or", "to", "of", "in", "for", "on", "is", "are", "be", "with"]);
|
|
40
|
+
|
|
41
|
+
/** File extensions considered relevant for retrieval. */
|
|
42
|
+
const RELEVANT_EXTS: ReadonlySet<string> = new Set([
|
|
43
|
+
".ts",
|
|
44
|
+
".tsx",
|
|
45
|
+
".js",
|
|
46
|
+
".jsx",
|
|
47
|
+
".mjs",
|
|
48
|
+
".cjs",
|
|
49
|
+
".md",
|
|
50
|
+
".markdown",
|
|
51
|
+
".json",
|
|
52
|
+
".yaml",
|
|
53
|
+
".yml",
|
|
54
|
+
]);
|
|
55
|
+
|
|
56
|
+
/** Result of a single runRetrievalCycle call. */
|
|
57
|
+
export interface RetrievalResult {
|
|
58
|
+
files: Array<{ path: string; score: number; reason: string }>;
|
|
59
|
+
cycles: number;
|
|
60
|
+
converged: boolean;
|
|
61
|
+
usedFallback: boolean;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
interface RipgrepAvailable {
|
|
65
|
+
available: boolean;
|
|
66
|
+
version?: string;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
let cachedRgCheck: RipgrepAvailable | undefined;
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Detect ripgrep availability once per process. Uses `rg --version` and
|
|
73
|
+
* catches ENOENT or non-zero exit. Cached so the cost (one spawn) is
|
|
74
|
+
* paid only on the first retrieval cycle.
|
|
75
|
+
*/
|
|
76
|
+
export async function detectRipgrep(): Promise<RipgrepAvailable> {
|
|
77
|
+
if (cachedRgCheck !== undefined) return cachedRgCheck;
|
|
78
|
+
return await new Promise<RipgrepAvailable>((resolve) => {
|
|
79
|
+
let settled = false;
|
|
80
|
+
try {
|
|
81
|
+
const child = spawn("rg", ["--version"], { stdio: ["ignore", "pipe", "pipe"] });
|
|
82
|
+
let stdout = "";
|
|
83
|
+
child.stdout?.on("data", (chunk) => {
|
|
84
|
+
stdout += chunk.toString("utf-8");
|
|
85
|
+
});
|
|
86
|
+
child.on("error", () => {
|
|
87
|
+
if (settled) return;
|
|
88
|
+
settled = true;
|
|
89
|
+
cachedRgCheck = { available: false };
|
|
90
|
+
resolve(cachedRgCheck);
|
|
91
|
+
});
|
|
92
|
+
child.on("close", (code) => {
|
|
93
|
+
if (settled) return;
|
|
94
|
+
settled = true;
|
|
95
|
+
if (code === 0) {
|
|
96
|
+
cachedRgCheck = { available: true, version: stdout.split("\n")[0] ?? undefined };
|
|
97
|
+
} else {
|
|
98
|
+
cachedRgCheck = { available: false };
|
|
99
|
+
}
|
|
100
|
+
resolve(cachedRgCheck);
|
|
101
|
+
});
|
|
102
|
+
} catch {
|
|
103
|
+
if (settled) return;
|
|
104
|
+
settled = true;
|
|
105
|
+
cachedRgCheck = { available: false };
|
|
106
|
+
resolve(cachedRgCheck);
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** @internal Test-only: reset the ripgrep detection cache. */
|
|
112
|
+
export function __test_resetRipgrepCache(): void {
|
|
113
|
+
cachedRgCheck = undefined;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Tokenize a task + goal string into lowercase keywords, dropping
|
|
118
|
+
* stopwords. Single-letter tokens and pure-punctuation tokens are
|
|
119
|
+
* dropped. Output is deduped, original-order preserved.
|
|
120
|
+
*/
|
|
121
|
+
export function tokenizeQuery(task: string, goal: string): string[] {
|
|
122
|
+
const combined = `${task}\n${goal}`.toLowerCase();
|
|
123
|
+
const tokens = combined
|
|
124
|
+
.split(/[^a-z0-9_-]+/)
|
|
125
|
+
.map((t) => t.trim())
|
|
126
|
+
.filter((t) => t.length >= 2 && !STOPWORDS.has(t));
|
|
127
|
+
return [...new Set(tokens)];
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** Reason template for a discovery hit. Exported for tests. */
|
|
131
|
+
export function reasonFor(file: string, keywords: string[]): string {
|
|
132
|
+
const lower = file.toLowerCase();
|
|
133
|
+
const hits = keywords.filter((k) => lower.includes(k));
|
|
134
|
+
if (hits.length === 0) return `matched by relevance score (no direct keyword hit in path)`;
|
|
135
|
+
return `keyword match: ${hits.join(", ")}`;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Run ripgrep with the given args, returning stdout as a string.
|
|
140
|
+
* Throws on ENOENT / non-zero exit. Caller handles fallback.
|
|
141
|
+
*/
|
|
142
|
+
function runRipgrep(args: string[], cwd: string): Promise<string> {
|
|
143
|
+
return new Promise<string>((resolve, reject) => {
|
|
144
|
+
let settled = false;
|
|
145
|
+
let stdout = "";
|
|
146
|
+
let stderr = "";
|
|
147
|
+
try {
|
|
148
|
+
const child = spawn("rg", args, { cwd, stdio: ["ignore", "pipe", "pipe"] });
|
|
149
|
+
child.stdout?.on("data", (chunk) => {
|
|
150
|
+
stdout += chunk.toString("utf-8");
|
|
151
|
+
});
|
|
152
|
+
child.stderr?.on("data", (chunk) => {
|
|
153
|
+
stderr += chunk.toString("utf-8");
|
|
154
|
+
});
|
|
155
|
+
child.on("error", (err) => {
|
|
156
|
+
if (settled) return;
|
|
157
|
+
settled = true;
|
|
158
|
+
reject(err);
|
|
159
|
+
});
|
|
160
|
+
child.on("close", (code) => {
|
|
161
|
+
if (settled) return;
|
|
162
|
+
settled = true;
|
|
163
|
+
// rg exit code 1 = "no matches" (NOT an error). Any other
|
|
164
|
+
// non-zero exit IS an error.
|
|
165
|
+
if (code === 0 || code === 1) {
|
|
166
|
+
resolve(stdout);
|
|
167
|
+
} else {
|
|
168
|
+
reject(new Error(`rg exited ${code}: ${stderr.slice(0, 200)}`));
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
} catch (e) {
|
|
172
|
+
if (settled) return;
|
|
173
|
+
settled = true;
|
|
174
|
+
reject(e);
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* In-memory fallback: walk cwd with readdir({recursive:true}), filter
|
|
181
|
+
* to relevant extensions, score by filename keyword match. Used when
|
|
182
|
+
* ripgrep is not installed. Mirrors the rg --files path closely so
|
|
183
|
+
* downstream scoring behaves the same.
|
|
184
|
+
*/
|
|
185
|
+
async function walkFilesFallback(cwd: string, keywords: string[]): Promise<Array<{ path: string; score: number; reason: string }>> {
|
|
186
|
+
const out: Array<{ path: string; score: number; reason: string }> = [];
|
|
187
|
+
const lowerCwd = cwd.toLowerCase();
|
|
188
|
+
async function walk(dir: string): Promise<void> {
|
|
189
|
+
let entries: fs.Dirent[];
|
|
190
|
+
try {
|
|
191
|
+
entries = await fs.promises.readdir(dir, { withFileTypes: true });
|
|
192
|
+
} catch {
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
for (const entry of entries) {
|
|
196
|
+
if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
|
|
197
|
+
const full = path.join(dir, entry.name);
|
|
198
|
+
if (entry.isDirectory()) {
|
|
199
|
+
await walk(full);
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
if (!entry.isFile()) continue;
|
|
203
|
+
const ext = path.extname(entry.name).toLowerCase();
|
|
204
|
+
if (!RELEVANT_EXTS.has(ext)) continue;
|
|
205
|
+
// scoreRelevance expects file content, but for the fallback we
|
|
206
|
+
// only have the filename. Use the filename as a proxy (the
|
|
207
|
+
// existing scoreRelevance handles short content gracefully —
|
|
208
|
+
// path match contributes 0.3, content match 0.05 × log2(N)).
|
|
209
|
+
const content = "";
|
|
210
|
+
const score = scoreRelevance(full, content, keywords);
|
|
211
|
+
if (score > 0) {
|
|
212
|
+
out.push({ path: path.relative(lowerCwd, full), score, reason: reasonFor(full, keywords) });
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
await walk(cwd);
|
|
217
|
+
return out;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Iterate up to MAX_CYCLES times. Each cycle: discover files, score
|
|
222
|
+
* them, check convergence. Stop early on convergence or when
|
|
223
|
+
* shouldContinue returns false.
|
|
224
|
+
*/
|
|
225
|
+
export async function runRetrievalCycle(task: string, goal: string, cwd: string): Promise<RetrievalResult> {
|
|
226
|
+
const keywords = tokenizeQuery(task, goal);
|
|
227
|
+
if (keywords.length === 0) {
|
|
228
|
+
return { files: [], cycles: 0, converged: true, usedFallback: false };
|
|
229
|
+
}
|
|
230
|
+
const rg = await detectRipgrep();
|
|
231
|
+
const useRg = rg.available;
|
|
232
|
+
let usedFallback = !useRg;
|
|
233
|
+
const evaluations: RelevanceEvaluation[] = [];
|
|
234
|
+
let cycle = 0;
|
|
235
|
+
let converged = false;
|
|
236
|
+
for (; cycle < MAX_CYCLES; cycle++) {
|
|
237
|
+
let discovered: string[] = [];
|
|
238
|
+
try {
|
|
239
|
+
if (useRg) {
|
|
240
|
+
// Cycle 0: enumerate all relevant files via `rg --files`.
|
|
241
|
+
// Later cycles: filter by keywords via `rg --files | rg pattern`.
|
|
242
|
+
// We use the simpler `rg --files` + filter strategy because
|
|
243
|
+
// `rg --json` parsing adds complexity for marginal gain.
|
|
244
|
+
// `rg --files` respects .gitignore by default; we add an
|
|
245
|
+
// explicit -g '!node_modules' and -g '!.git' to be safe on
|
|
246
|
+
// repos that don't ignore them.
|
|
247
|
+
const stdout = await runRipgrep(["--files", "-g", "!node_modules", "-g", "!.git", cwd], cwd);
|
|
248
|
+
discovered = stdout
|
|
249
|
+
.split("\n")
|
|
250
|
+
.map((p) => p.trim())
|
|
251
|
+
.filter((p) => p && RELEVANT_EXTS.has(path.extname(p).toLowerCase()))
|
|
252
|
+
.map((p) => path.relative(cwd, p));
|
|
253
|
+
} else {
|
|
254
|
+
discovered = (await walkFilesFallback(cwd, keywords)).map((f) => f.path);
|
|
255
|
+
}
|
|
256
|
+
} catch {
|
|
257
|
+
// rg errored mid-run — switch to fallback for this cycle.
|
|
258
|
+
usedFallback = true;
|
|
259
|
+
discovered = (await walkFilesFallback(cwd, keywords)).map((f) => f.path);
|
|
260
|
+
}
|
|
261
|
+
// Score each discovered file. Path-only scoring (no file read) so
|
|
262
|
+
// we don't slow down prompt building for hundreds of files.
|
|
263
|
+
const seenInThisCycle = new Set<string>();
|
|
264
|
+
for (const relPath of discovered) {
|
|
265
|
+
if (seenInThisCycle.has(relPath)) continue;
|
|
266
|
+
seenInThisCycle.add(relPath);
|
|
267
|
+
const absPath = path.isAbsolute(relPath) ? relPath : path.join(cwd, relPath);
|
|
268
|
+
const score = scoreRelevance(absPath, "", keywords);
|
|
269
|
+
if (score > 0) {
|
|
270
|
+
evaluations.push({
|
|
271
|
+
path: absPath,
|
|
272
|
+
relevance: score,
|
|
273
|
+
reason: reasonFor(absPath, keywords),
|
|
274
|
+
missingContext: [],
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
converged = hasConverged(evaluations);
|
|
279
|
+
if (converged) break;
|
|
280
|
+
if (!shouldContinue(evaluations, cycle)) break;
|
|
281
|
+
}
|
|
282
|
+
// Sort by score desc, take top N (5..10).
|
|
283
|
+
evaluations.sort((a, b) => b.relevance - a.relevance);
|
|
284
|
+
const cap = Math.min(MAX_SUGGESTED_FILES, Math.max(MIN_SUGGESTED_FILES, evaluations.length));
|
|
285
|
+
const top = evaluations.slice(0, cap).map((e) => ({
|
|
286
|
+
path: path.isAbsolute(e.path) ? path.relative(cwd, e.path) : e.path,
|
|
287
|
+
score: e.relevance,
|
|
288
|
+
reason: e.reason,
|
|
289
|
+
}));
|
|
290
|
+
return { files: top, cycles: cycle, converged, usedFallback };
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Render the "Suggested files to read" section for injection into the
|
|
295
|
+
* worker prompt. Returns an empty string when retrieval returned no
|
|
296
|
+
* files. Format is a markdown bullet list (one line per file) prefixed
|
|
297
|
+
* with a heading so the worker can `grep` for it.
|
|
298
|
+
*/
|
|
299
|
+
export function renderSuggestedFilesSection(result: RetrievalResult): string {
|
|
300
|
+
if (result.files.length === 0) return "";
|
|
301
|
+
const lines: string[] = [
|
|
302
|
+
`# Suggested files to read (top-${result.files.length} by retrieval score)`,
|
|
303
|
+
`Retrieval ran for ${result.cycles} cycle(s)${result.usedFallback ? " (in-memory fallback, rg unavailable)" : ""}${result.converged ? " and converged" : ""}.`,
|
|
304
|
+
"",
|
|
305
|
+
];
|
|
306
|
+
for (const file of result.files) {
|
|
307
|
+
lines.push(`- ${file.path} — ${file.reason} (score ${file.score.toFixed(2)})`);
|
|
308
|
+
}
|
|
309
|
+
return lines.join("\n");
|
|
310
|
+
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import type { TeamRunManifest, TeamTaskState } from "../../state/types.ts";
|
|
2
1
|
import type { MailboxMessage } from "../../state/mailbox.ts";
|
|
3
|
-
import type { ArtifactDescriptor } from "../../state/types.ts";
|
|
2
|
+
import type { ArtifactDescriptor, TeamRunManifest, TeamTaskState } from "../../state/types.ts";
|
|
4
3
|
|
|
5
4
|
export interface RunProjectionSource {
|
|
6
5
|
kind: "events" | "mailbox" | "artifacts" | "ui_metadata" | "runtime_metadata";
|
|
@@ -44,7 +43,11 @@ export function transformRunContextBeforeWorkerStart(input: {
|
|
|
44
43
|
const completedTasks = input.tasks.filter((t) => t.status === "completed" || t.status === "failed");
|
|
45
44
|
if (completedTasks.length > 0) {
|
|
46
45
|
const tasks = completedTasks.slice(0, maxEvents);
|
|
47
|
-
sources.push({
|
|
46
|
+
sources.push({
|
|
47
|
+
kind: "events",
|
|
48
|
+
bounded: true,
|
|
49
|
+
reference: `tasks:${tasks.length}/${completedTasks.length}`,
|
|
50
|
+
});
|
|
48
51
|
lines.push(`Previous tasks (${tasks.length}/${completedTasks.length}):`);
|
|
49
52
|
for (const task of tasks) {
|
|
50
53
|
lines.push(`- ${task.id}: ${task.status}${task.error ? ` (${task.error})` : ""}`);
|
|
@@ -54,7 +57,11 @@ export function transformRunContextBeforeWorkerStart(input: {
|
|
|
54
57
|
// Project pending mailbox that is relevant to this worker
|
|
55
58
|
if (input.pendingMailbox.length > 0) {
|
|
56
59
|
const messages = input.pendingMailbox.slice(0, maxMailbox);
|
|
57
|
-
sources.push({
|
|
60
|
+
sources.push({
|
|
61
|
+
kind: "mailbox",
|
|
62
|
+
bounded: true,
|
|
63
|
+
reference: `mailbox:${messages.length}/${input.pendingMailbox.length}`,
|
|
64
|
+
});
|
|
58
65
|
lines.push(`Pending messages (${messages.length}/${input.pendingMailbox.length}):`);
|
|
59
66
|
for (const msg of messages) {
|
|
60
67
|
lines.push(`- ${msg.kind ?? "message"}: ${msg.body.slice(0, 100)}`);
|
|
@@ -64,7 +71,11 @@ export function transformRunContextBeforeWorkerStart(input: {
|
|
|
64
71
|
// Project artifact references (not content)
|
|
65
72
|
if (input.artifacts.length > 0) {
|
|
66
73
|
const artifacts = input.artifacts.slice(0, maxArtifacts);
|
|
67
|
-
sources.push({
|
|
74
|
+
sources.push({
|
|
75
|
+
kind: "artifacts",
|
|
76
|
+
bounded: true,
|
|
77
|
+
reference: `artifacts:${artifacts.length}/${input.artifacts.length}`,
|
|
78
|
+
});
|
|
68
79
|
lines.push(`Available artifacts (${artifacts.length}/${input.artifacts.length}):`);
|
|
69
80
|
for (const art of artifacts) {
|
|
70
81
|
lines.push(`- ${art.kind} (${art.producer})`);
|
|
@@ -72,8 +83,16 @@ export function transformRunContextBeforeWorkerStart(input: {
|
|
|
72
83
|
}
|
|
73
84
|
|
|
74
85
|
// Metadata markers — not injected as prompt instructions
|
|
75
|
-
sources.push({
|
|
76
|
-
|
|
86
|
+
sources.push({
|
|
87
|
+
kind: "ui_metadata",
|
|
88
|
+
bounded: false,
|
|
89
|
+
reference: "excluded_from_prompt",
|
|
90
|
+
});
|
|
91
|
+
sources.push({
|
|
92
|
+
kind: "runtime_metadata",
|
|
93
|
+
bounded: false,
|
|
94
|
+
reference: "excluded_from_prompt",
|
|
95
|
+
});
|
|
77
96
|
|
|
78
97
|
return {
|
|
79
98
|
sources,
|
|
@@ -101,4 +120,4 @@ export function convertRunHistoryToWorkerPrompt(input: {
|
|
|
101
120
|
"",
|
|
102
121
|
`Projection sources: ${projection.sources.map((s) => s.kind).join(", ")}`,
|
|
103
122
|
].join("\n");
|
|
104
|
-
}
|
|
123
|
+
}
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
|
+
import { flushPendingAtomicWrites } from "../../state/atomic-write.ts";
|
|
3
|
+
import { withRunLockSync } from "../../state/locks.ts";
|
|
4
|
+
import { loadRunManifestById, saveRunTasksCoalesced } from "../../state/state-store.ts";
|
|
2
5
|
import type { TaskCheckpointState, TeamRunManifest, TeamTaskState } from "../../state/types.ts";
|
|
3
|
-
import { loadRunManifestById, saveRunTasks } from "../../state/state-store.ts";
|
|
4
|
-
import { recordFromTask, upsertCrewAgent } from "../crew-agent-records.ts";
|
|
5
6
|
import { logInternalError } from "../../utils/internal-error.ts";
|
|
6
|
-
import {
|
|
7
|
+
import { recordFromTask, upsertCrewAgent } from "../crew-agent-records.ts";
|
|
7
8
|
|
|
8
9
|
export function updateTask(tasks: TeamTaskState[], updated: TeamTaskState): TeamTaskState[] {
|
|
9
|
-
return tasks.map((task) => task.id === updated.id ? updated : task);
|
|
10
|
+
return tasks.map((task) => (task.id === updated.id ? updated : task));
|
|
10
11
|
}
|
|
11
12
|
|
|
12
13
|
/**
|
|
@@ -25,7 +26,12 @@ export function updateTask(tasks: TeamTaskState[], updated: TeamTaskState): Team
|
|
|
25
26
|
*
|
|
26
27
|
* @param checkpointPhase - Optional checkpoint phase to include in the task state alongside the update.
|
|
27
28
|
*/
|
|
28
|
-
export function persistSingleTaskUpdate(
|
|
29
|
+
export function persistSingleTaskUpdate(
|
|
30
|
+
manifest: TeamRunManifest,
|
|
31
|
+
fallbackTasks: TeamTaskState[],
|
|
32
|
+
updated: TeamTaskState,
|
|
33
|
+
checkpointPhase?: TaskCheckpointState["phase"],
|
|
34
|
+
): TeamTaskState[] {
|
|
29
35
|
let baseMtime = 0;
|
|
30
36
|
try {
|
|
31
37
|
baseMtime = fs.statSync(manifest.tasksPath).mtimeMs;
|
|
@@ -38,12 +44,27 @@ export function persistSingleTaskUpdate(manifest: TeamRunManifest, fallbackTasks
|
|
|
38
44
|
|
|
39
45
|
// Build the task with optional checkpoint phase
|
|
40
46
|
const taskWithCheckpoint = checkpointPhase
|
|
41
|
-
? {
|
|
47
|
+
? {
|
|
48
|
+
...updated,
|
|
49
|
+
checkpoint: {
|
|
50
|
+
phase: checkpointPhase,
|
|
51
|
+
updatedAt: new Date().toISOString(),
|
|
52
|
+
},
|
|
53
|
+
}
|
|
42
54
|
: updated;
|
|
43
55
|
|
|
44
56
|
try {
|
|
45
57
|
return withRunLockSync(manifest, () => {
|
|
46
|
-
|
|
58
|
+
for (let attempt = 0; attempt < 100; attempt++) {
|
|
59
|
+
// F4: persistSingleTaskUpdate now uses saveRunTasksCoalesced below
|
|
60
|
+
// (50ms debounce window). Read-modify-write loops are unsafe under
|
|
61
|
+
// coalescing — a parallel writer's buffered write is invisible to
|
|
62
|
+
// loadRunManifestById until it actually lands. Force any pending
|
|
63
|
+
// coalesced writes to flush first so this read sees the latest
|
|
64
|
+
// durable state. Without this guard, a parallel writer could
|
|
65
|
+
// overwrite our buffered write between our load and our (async)
|
|
66
|
+
// fsync, silently losing the intermediate update.
|
|
67
|
+
flushPendingAtomicWrites();
|
|
47
68
|
const latest = loadRunManifestById(manifest.cwd, manifest.runId)?.tasks ?? fallbackTasks;
|
|
48
69
|
merged = updateTask(latest, taskWithCheckpoint);
|
|
49
70
|
|
|
@@ -58,7 +79,7 @@ export function persistSingleTaskUpdate(manifest: TeamRunManifest, fallbackTasks
|
|
|
58
79
|
if (currentMtime !== baseMtime) {
|
|
59
80
|
// Another writer committed — their update is in latest, re-merge on top
|
|
60
81
|
baseMtime = currentMtime;
|
|
61
|
-
continue
|
|
82
|
+
continue;
|
|
62
83
|
}
|
|
63
84
|
|
|
64
85
|
// No concurrent writer — check that our merged result is based on the
|
|
@@ -72,7 +93,7 @@ export function persistSingleTaskUpdate(manifest: TeamRunManifest, fallbackTasks
|
|
|
72
93
|
}
|
|
73
94
|
if (recheckMtime !== baseMtime) {
|
|
74
95
|
baseMtime = recheckMtime;
|
|
75
|
-
continue
|
|
96
|
+
continue;
|
|
76
97
|
}
|
|
77
98
|
|
|
78
99
|
// Final pre-write mtime check to catch any concurrent writer that completed
|
|
@@ -86,10 +107,10 @@ export function persistSingleTaskUpdate(manifest: TeamRunManifest, fallbackTasks
|
|
|
86
107
|
if (preWriteMtime !== baseMtime) {
|
|
87
108
|
// Another writer committed — retry
|
|
88
109
|
baseMtime = preWriteMtime;
|
|
89
|
-
continue
|
|
110
|
+
continue;
|
|
90
111
|
}
|
|
91
112
|
|
|
92
|
-
break
|
|
113
|
+
break;
|
|
93
114
|
}
|
|
94
115
|
|
|
95
116
|
if (merged === undefined) {
|
|
@@ -98,7 +119,12 @@ export function persistSingleTaskUpdate(manifest: TeamRunManifest, fallbackTasks
|
|
|
98
119
|
}
|
|
99
120
|
|
|
100
121
|
try {
|
|
101
|
-
|
|
122
|
+
// F4: coalesced write inside the withRunLockSync critical section.
|
|
123
|
+
// The mtime CAS retry loop above still guards against concurrent
|
|
124
|
+
// non-coalesced writers; the flushPendingAtomicWrites() guard at
|
|
125
|
+
// the top of the retry loop ensures reads see any other coalesced
|
|
126
|
+
// writer's flushed-before-this-call state.
|
|
127
|
+
saveRunTasksCoalesced(manifest, merged);
|
|
102
128
|
} catch (err) {
|
|
103
129
|
logInternalError("persistSingleTaskUpdate", err);
|
|
104
130
|
throw err;
|
|
@@ -113,8 +139,18 @@ export function persistSingleTaskUpdate(manifest: TeamRunManifest, fallbackTasks
|
|
|
113
139
|
}
|
|
114
140
|
}
|
|
115
141
|
|
|
116
|
-
export function checkpointTask(
|
|
117
|
-
|
|
142
|
+
export function checkpointTask(
|
|
143
|
+
manifest: TeamRunManifest,
|
|
144
|
+
tasks: TeamTaskState[],
|
|
145
|
+
task: TeamTaskState,
|
|
146
|
+
phase: TaskCheckpointState["phase"],
|
|
147
|
+
childPid?: number,
|
|
148
|
+
): { task: TeamTaskState; tasks: TeamTaskState[] } {
|
|
149
|
+
const checkpoint: TaskCheckpointState = {
|
|
150
|
+
phase,
|
|
151
|
+
updatedAt: new Date().toISOString(),
|
|
152
|
+
...(childPid ? { childPid } : task.checkpoint?.childPid ? { childPid: task.checkpoint.childPid } : {}),
|
|
153
|
+
};
|
|
118
154
|
const nextTask = { ...task, checkpoint };
|
|
119
155
|
const nextTasks = persistSingleTaskUpdate(manifest, updateTask(tasks, nextTask), nextTask);
|
|
120
156
|
try {
|
|
@@ -5,11 +5,7 @@ import * as fs from "node:fs";
|
|
|
5
5
|
* If the file exceeds maxBytes, reads only the last maxBytes and snaps
|
|
6
6
|
* to the nearest newline boundary to avoid partial JSONL lines.
|
|
7
7
|
*/
|
|
8
|
-
export function tailReadWithLineSnap(
|
|
9
|
-
filePath: string,
|
|
10
|
-
maxBytes: number,
|
|
11
|
-
fallbackContent: string,
|
|
12
|
-
): string {
|
|
8
|
+
export function tailReadWithLineSnap(filePath: string, maxBytes: number, fallbackContent: string): string {
|
|
13
9
|
if (!fs.existsSync(filePath)) return fallbackContent;
|
|
14
10
|
const stat = fs.statSync(filePath);
|
|
15
11
|
if (stat.size <= maxBytes) return fs.readFileSync(filePath, "utf-8");
|
|
@@ -23,4 +19,4 @@ export function tailReadWithLineSnap(
|
|
|
23
19
|
} finally {
|
|
24
20
|
fs.closeSync(fd);
|
|
25
21
|
}
|
|
26
|
-
}
|
|
22
|
+
}
|