pi-crew 0.9.15 → 0.9.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +145 -0
- package/README.md +19 -0
- package/dist/build-meta.json +21790 -0
- package/dist/index.mjs +83070 -0
- package/dist/index.mjs.map +7 -0
- package/docs/A +358 -0
- package/docs/M-A +357 -0
- package/docs/REVIEW-FINDINGS-2026-06 +357 -0
- package/docs/Y +357 -0
- package/docs/a +357 -0
- package/docs/aA +358 -0
- package/docs/archive/README.md +91 -0
- package/docs/patterns/command-agent-skill.md +1 -1
- package/docs/skills/REFERENCE.md +0 -17
- package/docs//303/242mmaAAA/303/242 +357 -0
- package/index.bundle.ts +25 -0
- package/index.ts +95 -4
- package/package.json +15 -4
- package/skills/iterative-audit/SKILL.md +0 -1
- package/skills/widget-rendering/SKILL.md +17 -0
- package/src/adapters/claude-adapter.ts +1 -3
- package/src/adapters/export-util.ts +16 -16
- package/src/adapters/index.ts +5 -5
- package/src/agents/agent-config.ts +20 -14
- package/src/agents/agent-serializer.ts +1 -1
- package/src/agents/discover-agents.ts +83 -39
- package/src/benchmark/benchmark-runner.ts +51 -51
- package/src/benchmark/feedback-loop.ts +1 -1
- package/src/config/config.ts +166 -590
- package/src/config/defaults.ts +28 -2
- package/src/config/drift-detector.ts +11 -14
- package/src/config/markers.ts +201 -203
- package/src/config/resilient-parser.ts +14 -6
- package/src/config/role-tools.ts +3 -3
- package/src/config/suggestions.ts +5 -12
- package/src/config/types.ts +9 -25
- package/src/errors.ts +151 -157
- package/src/extension/action-suggestions.ts +54 -9
- package/src/extension/async-notifier.ts +43 -13
- package/src/extension/autonomous-policy.ts +77 -37
- package/src/extension/command-completions.ts +9 -8
- package/src/extension/context-status-injection.ts +14 -12
- package/src/extension/crew-autocomplete.ts +8 -16
- package/src/extension/crew-cleanup.ts +2 -1
- package/src/extension/crew-shortcuts.ts +9 -6
- package/src/extension/cross-extension-rpc.ts +136 -42
- package/src/extension/help.ts +1 -1
- package/src/extension/import-index.ts +10 -5
- package/src/extension/knowledge-injection.ts +88 -15
- package/src/extension/management.ts +89 -349
- package/src/extension/message-renderers.ts +3 -4
- package/src/extension/notification-router.ts +22 -5
- package/src/extension/notification-sink.ts +6 -3
- package/src/extension/pi-api.ts +17 -8
- package/src/extension/plan-orchestrate.ts +8 -27
- package/src/extension/project-init.ts +21 -6
- package/src/extension/register.ts +329 -975
- package/src/extension/registration/artifact-cleanup.ts +9 -4
- package/src/extension/registration/command-utils.ts +5 -1
- package/src/extension/registration/commands.ts +806 -314
- package/src/extension/registration/compaction-guard.ts +15 -15
- package/src/extension/registration/lifecycle.ts +259 -0
- package/src/extension/registration/observability.ts +293 -0
- package/src/extension/registration/subagent-helpers.ts +29 -7
- package/src/extension/registration/subagent-tools.ts +253 -44
- package/src/extension/registration/team-tool.ts +19 -84
- package/src/extension/registration/ui.ts +168 -0
- package/src/extension/registration/viewers.ts +64 -24
- package/src/extension/result-watcher.ts +18 -7
- package/src/extension/run-bundle-schema.ts +21 -6
- package/src/extension/run-export.ts +15 -6
- package/src/extension/run-import.ts +64 -29
- package/src/extension/run-index.ts +28 -9
- package/src/extension/run-maintenance.ts +32 -12
- package/src/extension/session-summary.ts +1 -1
- package/src/extension/team-manager-command.ts +64 -7
- package/src/extension/team-onboard.ts +151 -150
- package/src/extension/team-recommendation.ts +121 -21
- package/src/extension/team-tool/anchor.ts +31 -64
- package/src/extension/team-tool/api.ts +929 -141
- package/src/extension/team-tool/auto-summarize.ts +14 -26
- package/src/extension/team-tool/cache-control.ts +1 -5
- package/src/extension/team-tool/cancel.ts +138 -38
- package/src/extension/team-tool/chain-dispatch.ts +5 -13
- package/src/extension/team-tool/chain-executor.ts +29 -51
- package/src/extension/team-tool/config-patch.ts +1 -1
- package/src/extension/team-tool/context.ts +40 -20
- package/src/extension/team-tool/destructive-gate.ts +10 -5
- package/src/extension/team-tool/doctor.ts +170 -51
- package/src/extension/team-tool/explain.ts +228 -214
- package/src/extension/team-tool/failure-patterns.ts +3 -9
- package/src/extension/team-tool/goal-wrap.ts +71 -29
- package/src/extension/team-tool/goal.ts +185 -35
- package/src/extension/team-tool/handle-schedule.ts +34 -15
- package/src/extension/team-tool/handle-settings.ts +94 -56
- package/src/extension/team-tool/health-monitor.ts +27 -108
- package/src/extension/team-tool/inspect.ts +42 -9
- package/src/extension/team-tool/intent-policy.ts +4 -1
- package/src/extension/team-tool/lifecycle-actions.ts +244 -48
- package/src/extension/team-tool/orchestrate.ts +7 -18
- package/src/extension/team-tool/parallel-dispatch.ts +51 -29
- package/src/extension/team-tool/plan.ts +29 -5
- package/src/extension/team-tool/respond.ts +49 -16
- package/src/extension/team-tool/run-not-found.ts +3 -8
- package/src/extension/team-tool/run.ts +80 -317
- package/src/extension/team-tool/status.ts +135 -43
- package/src/extension/team-tool/workflow-manage.ts +92 -25
- package/src/extension/team-tool-types.ts +8 -1
- package/src/extension/team-tool.ts +142 -551
- package/src/extension/validate-resources.ts +39 -8
- package/src/hooks/registry.ts +66 -17
- package/src/hooks/types.ts +1 -1
- package/src/i18n.ts +25 -11
- package/src/observability/correlation.ts +17 -3
- package/src/observability/event-bus.ts +51 -56
- package/src/observability/event-to-metric.ts +117 -15
- package/src/observability/exporters/otlp-exporter.ts +56 -17
- package/src/observability/exporters/prometheus-exporter.ts +1 -1
- package/src/observability/metric-registry.ts +16 -4
- package/src/observability/metric-retention.ts +8 -2
- package/src/observability/metric-sink.ts +11 -3
- package/src/observability/metrics-primitives.ts +42 -9
- package/src/plugins/plugin-define.ts +2 -2
- package/src/plugins/plugin-registry.ts +25 -25
- package/src/plugins/plugins/index.ts +1 -1
- package/src/plugins/plugins/nextjs.ts +16 -16
- package/src/plugins/plugins/vite.ts +7 -11
- package/src/plugins/plugins/vitest.ts +6 -11
- package/src/runtime/adaptive-plan.ts +225 -40
- package/src/runtime/agent-control.ts +58 -15
- package/src/runtime/agent-memory.ts +15 -9
- package/src/runtime/agent-observability.ts +10 -2
- package/src/runtime/anchor-manager.ts +27 -25
- package/src/runtime/async-marker.ts +7 -1
- package/src/runtime/async-runner.ts +27 -23
- package/src/runtime/auto-summarize.ts +7 -13
- package/src/runtime/background-runner.ts +189 -251
- package/src/runtime/batch-barrier.ts +18 -16
- package/src/runtime/cancellation-token.ts +16 -6
- package/src/runtime/cancellation.ts +46 -8
- package/src/runtime/capability-inventory.ts +6 -4
- package/src/runtime/chain-parser.ts +44 -11
- package/src/runtime/chain-runner.ts +63 -69
- package/src/runtime/checkpoint.ts +12 -56
- package/src/runtime/child-pi.ts +343 -78
- package/src/runtime/coalesce-tasks.ts +268 -0
- package/src/runtime/code-summary.ts +71 -26
- package/src/runtime/compact-stages/index.ts +15 -5
- package/src/runtime/compact-stages/tail-capture-stage.ts +7 -2
- package/src/runtime/compact-stages/truncation-stage.ts +4 -1
- package/src/runtime/compaction-summary.ts +17 -10
- package/src/runtime/completion-guard.ts +33 -16
- package/src/runtime/crash-classification.ts +28 -7
- package/src/runtime/crash-recovery.ts +177 -37
- package/src/runtime/crew-agent-records.ts +193 -40
- package/src/runtime/crew-hooks.ts +9 -24
- package/src/runtime/cross-extension-rpc.ts +25 -23
- package/src/runtime/custom-tools/irc-tool.ts +43 -15
- package/src/runtime/custom-tools/submit-result-tool.ts +16 -5
- package/src/runtime/delivery-coordinator.ts +34 -7
- package/src/runtime/delta-conflict.ts +9 -21
- package/src/runtime/deterministic-ast.ts +1 -1
- package/src/runtime/diagnostic-export.ts +53 -20
- package/src/runtime/direct-run.ts +12 -2
- package/src/runtime/dwf-state-store.ts +1 -1
- package/src/runtime/dynamic-workflow-context.ts +186 -58
- package/src/runtime/dynamic-workflow-runner.ts +42 -20
- package/src/runtime/effectiveness.ts +16 -8
- package/src/runtime/errors/crew-errors.ts +7 -11
- package/src/runtime/event-stream-bridge.ts +9 -3
- package/src/runtime/foreground-control.ts +54 -14
- package/src/runtime/foreground-watchdog.ts +9 -6
- package/src/runtime/goal-achievement.ts +27 -10
- package/src/runtime/goal-evaluator.ts +54 -19
- package/src/runtime/goal-loop-runner.ts +280 -99
- package/src/runtime/goal-state-store.ts +27 -17
- package/src/runtime/green-contract.ts +11 -2
- package/src/runtime/group-join.ts +64 -31
- package/src/runtime/handoff-manager.ts +37 -42
- package/src/runtime/heartbeat-gradient.ts +10 -2
- package/src/runtime/heartbeat-watcher.ts +32 -6
- package/src/runtime/hidden-handoff.ts +12 -30
- package/src/runtime/important-line-classifier.ts +12 -2
- package/src/runtime/iteration-hooks.ts +25 -15
- package/src/runtime/live-agent-control.ts +55 -7
- package/src/runtime/live-agent-manager.ts +130 -27
- package/src/runtime/live-control-realtime.ts +23 -3
- package/src/runtime/live-irc.ts +4 -1
- package/src/runtime/live-session-health.ts +12 -4
- package/src/runtime/live-session-runtime.ts +379 -117
- package/src/runtime/manifest-cache.ts +28 -12
- package/src/runtime/mcp-proxy.ts +8 -16
- package/src/runtime/metric-parser.ts +1 -5
- package/src/runtime/model-fallback.ts +55 -18
- package/src/runtime/model-resolver.ts +2 -6
- package/src/runtime/model-scope.ts +19 -3
- package/src/runtime/notebook-helpers.ts +60 -62
- package/src/runtime/orphan-worker-registry.ts +37 -26
- package/src/runtime/output-validator.ts +18 -3
- package/src/runtime/overflow-recovery.ts +5 -4
- package/src/runtime/parallel-research.ts +29 -5
- package/src/runtime/parallel-utils.ts +9 -11
- package/src/runtime/path-overlap.ts +150 -0
- package/src/runtime/peer-dep.ts +12 -17
- package/src/runtime/per-write-validator.ts +1 -3
- package/src/runtime/phase-tracker.ts +342 -330
- package/src/runtime/pi-args.ts +26 -8
- package/src/runtime/pi-json-output.ts +1 -1
- package/src/runtime/pi-spawn.ts +30 -19
- package/src/runtime/pipeline-runner.ts +56 -55
- package/src/runtime/plan-templates.ts +5 -6
- package/src/runtime/policy-engine.ts +43 -7
- package/src/runtime/post-checks.ts +12 -4
- package/src/runtime/post-exit-stdio-guard.ts +2 -2
- package/src/runtime/process-lifecycle.ts +20 -10
- package/src/runtime/process-status.ts +16 -5
- package/src/runtime/progress-event-coalescer.ts +2 -1
- package/src/runtime/progress-tracker.ts +103 -103
- package/src/runtime/prose-compressor.ts +9 -11
- package/src/runtime/recovery-recipes.ts +118 -24
- package/src/runtime/replace.ts +25 -10
- package/src/runtime/resilient-edit.ts +10 -25
- package/src/runtime/result-extractor.ts +1 -2
- package/src/runtime/retry-executor.ts +20 -4
- package/src/runtime/retry-runner.ts +26 -50
- package/src/runtime/role-permission.ts +6 -1
- package/src/runtime/run-coalesced-task-group.ts +256 -0
- package/src/runtime/run-drift.ts +14 -15
- package/src/runtime/run-tracker.ts +6 -28
- package/src/runtime/runtime-policy.ts +1 -6
- package/src/runtime/runtime-resolver.ts +80 -15
- package/src/runtime/scheduler.ts +47 -18
- package/src/runtime/semaphore.ts +5 -7
- package/src/runtime/sensitive-paths.ts +2 -1
- package/src/runtime/session-usage.ts +1 -1
- package/src/runtime/settings-store.ts +16 -12
- package/src/runtime/sidechain-output.ts +6 -2
- package/src/runtime/single-agent-compose.ts +1 -4
- package/src/runtime/skill-effectiveness.ts +29 -97
- package/src/runtime/skill-instructions.ts +40 -83
- package/src/runtime/stale-reconciler.ts +73 -197
- package/src/runtime/stream-preview.ts +1 -1
- package/src/runtime/streaming-output.ts +1 -1
- package/src/runtime/subagent-manager.ts +86 -169
- package/src/runtime/subprocess-tool-registry.ts +4 -1
- package/src/runtime/supervisor-contact.ts +10 -5
- package/src/runtime/task-display.ts +19 -5
- package/src/runtime/task-graph-scheduler.ts +95 -21
- package/src/runtime/task-graph.ts +5 -11
- package/src/runtime/task-health.ts +54 -47
- package/src/runtime/task-id.ts +11 -18
- package/src/runtime/task-output-context.ts +265 -81
- package/src/runtime/task-packet.ts +12 -20
- package/src/runtime/task-quality.ts +6 -14
- package/src/runtime/task-runner/context-retrieval.ts +4 -13
- package/src/runtime/task-runner/live-executor.ts +114 -36
- package/src/runtime/task-runner/output-splitter.ts +152 -0
- package/src/runtime/task-runner/progress.ts +50 -12
- package/src/runtime/task-runner/prompt-builder.ts +31 -7
- package/src/runtime/task-runner/prompt-pipeline.ts +31 -7
- package/src/runtime/task-runner/result-utils.ts +3 -1
- package/src/runtime/task-runner/retrieval-orchestrator.ts +310 -0
- package/src/runtime/task-runner/run-projection.ts +27 -8
- package/src/runtime/task-runner/state-helpers.ts +29 -8
- package/src/runtime/task-runner/tail-read.ts +2 -6
- package/src/runtime/task-runner.ts +180 -326
- package/src/runtime/team-runner-artifacts.ts +13 -0
- package/src/runtime/team-runner.ts +456 -974
- package/src/runtime/tool-output-pruner.ts +6 -9
- package/src/runtime/tool-progress.ts +11 -21
- package/src/runtime/verification-gates.ts +33 -24
- package/src/runtime/verification-integrity.ts +1 -4
- package/src/runtime/verification-worktree.ts +62 -12
- package/src/runtime/worker-heartbeat.ts +5 -1
- package/src/runtime/worker-startup.ts +29 -5
- package/src/runtime/workflow-state.ts +17 -9
- package/src/runtime/workspace-lock.ts +30 -35
- package/src/runtime/workspace-tree.ts +20 -32
- package/src/runtime/yield-handler.ts +43 -9
- package/src/runtime/zombie-scanner.ts +5 -4
- package/src/schema/config-schema.ts +266 -172
- package/src/schema/team-tool-schema.ts +29 -74
- package/src/schema/validation-types.ts +25 -17
- package/src/skills/discover-skills.ts +35 -10
- package/src/skills/skill-templates.ts +109 -27
- package/src/skills/validate.ts +26 -8
- package/src/state/active-run-registry.ts +87 -28
- package/src/state/artifact-store.ts +9 -5
- package/src/state/atomic-write-v2.ts +85 -63
- package/src/state/atomic-write.ts +105 -26
- package/src/state/blob-store.ts +47 -20
- package/src/state/contracts.ts +20 -5
- package/src/state/crew-init.ts +5 -22
- package/src/state/decision-ledger.ts +19 -73
- package/src/state/event-log-rotation.ts +41 -15
- package/src/state/event-log.ts +168 -53
- package/src/state/event-reconstructor.ts +11 -1
- package/src/state/gitignore-manager.ts +2 -8
- package/src/state/health-store.ts +57 -57
- package/src/state/hook-instinct-bridge.ts +1 -1
- package/src/state/hook-integrations.ts +1 -1
- package/src/state/instinct-store.ts +17 -5
- package/src/state/jsonl-writer.ts +1 -1
- package/src/state/locks.ts +23 -9
- package/src/state/mailbox.ts +151 -28
- package/src/state/observation-store.ts +20 -14
- package/src/state/run-cache.ts +142 -135
- package/src/state/run-graph.ts +6 -15
- package/src/state/run-metrics.ts +5 -18
- package/src/state/schedule.ts +15 -9
- package/src/state/state-store.ts +129 -45
- package/src/state/task-claims.ts +12 -2
- package/src/state/tiered-eval.ts +52 -43
- package/src/state/types-eval.ts +2 -2
- package/src/state/types.ts +24 -20
- package/src/state/usage.ts +20 -4
- package/src/state/worker-atomic-writer.ts +6 -3
- package/src/subagents/index.ts +2 -2
- package/src/teams/discover-teams.ts +21 -6
- package/src/tools/safe-bash-extension.ts +5 -6
- package/src/tools/safe-bash.ts +10 -11
- package/src/types/new-api-types.ts +6 -10
- package/src/ui/agent-management-overlay.ts +52 -40
- package/src/ui/card-colors.ts +13 -4
- package/src/ui/crew-footer.ts +8 -7
- package/src/ui/crew-select-list.ts +1 -1
- package/src/ui/dashboard-panes/agents-pane.ts +41 -25
- package/src/ui/dashboard-panes/cancellation-pane.ts +1 -1
- package/src/ui/dashboard-panes/capability-pane.ts +32 -15
- package/src/ui/dashboard-panes/health-pane.ts +2 -1
- package/src/ui/dashboard-panes/metrics-pane.ts +4 -1
- package/src/ui/dashboard-panes/progress-pane.ts +12 -9
- package/src/ui/heartbeat-aggregator.ts +15 -4
- package/src/ui/keybinding-map.ts +40 -7
- package/src/ui/live-conversation-overlay.ts +36 -20
- package/src/ui/live-duration.ts +1 -4
- package/src/ui/live-run-sidebar.ts +88 -25
- package/src/ui/loaders.ts +2 -8
- package/src/ui/mascot.ts +20 -36
- package/src/ui/overlays/agent-picker-overlay.ts +11 -3
- package/src/ui/overlays/confirm-overlay.ts +4 -2
- package/src/ui/overlays/help-overlay.ts +25 -14
- package/src/ui/overlays/mailbox-compose-overlay.ts +48 -11
- package/src/ui/overlays/mailbox-compose-preview.ts +20 -5
- package/src/ui/overlays/mailbox-detail-overlay.ts +27 -6
- package/src/ui/pi-ui-compat.ts +7 -7
- package/src/ui/powerbar-publisher.ts +120 -42
- package/src/ui/render-diff.ts +10 -3
- package/src/ui/render-scheduler.ts +12 -4
- package/src/ui/run-action-dispatcher.ts +81 -18
- package/src/ui/run-dashboard.ts +172 -76
- package/src/ui/run-event-bus.ts +59 -37
- package/src/ui/run-snapshot-cache.ts +276 -86
- package/src/ui/settings-overlay.ts +361 -74
- package/src/ui/status-colors.ts +12 -1
- package/src/ui/syntax-highlight.ts +1 -1
- package/src/ui/theme-adapter.ts +16 -14
- package/src/ui/theme-discovery.ts +13 -2
- package/src/ui/tool-progress-formatter.ts +7 -7
- package/src/ui/tool-render.ts +128 -56
- package/src/ui/tool-renderers/brief-mode.ts +45 -27
- package/src/ui/tool-renderers/index.ts +109 -43
- package/src/ui/transcript-cache.ts +32 -6
- package/src/ui/transcript-entries.ts +25 -23
- package/src/ui/transcript-viewer.ts +78 -29
- package/src/ui/widget/index.ts +99 -40
- package/src/ui/widget/widget-formatters.ts +13 -6
- package/src/ui/widget/widget-model.ts +19 -8
- package/src/ui/widget/widget-renderer.ts +23 -13
- package/src/ui/widget/widget-types.ts +1 -1
- package/src/utils/bm25-search.ts +199 -199
- package/src/utils/conflict-detect.ts +22 -21
- package/src/utils/env-filter.ts +14 -7
- package/src/utils/file-coalescer.ts +5 -1
- package/src/utils/fingerprint.ts +3 -6
- package/src/utils/frontmatter.ts +3 -1
- package/src/utils/fs-watch.ts +2 -6
- package/src/utils/gh-protocol.ts +119 -44
- package/src/utils/git.ts +13 -15
- package/src/utils/guards.ts +2 -5
- package/src/utils/ids.ts +9 -2
- package/src/utils/incremental-reader.ts +14 -3
- package/src/utils/internal-error.ts +2 -1
- package/src/utils/names.ts +12 -3
- package/src/utils/paths.ts +49 -5
- package/src/utils/project-detector.ts +2 -2
- package/src/utils/redaction.ts +29 -19
- package/src/utils/resolve-shell.ts +9 -7
- package/src/utils/run-watcher-registry.ts +19 -31
- package/src/utils/safe-paths.ts +45 -24
- package/src/utils/scan-cache.ts +9 -2
- package/src/utils/session-utils.ts +2 -4
- package/src/utils/sleep.ts +12 -5
- package/src/utils/sse-parser.ts +5 -17
- package/src/utils/visual.ts +52 -32
- package/src/workflows/discover-workflows.ts +41 -109
- package/src/workflows/intermediate-store.ts +5 -21
- package/src/workflows/preflight-validator.ts +6 -33
- package/src/workflows/topology-analyzer.ts +5 -21
- package/src/workflows/workflow-config.ts +7 -6
- package/src/worktree/branch-freshness.ts +66 -8
- package/src/worktree/cleanup.ts +130 -20
- package/src/worktree/worktree-manager.ts +212 -69
- package/skills/artifact-analysis-loop/SKILL.md +0 -303
- package/skills/detection-pipeline-design/SKILL.md +0 -286
- package/skills/hunting-investigation-loop/SKILL.md +0 -402
- package/skills/incident-playbook-construction/SKILL.md +0 -384
- package/skills/security-review/SKILL.md +0 -561
- package/skills/threat-hypothesis-framework/SKILL.md +0 -176
- package/skills/ui-render-performance/SKILL.md +0 -58
- /package/docs/{followup-review-round3-2026-05-12.md → archive/followup-review-round3-2026-05-12.md} +0 -0
- /package/docs/{followup-review-round4-2026-05-13.md → archive/followup-review-round4-2026-05-13.md} +0 -0
- /package/docs/{pi-crew-bugs.md → archive/pi-crew-bugs.md} +0 -0
- /package/docs/{pi-crew-test-final.md → archive/pi-crew-test-final.md} +0 -0
- /package/docs/{pi-crew-test-results.md → archive/pi-crew-test-results.md} +0 -0
- /package/docs/{pi-crew-test-round2.md → archive/pi-crew-test-round2.md} +0 -0
- /package/docs/{pi-crew-test-round4.md → archive/pi-crew-test-round4.md} +0 -0
- /package/docs/{pi-crew-test-round5.md → archive/pi-crew-test-round5.md} +0 -0
- /package/docs/{pi-crew-test-round6.md → archive/pi-crew-test-round6.md} +0 -0
- /package/docs/{pi-crew-v0.5.10-audit-fix-plan.md → archive/pi-crew-v0.5.10-audit-fix-plan.md} +0 -0
- /package/docs/{pi-crew-v0.5.11-audit-fix-plan.md → archive/pi-crew-v0.5.11-audit-fix-plan.md} +0 -0
- /package/docs/{pi-crew-v0.5.12-audit-fix-plan.md → archive/pi-crew-v0.5.12-audit-fix-plan.md} +0 -0
- /package/docs/{pi-crew-v0.5.13-audit-fix-plan.md → archive/pi-crew-v0.5.13-audit-fix-plan.md} +0 -0
- /package/docs/{pi-crew-v0.5.14-audit-fix-plan.md → archive/pi-crew-v0.5.14-audit-fix-plan.md} +0 -0
- /package/docs/{pi-crew-v0.5.16-audit-fix-plan.md → archive/pi-crew-v0.5.16-audit-fix-plan.md} +0 -0
- /package/docs/{pi-crew-v0.5.17-audit-fix-plan.md → archive/pi-crew-v0.5.17-audit-fix-plan.md} +0 -0
- /package/docs/{pi-crew-v0.5.5-audit-fix-plan.md → archive/pi-crew-v0.5.5-audit-fix-plan.md} +0 -0
- /package/docs/{pi-crew-v0.5.9-audit-fix-plan.md → archive/pi-crew-v0.5.9-audit-fix-plan.md} +0 -0
- /package/docs/{pi-mono-opportunities.md → archive/pi-mono-opportunities.md} +0 -0
- /package/docs/{pi-mono-review.md → archive/pi-mono-review.md} +0 -0
- /package/docs/{pi-subagent4-comparison.md → archive/pi-subagent4-comparison.md} +0 -0
- /package/docs/{pi-subagents3-deep-analysis.md → archive/pi-subagents3-deep-analysis.md} +0 -0
|
@@ -1,107 +1,48 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import type { AgentConfig } from "../agents/agent-config.ts";
|
|
4
|
-
import type {
|
|
5
|
-
CrewLimitsConfig,
|
|
6
|
-
CrewReliabilityConfig,
|
|
7
|
-
CrewRuntimeConfig,
|
|
8
|
-
} from "../config/config.ts";
|
|
4
|
+
import type { CrewLimitsConfig, CrewReliabilityConfig, CrewRuntimeConfig } from "../config/config.ts";
|
|
9
5
|
import { appendHookEvent, executeHook } from "../hooks/registry.ts";
|
|
10
|
-
import {
|
|
11
|
-
childCorrelation,
|
|
12
|
-
withCorrelation,
|
|
13
|
-
} from "../observability/correlation.ts";
|
|
6
|
+
import { childCorrelation, withCorrelation } from "../observability/correlation.ts";
|
|
14
7
|
import type { MetricRegistry } from "../observability/metric-registry.ts";
|
|
15
8
|
import { PluginRegistry } from "../plugins/plugin-registry.ts";
|
|
16
|
-
import {
|
|
17
|
-
NextJsPlugin,
|
|
18
|
-
VitePlugin,
|
|
19
|
-
VitestPlugin,
|
|
20
|
-
} from "../plugins/plugins/index.ts";
|
|
9
|
+
import { NextJsPlugin, VitePlugin, VitestPlugin } from "../plugins/plugins/index.ts";
|
|
21
10
|
import { writeArtifact } from "../state/artifact-store.ts";
|
|
22
|
-
import {
|
|
23
|
-
appendEvent,
|
|
24
|
-
appendEventAsync,
|
|
25
|
-
appendEventFireAndForget,
|
|
26
|
-
} from "../state/event-log.ts";
|
|
11
|
+
import { appendEvent, appendEventAsync, appendEventBuffered, flushEventLogBuffer } from "../state/event-log.ts";
|
|
27
12
|
import { HealthStore } from "../state/health-store.ts";
|
|
28
13
|
import { withRunLock } from "../state/locks.ts";
|
|
29
|
-
import {
|
|
30
|
-
|
|
31
|
-
saveRunManifest,
|
|
32
|
-
saveRunManifestAsync,
|
|
33
|
-
saveRunTasksAsync,
|
|
34
|
-
updateRunStatus,
|
|
35
|
-
} from "../state/state-store.ts";
|
|
36
|
-
import type {
|
|
37
|
-
ArtifactDescriptor,
|
|
38
|
-
PolicyDecision,
|
|
39
|
-
TaskAttemptState,
|
|
40
|
-
TeamRunManifest,
|
|
41
|
-
TeamTaskState,
|
|
42
|
-
} from "../state/types.ts";
|
|
14
|
+
import { loadRunManifestById, saveRunManifest, saveRunManifestAsync, saveRunTasksAsync, updateRunStatus } from "../state/state-store.ts";
|
|
15
|
+
import type { ArtifactDescriptor, PolicyDecision, TaskAttemptState, TeamRunManifest, TeamTaskState } from "../state/types.ts";
|
|
43
16
|
import { aggregateUsage, formatUsage } from "../state/usage.ts";
|
|
44
17
|
import type { TeamConfig } from "../teams/team-config.ts";
|
|
45
18
|
import { logInternalError } from "../utils/internal-error.ts";
|
|
46
|
-
import type {
|
|
47
|
-
WorkflowConfig,
|
|
48
|
-
WorkflowStep,
|
|
49
|
-
} from "../workflows/workflow-config.ts";
|
|
19
|
+
import type { WorkflowConfig, WorkflowStep } from "../workflows/workflow-config.ts";
|
|
50
20
|
import { checkBranchFreshness } from "../worktree/branch-freshness.ts";
|
|
51
|
-
import {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
} from "./cancellation.ts";
|
|
21
|
+
import { mergeArtifacts } from "./team-runner-artifacts.ts";
|
|
22
|
+
import { buildSyntheticTerminalEvidence, CrewCancellationError, cancellationReasonFromSignal } from "./cancellation.ts";
|
|
23
|
+
import { planCoalescedGroups, buildDispatchUnits } from "./coalesce-tasks.ts";
|
|
24
|
+
import { runCoalescedTaskGroup } from "./run-coalesced-task-group.ts";
|
|
56
25
|
import { resolveBatchConcurrency } from "./concurrency.ts";
|
|
57
26
|
import { readCrewAgents, saveCrewAgents } from "./crew-agent-records.ts";
|
|
58
27
|
import type { CrewRuntimeKind } from "./crew-agent-runtime.ts";
|
|
59
28
|
import { crewHooks } from "./crew-hooks.ts";
|
|
60
29
|
import { appendDeadletter } from "./deadletter.ts";
|
|
61
|
-
import {
|
|
62
|
-
|
|
63
|
-
evaluateRunEffectiveness,
|
|
64
|
-
formatRunEffectivenessLines,
|
|
65
|
-
} from "./effectiveness.ts";
|
|
66
|
-
import {
|
|
67
|
-
applyGoalAchievement,
|
|
68
|
-
assessGoalAchievement,
|
|
69
|
-
} from "./goal-achievement.ts";
|
|
30
|
+
import { effectivenessPolicyDecision, evaluateRunEffectiveness, formatRunEffectivenessLines } from "./effectiveness.ts";
|
|
31
|
+
import { applyGoalAchievement, assessGoalAchievement } from "./goal-achievement.ts";
|
|
70
32
|
import { deliverGroupJoin, resolveGroupJoinMode } from "./group-join.ts";
|
|
71
33
|
import { terminateLiveAgentsForRun } from "./live-agent-manager.ts";
|
|
72
34
|
import { mapConcurrent } from "./parallel-utils.ts";
|
|
73
|
-
import {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
} from "./
|
|
77
|
-
import {
|
|
78
|
-
buildRecoveryLedger,
|
|
79
|
-
shouldRerunFailedTask,
|
|
80
|
-
} from "./recovery-recipes.ts";
|
|
81
|
-
import {
|
|
82
|
-
DEFAULT_RETRY_POLICY,
|
|
83
|
-
executeWithRetry,
|
|
84
|
-
type RetryPolicy,
|
|
85
|
-
} from "./retry-executor.ts";
|
|
35
|
+
import { filterReadyByWriteOverlap } from "./path-overlap.ts";
|
|
36
|
+
import { evaluateCrewPolicy, summarizePolicyDecisions } from "./policy-engine.ts";
|
|
37
|
+
import { buildRecoveryLedger, shouldRerunFailedTask } from "./recovery-recipes.ts";
|
|
38
|
+
import { DEFAULT_RETRY_POLICY, executeWithRetry, type RetryPolicy } from "./retry-executor.ts";
|
|
86
39
|
import { permissionForRole } from "./role-permission.ts";
|
|
87
|
-
import {
|
|
88
|
-
registerRunPromise,
|
|
89
|
-
rejectRunPromise,
|
|
90
|
-
resolveRunPromise,
|
|
91
|
-
} from "./run-tracker.ts";
|
|
40
|
+
import { registerRunPromise, rejectRunPromise, resolveRunPromise } from "./run-tracker.ts";
|
|
92
41
|
import { resolveTaskRuntimeKind } from "./runtime-policy.ts";
|
|
93
42
|
import type { CrewRuntimeCapabilities } from "./runtime-resolver.ts";
|
|
94
43
|
import { recordsForMaterializedTasks } from "./task-display.ts";
|
|
95
|
-
import {
|
|
96
|
-
|
|
97
|
-
getReadyTasks as getDagReadyTasks,
|
|
98
|
-
type TaskNode,
|
|
99
|
-
} from "./task-graph.ts";
|
|
100
|
-
import {
|
|
101
|
-
buildTaskGraphIndex,
|
|
102
|
-
refreshTaskGraphQueues,
|
|
103
|
-
taskGraphSnapshot,
|
|
104
|
-
} from "./task-graph-scheduler.ts";
|
|
44
|
+
import { buildExecutionPlan as buildDagExecutionPlan, getReadyTasks as getDagReadyTasks, type TaskNode } from "./task-graph.ts";
|
|
45
|
+
import { buildTaskGraphIndex, refreshTaskGraphQueues, taskGraphSnapshot } from "./task-graph-scheduler.ts";
|
|
105
46
|
import { aggregateTaskOutputs } from "./task-output-context.ts";
|
|
106
47
|
import { runTeamTask } from "./task-runner.ts";
|
|
107
48
|
import { clearTrackedTaskUsage } from "./usage-tracker.ts";
|
|
@@ -192,22 +133,14 @@ export interface ExecuteTeamRunInput {
|
|
|
192
133
|
}
|
|
193
134
|
|
|
194
135
|
function findStep(workflow: WorkflowConfig, task: TeamTaskState): WorkflowStep {
|
|
195
|
-
const step = workflow.steps.find(
|
|
196
|
-
|
|
197
|
-
);
|
|
198
|
-
if (!step)
|
|
199
|
-
throw new Error(
|
|
200
|
-
`Workflow step '${task.stepId}' not found for task '${task.id}'.`,
|
|
201
|
-
);
|
|
136
|
+
const step = workflow.steps.find((candidate) => candidate.id === task.stepId);
|
|
137
|
+
if (!step) throw new Error(`Workflow step '${task.stepId}' not found for task '${task.id}'.`);
|
|
202
138
|
return step;
|
|
203
139
|
}
|
|
204
140
|
|
|
205
141
|
function findAgent(agents: AgentConfig[], task: TeamTaskState): AgentConfig {
|
|
206
142
|
const agent = agents.find((candidate) => candidate.name === task.agent);
|
|
207
|
-
if (!agent)
|
|
208
|
-
throw new Error(
|
|
209
|
-
`Agent '${task.agent}' not found for task '${task.id}'.`,
|
|
210
|
-
);
|
|
143
|
+
if (!agent) throw new Error(`Agent '${task.agent}' not found for task '${task.id}'.`);
|
|
211
144
|
return agent;
|
|
212
145
|
}
|
|
213
146
|
|
|
@@ -219,20 +152,12 @@ function markBlocked(tasks: TeamTaskState[], reason: string): TeamTaskState[] {
|
|
|
219
152
|
status: "skipped",
|
|
220
153
|
error: reason,
|
|
221
154
|
finishedAt: new Date().toISOString(),
|
|
222
|
-
graph: task.graph
|
|
223
|
-
? { ...task.graph, queue: "blocked" }
|
|
224
|
-
: undefined,
|
|
155
|
+
graph: task.graph ? { ...task.graph, queue: "blocked" } : undefined,
|
|
225
156
|
}
|
|
226
157
|
: task,
|
|
227
158
|
);
|
|
228
159
|
}
|
|
229
160
|
|
|
230
|
-
function mergeArtifacts(items: ArtifactDescriptor[]): ArtifactDescriptor[] {
|
|
231
|
-
const byPath = new Map<string, ArtifactDescriptor>();
|
|
232
|
-
for (const item of items) byPath.set(item.path, item);
|
|
233
|
-
return [...byPath.values()];
|
|
234
|
-
}
|
|
235
|
-
|
|
236
161
|
function isNonTerminalTaskStatus(status: TeamTaskState["status"]): boolean {
|
|
237
162
|
return status === "queued" || status === "running" || status === "waiting";
|
|
238
163
|
}
|
|
@@ -253,22 +178,15 @@ function safeFinishedAt(task: TeamTaskState): number {
|
|
|
253
178
|
* and the updated task has a valid finite finishedAt. Malformed finishedAt
|
|
254
179
|
* should be replaced rather than persisting corruption.
|
|
255
180
|
*/
|
|
256
|
-
function isMalformedFinishedAtReplacement(
|
|
257
|
-
currentTime: number,
|
|
258
|
-
updatedTime: number,
|
|
259
|
-
): boolean {
|
|
181
|
+
function isMalformedFinishedAtReplacement(currentTime: number, updatedTime: number): boolean {
|
|
260
182
|
return !Number.isFinite(currentTime) && Number.isFinite(updatedTime);
|
|
261
183
|
}
|
|
262
184
|
|
|
263
|
-
function shouldMergeTaskUpdate(
|
|
264
|
-
current: TeamTaskState,
|
|
265
|
-
updated: TeamTaskState,
|
|
266
|
-
): boolean {
|
|
185
|
+
function shouldMergeTaskUpdate(current: TeamTaskState, updated: TeamTaskState): boolean {
|
|
267
186
|
// Parallel workers receive the same input snapshot. A later result may still
|
|
268
187
|
// contain stale queued/running copies of tasks that another worker already
|
|
269
188
|
// completed. Never let those stale snapshots regress durable task state.
|
|
270
|
-
if (current.status === "waiting" && updated.status === "running")
|
|
271
|
-
return false;
|
|
189
|
+
if (current.status === "waiting" && updated.status === "running") return false;
|
|
272
190
|
// Block terminal→non-terminal transitions (e.g. completed→running).
|
|
273
191
|
// A task that has reached a terminal state must not be resurrected.
|
|
274
192
|
const currentIsTerminal = !isNonTerminalTaskStatus(current.status);
|
|
@@ -277,34 +195,21 @@ function shouldMergeTaskUpdate(
|
|
|
277
195
|
// Explicitly block completed↔needs_attention terminal-to-terminal transitions.
|
|
278
196
|
// Both are success terminal states used interchangeably; stale worker updates must
|
|
279
197
|
// not cause a completed task to appear as needs_attention or vice versa.
|
|
280
|
-
if (current.status === "completed" && updated.status === "needs_attention")
|
|
281
|
-
|
|
282
|
-
if (current.status === "needs_attention" && updated.status === "completed")
|
|
283
|
-
return false;
|
|
198
|
+
if (current.status === "completed" && updated.status === "needs_attention") return false;
|
|
199
|
+
if (current.status === "needs_attention" && updated.status === "completed") return false;
|
|
284
200
|
// Explicitly block failed→completed resurrection. Both statuses are terminal,
|
|
285
201
|
// but completed is the success terminal state and should not be reachable from
|
|
286
202
|
// failed via a stale merge. The check above only guards non-terminal→terminal.
|
|
287
|
-
if (current.status === "failed" && updated.status === "completed")
|
|
288
|
-
return false;
|
|
203
|
+
if (current.status === "failed" && updated.status === "completed") return false;
|
|
289
204
|
// Guard: when current is "running" but has resultArtifact (another worker already
|
|
290
205
|
// completed it), a stale updated with status="running" and no resultArtifact
|
|
291
206
|
// must not overwrite the actual completed state.
|
|
292
|
-
if (
|
|
293
|
-
current.status === updated.status &&
|
|
294
|
-
updated.status === "running" &&
|
|
295
|
-
current.resultArtifact &&
|
|
296
|
-
!updated.resultArtifact
|
|
297
|
-
)
|
|
207
|
+
if (current.status === updated.status && updated.status === "running" && current.resultArtifact && !updated.resultArtifact)
|
|
298
208
|
return false;
|
|
299
209
|
// Guard: when current is "completed" and has resultArtifact but updated is also
|
|
300
210
|
// "completed" without resultArtifact, block the stale update from overwriting
|
|
301
211
|
// a task that successfully produced output.
|
|
302
|
-
if (
|
|
303
|
-
current.status === updated.status &&
|
|
304
|
-
current.status === "completed" &&
|
|
305
|
-
current.resultArtifact &&
|
|
306
|
-
!updated.resultArtifact
|
|
307
|
-
)
|
|
212
|
+
if (current.status === updated.status && current.status === "completed" && current.resultArtifact && !updated.resultArtifact)
|
|
308
213
|
return false;
|
|
309
214
|
// Prevent a stale completed task from overwriting a fresher one.
|
|
310
215
|
// Restructure to handle undefined current.finishedAt as a special case:
|
|
@@ -318,9 +223,7 @@ function shouldMergeTaskUpdate(
|
|
|
318
223
|
// Malformed finishedAt (NaN) is treated as Infinity — invalid state should be
|
|
319
224
|
// replaced rather than persisting corruption. Log warning for visibility.
|
|
320
225
|
if (!Number.isFinite(currentTime)) {
|
|
321
|
-
console.warn(
|
|
322
|
-
`[team-runner] Task ${current.id} has malformed finishedAt: ${current.finishedAt}`,
|
|
323
|
-
);
|
|
226
|
+
console.warn(`[team-runner] Task ${current.id} has malformed finishedAt: ${current.finishedAt}`);
|
|
324
227
|
}
|
|
325
228
|
if (isMalformedFinishedAtReplacement(currentTime, updatedTime)) {
|
|
326
229
|
return true;
|
|
@@ -330,8 +233,7 @@ function shouldMergeTaskUpdate(
|
|
|
330
233
|
// Block if updated is trying to establish a terminal status without a finishedAt
|
|
331
234
|
// timestamp. Heartbeat-only updates (status='running', no finishedAt) are
|
|
332
235
|
// allowed if heartbeat has changed (checked separately in hasMeaningfulUpdate).
|
|
333
|
-
if (!updated.finishedAt && !isNonTerminalTaskStatus(updated.status))
|
|
334
|
-
return false;
|
|
236
|
+
if (!updated.finishedAt && !isNonTerminalTaskStatus(updated.status)) return false;
|
|
335
237
|
// Explicitly enumerate all fields that constitute a meaningful update so that
|
|
336
238
|
// adding a new important field requires updating this list (rather than silently
|
|
337
239
|
// losing data if a field is forgotten in the boolean OR chain below).
|
|
@@ -340,25 +242,20 @@ function shouldMergeTaskUpdate(
|
|
|
340
242
|
updated.finishedAt !== current.finishedAt ||
|
|
341
243
|
updated.startedAt !== current.startedAt ||
|
|
342
244
|
Boolean(updated.resultArtifact) !== Boolean(current.resultArtifact) ||
|
|
343
|
-
(Boolean(updated.resultArtifact) &&
|
|
344
|
-
updated.resultArtifact !== current.resultArtifact) ||
|
|
245
|
+
(Boolean(updated.resultArtifact) && updated.resultArtifact !== current.resultArtifact) ||
|
|
345
246
|
Boolean(updated.error) ||
|
|
346
247
|
Boolean(updated.modelAttempts?.length) ||
|
|
347
248
|
Boolean(updated.usage) ||
|
|
348
249
|
Boolean(updated.attempts?.length) ||
|
|
349
250
|
updated.heartbeat?.lastSeenAt !== current.heartbeat?.lastSeenAt ||
|
|
350
251
|
updated.jsonEvents !== current.jsonEvents ||
|
|
351
|
-
updated.agentProgress?.lastActivityAt !==
|
|
352
|
-
current.agentProgress?.lastActivityAt;
|
|
252
|
+
updated.agentProgress?.lastActivityAt !== current.agentProgress?.lastActivityAt;
|
|
353
253
|
return hasMeaningfulUpdate;
|
|
354
254
|
}
|
|
355
255
|
|
|
356
256
|
// H4 fix: rename to descriptive name. Kept __test__ as alias for backward
|
|
357
257
|
// compat test imports.
|
|
358
|
-
export function mergeTaskUpdatesPreservingTerminal(
|
|
359
|
-
base: TeamTaskState[],
|
|
360
|
-
results: Array<{ tasks: TeamTaskState[] }>,
|
|
361
|
-
): TeamTaskState[] {
|
|
258
|
+
export function mergeTaskUpdatesPreservingTerminal(base: TeamTaskState[], results: Array<{ tasks: TeamTaskState[] }>): TeamTaskState[] {
|
|
362
259
|
let merged = base;
|
|
363
260
|
for (const result of results) {
|
|
364
261
|
for (const updated of result.tasks) {
|
|
@@ -368,21 +265,15 @@ export function mergeTaskUpdatesPreservingTerminal(
|
|
|
368
265
|
// Log skipped merges for visibility into rejected parallel updates.
|
|
369
266
|
// In distributed systems with parallel workers, rejected merges may
|
|
370
267
|
// indicate bugs (wrong status, timestamp corruption) if they accumulate.
|
|
371
|
-
console.debug(
|
|
372
|
-
|
|
373
|
-
updated.
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
currentFinishedAt: current.finishedAt,
|
|
378
|
-
updatedFinishedAt: updated.finishedAt,
|
|
379
|
-
},
|
|
380
|
-
);
|
|
268
|
+
console.debug("[team-runner] Skipping stale merge for task", updated.id, {
|
|
269
|
+
currentStatus: current.status,
|
|
270
|
+
updatedStatus: updated.status,
|
|
271
|
+
currentFinishedAt: current.finishedAt,
|
|
272
|
+
updatedFinishedAt: updated.finishedAt,
|
|
273
|
+
});
|
|
381
274
|
continue;
|
|
382
275
|
}
|
|
383
|
-
merged = merged.map((task) =>
|
|
384
|
-
task.id === updated.id ? updated : task,
|
|
385
|
-
);
|
|
276
|
+
merged = merged.map((task) => (task.id === updated.id ? updated : task));
|
|
386
277
|
}
|
|
387
278
|
}
|
|
388
279
|
return refreshTaskGraphQueues(merged);
|
|
@@ -427,8 +318,7 @@ function writeProgress(
|
|
|
427
318
|
runtimeConfig?: CrewRuntimeConfig,
|
|
428
319
|
): TeamRunManifest {
|
|
429
320
|
const counts = new Map<string, number>();
|
|
430
|
-
for (const task of tasks)
|
|
431
|
-
counts.set(task.status, (counts.get(task.status) ?? 0) + 1);
|
|
321
|
+
for (const task of tasks) counts.set(task.status, (counts.get(task.status) ?? 0) + 1);
|
|
432
322
|
const queue = taskGraphSnapshot(tasks);
|
|
433
323
|
const progress = writeArtifact(manifest.artifactsRoot, {
|
|
434
324
|
kind: "progress",
|
|
@@ -448,12 +338,7 @@ function writeProgress(
|
|
|
448
338
|
...tasks.map(formatTaskProgress),
|
|
449
339
|
"",
|
|
450
340
|
"## Effectiveness",
|
|
451
|
-
...runEffectivenessLines(
|
|
452
|
-
manifest,
|
|
453
|
-
tasks,
|
|
454
|
-
executeWorkers,
|
|
455
|
-
runtimeConfig,
|
|
456
|
-
),
|
|
341
|
+
...runEffectivenessLines(manifest, tasks, executeWorkers, runtimeConfig),
|
|
457
342
|
"",
|
|
458
343
|
].join("\n"),
|
|
459
344
|
});
|
|
@@ -461,26 +346,13 @@ function writeProgress(
|
|
|
461
346
|
...manifest,
|
|
462
347
|
updatedAt: new Date().toISOString(),
|
|
463
348
|
artifacts: [
|
|
464
|
-
...manifest.artifacts.filter(
|
|
465
|
-
(artifact) =>
|
|
466
|
-
!(
|
|
467
|
-
artifact.kind === "progress" &&
|
|
468
|
-
artifact.path === progress.path
|
|
469
|
-
),
|
|
470
|
-
),
|
|
349
|
+
...manifest.artifacts.filter((artifact) => !(artifact.kind === "progress" && artifact.path === progress.path)),
|
|
471
350
|
progress,
|
|
472
|
-
].filter(
|
|
473
|
-
(artifact, index, self) =>
|
|
474
|
-
self.findIndex((a) => a.path === artifact.path) === index,
|
|
475
|
-
),
|
|
351
|
+
].filter((artifact, index, self) => self.findIndex((a) => a.path === artifact.path) === index),
|
|
476
352
|
};
|
|
477
353
|
}
|
|
478
354
|
|
|
479
|
-
function applyPolicy(
|
|
480
|
-
manifest: TeamRunManifest,
|
|
481
|
-
tasks: TeamTaskState[],
|
|
482
|
-
limits?: CrewLimitsConfig,
|
|
483
|
-
): TeamRunManifest {
|
|
355
|
+
function applyPolicy(manifest: TeamRunManifest, tasks: TeamTaskState[], limits?: CrewLimitsConfig): TeamRunManifest {
|
|
484
356
|
const branchFreshness = checkBranchFreshness(manifest.cwd);
|
|
485
357
|
const branchArtifact = writeArtifact(manifest.artifactsRoot, {
|
|
486
358
|
kind: "metadata",
|
|
@@ -493,10 +365,7 @@ function applyPolicy(
|
|
|
493
365
|
tasks,
|
|
494
366
|
limits,
|
|
495
367
|
});
|
|
496
|
-
if (
|
|
497
|
-
branchFreshness.status === "stale" ||
|
|
498
|
-
branchFreshness.status === "diverged"
|
|
499
|
-
) {
|
|
368
|
+
if (branchFreshness.status === "stale" || branchFreshness.status === "diverged") {
|
|
500
369
|
const branchDecision: PolicyDecision = {
|
|
501
370
|
action: "notify",
|
|
502
371
|
reason: "branch_stale",
|
|
@@ -526,10 +395,7 @@ function applyPolicy(
|
|
|
526
395
|
});
|
|
527
396
|
for (const item of decisions)
|
|
528
397
|
appendEvent(manifest.eventsPath, {
|
|
529
|
-
type:
|
|
530
|
-
item.action === "escalate"
|
|
531
|
-
? "policy.escalated"
|
|
532
|
-
: "policy.action",
|
|
398
|
+
type: item.action === "escalate" ? "policy.escalated" : "policy.action",
|
|
533
399
|
runId: manifest.runId,
|
|
534
400
|
taskId: item.taskId,
|
|
535
401
|
message: item.message,
|
|
@@ -537,10 +403,7 @@ function applyPolicy(
|
|
|
537
403
|
});
|
|
538
404
|
for (const item of recoveryLedger.entries)
|
|
539
405
|
appendEvent(manifest.eventsPath, {
|
|
540
|
-
type:
|
|
541
|
-
item.state === "escalation_required"
|
|
542
|
-
? "recovery.escalated"
|
|
543
|
-
: "recovery.attempted",
|
|
406
|
+
type: item.state === "escalation_required" ? "recovery.escalated" : "recovery.attempted",
|
|
544
407
|
runId: manifest.runId,
|
|
545
408
|
taskId: item.taskId,
|
|
546
409
|
message: item.message,
|
|
@@ -572,9 +435,7 @@ function applyPolicy(
|
|
|
572
435
|
};
|
|
573
436
|
}
|
|
574
437
|
|
|
575
|
-
function retryPolicyFromConfig(
|
|
576
|
-
config: CrewReliabilityConfig | undefined,
|
|
577
|
-
): RetryPolicy {
|
|
438
|
+
function retryPolicyFromConfig(config: CrewReliabilityConfig | undefined): RetryPolicy {
|
|
578
439
|
return { ...DEFAULT_RETRY_POLICY, ...(config?.retryPolicy ?? {}) };
|
|
579
440
|
}
|
|
580
441
|
|
|
@@ -584,25 +445,15 @@ function retryPolicyFromConfig(
|
|
|
584
445
|
* automatically. Previously opt-in, which left the entire retry+recovery stack dormant.
|
|
585
446
|
* Exported for unit testing.
|
|
586
447
|
*/
|
|
587
|
-
export function shouldUseRetry(
|
|
588
|
-
reliability: CrewReliabilityConfig | undefined,
|
|
589
|
-
): boolean {
|
|
448
|
+
export function shouldUseRetry(reliability: CrewReliabilityConfig | undefined): boolean {
|
|
590
449
|
return reliability?.autoRetry !== false;
|
|
591
450
|
}
|
|
592
451
|
|
|
593
|
-
function failedTaskFrom(
|
|
594
|
-
result
|
|
595
|
-
taskId: string,
|
|
596
|
-
): TeamTaskState | undefined {
|
|
597
|
-
return result.tasks.find(
|
|
598
|
-
(item) => item.id === taskId && item.status === "failed",
|
|
599
|
-
);
|
|
452
|
+
function failedTaskFrom(result: { tasks: TeamTaskState[] }, taskId: string): TeamTaskState | undefined {
|
|
453
|
+
return result.tasks.find((item) => item.id === taskId && item.status === "failed");
|
|
600
454
|
}
|
|
601
455
|
|
|
602
|
-
function requiresPlanApproval(
|
|
603
|
-
_workflow: WorkflowConfig,
|
|
604
|
-
runtimeConfig: CrewRuntimeConfig | undefined,
|
|
605
|
-
): boolean {
|
|
456
|
+
function requiresPlanApproval(_workflow: WorkflowConfig, runtimeConfig: CrewRuntimeConfig | undefined): boolean {
|
|
606
457
|
// ROADMAP T1.2: plan-level HITL applies to ANY workflow when
|
|
607
458
|
// config.runtime.requirePlanApproval === true (not just 'implementation').
|
|
608
459
|
// The gate fires at the read-only → mutating (plan → execute) boundary.
|
|
@@ -610,31 +461,19 @@ function requiresPlanApproval(
|
|
|
610
461
|
}
|
|
611
462
|
|
|
612
463
|
function isPlanApprovalPending(manifest: TeamRunManifest): boolean {
|
|
613
|
-
return
|
|
614
|
-
manifest.planApproval?.required === true &&
|
|
615
|
-
manifest.planApproval.status === "pending"
|
|
616
|
-
);
|
|
464
|
+
return manifest.planApproval?.required === true && manifest.planApproval.status === "pending";
|
|
617
465
|
}
|
|
618
466
|
|
|
619
467
|
function isMutatingTask(task: TeamTaskState): boolean {
|
|
620
468
|
return permissionForRole(task.role) !== "read_only";
|
|
621
469
|
}
|
|
622
470
|
|
|
623
|
-
function ensurePlanApprovalRequested(
|
|
624
|
-
manifest: TeamRunManifest,
|
|
625
|
-
tasks: TeamTaskState[],
|
|
626
|
-
): TeamRunManifest {
|
|
471
|
+
function ensurePlanApprovalRequested(manifest: TeamRunManifest, tasks: TeamTaskState[]): TeamRunManifest {
|
|
627
472
|
if (manifest.planApproval) return manifest;
|
|
628
|
-
const assessTask = tasks.find(
|
|
629
|
-
(task) => task.stepId === "assess" && task.status === "completed",
|
|
630
|
-
);
|
|
473
|
+
const assessTask = tasks.find((task) => task.stepId === "assess" && task.status === "completed");
|
|
631
474
|
// ROADMAP T1.2: for non-adaptive workflows, fall back to the most recent
|
|
632
475
|
// completed read-only (planning) task as the plan reference.
|
|
633
|
-
const planTask =
|
|
634
|
-
assessTask ??
|
|
635
|
-
[...tasks]
|
|
636
|
-
.reverse()
|
|
637
|
-
.find((t) => t.status === "completed" && !isMutatingTask(t));
|
|
476
|
+
const planTask = assessTask ?? [...tasks].reverse().find((t) => t.status === "completed" && !isMutatingTask(t));
|
|
638
477
|
const now = new Date().toISOString();
|
|
639
478
|
const updated: TeamRunManifest = {
|
|
640
479
|
...manifest,
|
|
@@ -653,39 +492,28 @@ function ensurePlanApprovalRequested(
|
|
|
653
492
|
type: "plan.approval_required",
|
|
654
493
|
runId: updated.runId,
|
|
655
494
|
taskId: planTask?.id,
|
|
656
|
-
message:
|
|
657
|
-
"Plan requires explicit approval before mutating tasks run. Use: team api op=approve-plan runId=...",
|
|
495
|
+
message: "Plan requires explicit approval before mutating tasks run. Use: team api op=approve-plan runId=...",
|
|
658
496
|
data: { planArtifactPath: planTask?.resultArtifact?.path },
|
|
659
497
|
});
|
|
660
498
|
return updated;
|
|
661
499
|
}
|
|
662
500
|
|
|
663
|
-
function cancelPlanTasks(
|
|
664
|
-
tasks: TeamTaskState[],
|
|
665
|
-
reason: string,
|
|
666
|
-
): TeamTaskState[] {
|
|
501
|
+
function cancelPlanTasks(tasks: TeamTaskState[], reason: string): TeamTaskState[] {
|
|
667
502
|
return tasks.map((task) =>
|
|
668
|
-
task.status === "queued" ||
|
|
669
|
-
task.status === "running" ||
|
|
670
|
-
task.status === "waiting"
|
|
503
|
+
task.status === "queued" || task.status === "running" || task.status === "waiting"
|
|
671
504
|
? {
|
|
672
505
|
...task,
|
|
673
506
|
status: "cancelled",
|
|
674
507
|
finishedAt: new Date().toISOString(),
|
|
675
508
|
error: reason,
|
|
676
|
-
graph: task.graph
|
|
677
|
-
? { ...task.graph, queue: "done" }
|
|
678
|
-
: undefined,
|
|
509
|
+
graph: task.graph ? { ...task.graph, queue: "done" } : undefined,
|
|
679
510
|
}
|
|
680
511
|
: task,
|
|
681
512
|
);
|
|
682
513
|
}
|
|
683
514
|
|
|
684
515
|
function hasPendingMutatingAdaptiveTask(tasks: TeamTaskState[]): boolean {
|
|
685
|
-
return tasks.some(
|
|
686
|
-
(task) =>
|
|
687
|
-
task.status === "queued" && task.adaptive && isMutatingTask(task),
|
|
688
|
-
);
|
|
516
|
+
return tasks.some((task) => task.status === "queued" && task.adaptive && isMutatingTask(task));
|
|
689
517
|
}
|
|
690
518
|
|
|
691
519
|
/**
|
|
@@ -693,15 +521,9 @@ function hasPendingMutatingAdaptiveTask(tasks: TeamTaskState[]): boolean {
|
|
|
693
521
|
* Fires when there are pending mutating tasks whose prerequisites (read-only
|
|
694
522
|
* tasks) have completed — i.e. the plan→execute boundary.
|
|
695
523
|
*/
|
|
696
|
-
export function hasPendingMutatingTaskAtBoundary(
|
|
697
|
-
tasks
|
|
698
|
-
)
|
|
699
|
-
const hasCompletedReadOnly = tasks.some(
|
|
700
|
-
(t) => t.status === "completed" && !isMutatingTask(t),
|
|
701
|
-
);
|
|
702
|
-
const hasPendingMutating = tasks.some(
|
|
703
|
-
(t) => t.status === "queued" && isMutatingTask(t),
|
|
704
|
-
);
|
|
524
|
+
export function hasPendingMutatingTaskAtBoundary(tasks: TeamTaskState[]): boolean {
|
|
525
|
+
const hasCompletedReadOnly = tasks.some((t) => t.status === "completed" && !isMutatingTask(t));
|
|
526
|
+
const hasPendingMutating = tasks.some((t) => t.status === "queued" && isMutatingTask(t));
|
|
705
527
|
return hasCompletedReadOnly && hasPendingMutating;
|
|
706
528
|
}
|
|
707
529
|
|
|
@@ -710,10 +532,7 @@ export function hasPendingMutatingTaskAtBoundary(
|
|
|
710
532
|
* execution planning. If so, build an execution plan and use `getDagReadyTasks`
|
|
711
533
|
* to augment the ready-set selection.
|
|
712
534
|
*/
|
|
713
|
-
function dagReadyTaskIds(
|
|
714
|
-
tasks: TeamTaskState[],
|
|
715
|
-
completedIds: Set<string>,
|
|
716
|
-
): string[] | null {
|
|
535
|
+
function dagReadyTaskIds(tasks: TeamTaskState[], completedIds: Set<string>): string[] | null {
|
|
717
536
|
const hasExplicitDeps = tasks.some((t) => t.dependsOn.length > 0);
|
|
718
537
|
if (!hasExplicitDeps) return null;
|
|
719
538
|
// FIX (goal-wrap runtime test): task.dependsOn stores STEP IDs (e.g. "execute"), not
|
|
@@ -736,9 +555,7 @@ function dagReadyTaskIds(
|
|
|
736
555
|
return getDagReadyTasks(plan, completedIds);
|
|
737
556
|
}
|
|
738
557
|
|
|
739
|
-
export async function executeTeamRun(
|
|
740
|
-
input: ExecuteTeamRunInput,
|
|
741
|
-
): Promise<{ manifest: TeamRunManifest; tasks: TeamTaskState[] }> {
|
|
558
|
+
export async function executeTeamRun(input: ExecuteTeamRunInput): Promise<{ manifest: TeamRunManifest; tasks: TeamTaskState[] }> {
|
|
742
559
|
const workflow = input.workflow;
|
|
743
560
|
|
|
744
561
|
// DEFENSE-IN-DEPTH (advisory-only since v0.9.15): re-validate topology here in
|
|
@@ -748,30 +565,17 @@ export async function executeTeamRun(
|
|
|
748
565
|
// Skip for synthetic direct-agent workflows (filePath="<generated>").
|
|
749
566
|
if (workflow.filePath !== "<generated>") {
|
|
750
567
|
// LAZY: defer preflight-validator import until the defense-in-depth guard actually runs.
|
|
751
|
-
const { validateWorkflowUsage } = await import(
|
|
752
|
-
"../workflows/preflight-validator.ts"
|
|
753
|
-
);
|
|
568
|
+
const { validateWorkflowUsage } = await import("../workflows/preflight-validator.ts");
|
|
754
569
|
const preflight = validateWorkflowUsage(workflow, {
|
|
755
570
|
force: input.reliability?.forcePreflight === true,
|
|
756
571
|
});
|
|
757
|
-
if (
|
|
758
|
-
preflight.level === "warn"
|
|
759
|
-
preflight.level === "note" ||
|
|
760
|
-
preflight.level === "info"
|
|
761
|
-
) {
|
|
762
|
-
const icon =
|
|
763
|
-
preflight.level === "warn"
|
|
764
|
-
? "⚠️ "
|
|
765
|
-
: preflight.level === "note"
|
|
766
|
-
? "✅ "
|
|
767
|
-
: "ℹ️ ";
|
|
572
|
+
if (preflight.level === "warn" || preflight.level === "note" || preflight.level === "info") {
|
|
573
|
+
const icon = preflight.level === "warn" ? "⚠️ " : preflight.level === "note" ? "✅ " : "ℹ️ ";
|
|
768
574
|
console.warn(
|
|
769
575
|
`${icon}[team-runner.preflight] ${preflight.level.toUpperCase()}: ${preflight.message} (workflow=${workflow.name})`,
|
|
770
576
|
);
|
|
771
577
|
if (preflight.suggestion) {
|
|
772
|
-
console.warn(
|
|
773
|
-
`[team-runner.preflight] → ${preflight.suggestion}`,
|
|
774
|
-
);
|
|
578
|
+
console.warn(`[team-runner.preflight] → ${preflight.suggestion}`);
|
|
775
579
|
}
|
|
776
580
|
}
|
|
777
581
|
}
|
|
@@ -779,9 +583,7 @@ export async function executeTeamRun(
|
|
|
779
583
|
let manifest = updateRunStatus(
|
|
780
584
|
input.manifest,
|
|
781
585
|
"running",
|
|
782
|
-
input.executeWorkers
|
|
783
|
-
? "Executing team workflow."
|
|
784
|
-
: "Creating workflow prompts and placeholder results.",
|
|
586
|
+
input.executeWorkers ? "Executing team workflow." : "Creating workflow prompts and placeholder results.",
|
|
785
587
|
);
|
|
786
588
|
|
|
787
589
|
void registerRunPromise(manifest.runId);
|
|
@@ -791,10 +593,7 @@ export async function executeTeamRun(
|
|
|
791
593
|
// (NO_PID_HEARTBEAT_STALE_MS). Previously only sub-task runners wrote
|
|
792
594
|
// heartbeats; the team-level run had no heartbeat, so any multi-phase
|
|
793
595
|
// workflow lasting >5min was marked stale and cancelled.
|
|
794
|
-
const stopTeamHeartbeat = startTeamRunHeartbeat(
|
|
795
|
-
manifest.stateRoot,
|
|
796
|
-
manifest.runId,
|
|
797
|
-
);
|
|
596
|
+
const stopTeamHeartbeat = startTeamRunHeartbeat(manifest.stateRoot, manifest.runId);
|
|
798
597
|
|
|
799
598
|
const cleanupUsage = (): void => {
|
|
800
599
|
for (const task of input.tasks) clearTrackedTaskUsage(task.id);
|
|
@@ -807,11 +606,7 @@ export async function executeTeamRun(
|
|
|
807
606
|
// (and/or had a failed task) is a false-green. We expose goalAchieved on the
|
|
808
607
|
// manifest + emit an event so the lie is never silent, and downgrade status
|
|
809
608
|
// to "failed" only when a failed task corroborates it (conservative).
|
|
810
|
-
const gaAssessment = assessGoalAchievement(
|
|
811
|
-
result.manifest,
|
|
812
|
-
result.tasks,
|
|
813
|
-
workflow,
|
|
814
|
-
);
|
|
609
|
+
const gaAssessment = assessGoalAchievement(result.manifest, result.tasks, workflow);
|
|
815
610
|
const gaApplied = applyGoalAchievement(result.manifest, gaAssessment);
|
|
816
611
|
if (gaApplied.manifest !== result.manifest) {
|
|
817
612
|
result.manifest = gaApplied.manifest;
|
|
@@ -820,9 +615,7 @@ export async function executeTeamRun(
|
|
|
820
615
|
} catch (persistError) {
|
|
821
616
|
logInternalError(
|
|
822
617
|
"team-runner.goalAchievement.persist",
|
|
823
|
-
persistError instanceof Error
|
|
824
|
-
? persistError
|
|
825
|
-
: new Error(String(persistError)),
|
|
618
|
+
persistError instanceof Error ? persistError : new Error(String(persistError)),
|
|
826
619
|
`runId=${manifest.runId}`,
|
|
827
620
|
);
|
|
828
621
|
}
|
|
@@ -841,27 +634,15 @@ export async function executeTeamRun(
|
|
|
841
634
|
if (gaApplied.downgraded)
|
|
842
635
|
logInternalError(
|
|
843
636
|
"team-runner.goalAchievement.falseGreen",
|
|
844
|
-
new Error(
|
|
845
|
-
gaApplied.manifest.goalAchievementNote ??
|
|
846
|
-
"false-green detected",
|
|
847
|
-
),
|
|
637
|
+
new Error(gaApplied.manifest.goalAchievementNote ?? "false-green detected"),
|
|
848
638
|
`runId=${manifest.runId}`,
|
|
849
639
|
);
|
|
850
640
|
stopTeamHeartbeat();
|
|
851
641
|
resolveRunPromise(manifest.runId, result);
|
|
852
642
|
cleanupUsage();
|
|
853
643
|
// Terminate live agents for this run — agents are done when the run ends.
|
|
854
|
-
void terminateLiveAgentsForRun(
|
|
855
|
-
manifest.runId,
|
|
856
|
-
"completed",
|
|
857
|
-
appendEvent,
|
|
858
|
-
manifest.eventsPath,
|
|
859
|
-
).catch((error) =>
|
|
860
|
-
logInternalError(
|
|
861
|
-
"team-runner.completed.terminate",
|
|
862
|
-
error,
|
|
863
|
-
`runId=${manifest.runId}`,
|
|
864
|
-
),
|
|
644
|
+
void terminateLiveAgentsForRun(manifest.runId, "completed", appendEvent, manifest.eventsPath).catch((error) =>
|
|
645
|
+
logInternalError("team-runner.completed.terminate", error, `runId=${manifest.runId}`),
|
|
865
646
|
);
|
|
866
647
|
|
|
867
648
|
// Emit run completion hook (100% reliable, fire-and-forget)
|
|
@@ -885,13 +666,16 @@ export async function executeTeamRun(
|
|
|
885
666
|
if (afterRunReport.outcome === "block") {
|
|
886
667
|
logInternalError(
|
|
887
668
|
"team-runner.after_run_complete.blocked",
|
|
888
|
-
new Error(
|
|
889
|
-
afterRunReport.reason ?? "after_run_complete hook blocked",
|
|
890
|
-
),
|
|
669
|
+
new Error(afterRunReport.reason ?? "after_run_complete hook blocked"),
|
|
891
670
|
`runId=${manifest.runId}`,
|
|
892
671
|
);
|
|
893
672
|
}
|
|
894
673
|
|
|
674
|
+
// M7: flush buffered task.progress events so the final state is durable
|
|
675
|
+
// before the run returns. Buffered producer wins on latency (p95≈0µs);
|
|
676
|
+
// this single flush at run-end coalesces any pending progress bursts
|
|
677
|
+
// before manifest updates are observed by readers.
|
|
678
|
+
await flushEventLogBuffer();
|
|
895
679
|
return result;
|
|
896
680
|
} catch (error) {
|
|
897
681
|
// Round 27 (BUG 1): the success path calls stopTeamHeartbeat() but this
|
|
@@ -905,23 +689,25 @@ export async function executeTeamRun(
|
|
|
905
689
|
stopTeamHeartbeat();
|
|
906
690
|
// P1: Catch unhandled errors — ensure manifest/tasks/agents are terminal so they don't stay "running" forever.
|
|
907
691
|
const message = error instanceof Error ? error.message : String(error);
|
|
908
|
-
//
|
|
909
|
-
//
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
692
|
+
// FIX (2026-07-02): drop withRunLock here entirely.
|
|
693
|
+
// Previously this catch path acquired withRunLock to reload manifest+tasks
|
|
694
|
+
// from disk (best-effort with in-memory fallback). But the closeout path at
|
|
695
|
+
// line ~1596 ALSO holds the same run lock for its final save — when a late
|
|
696
|
+
// failure fires during closeout (e.g. async hook error after run.completed),
|
|
697
|
+
// this catch path can hit `Run 'run.lock' is locked by another operation.`
|
|
698
|
+
// and the error propagates as "Unhandled error in team runner" after the
|
|
699
|
+
// run has actually completed. Lock acquisition here is unnecessary —
|
|
700
|
+
// the closeout writes through to disk before this catch fires (or after —
|
|
701
|
+
// either is fine), and the in-memory state already contains the latest
|
|
702
|
+
// post-completion manifest/tasks. We use in-memory state unconditionally.
|
|
703
|
+
// The stale-data concern is mitigated by the dispatcher having re-read disk
|
|
704
|
+
// state under lock at line ~1364 for each iteration; the manifest+task
|
|
705
|
+
// objects in the calling scope are already post-merge.
|
|
706
|
+
const freshManifest = manifest;
|
|
707
|
+
const freshTasks = refreshTaskGraphQueues(input.tasks);
|
|
920
708
|
const failedAt = new Date().toISOString();
|
|
921
709
|
const tasks = freshTasks.map((task) =>
|
|
922
|
-
task.status === "running" ||
|
|
923
|
-
task.status === "queued" ||
|
|
924
|
-
task.status === "waiting"
|
|
710
|
+
task.status === "running" || task.status === "queued" || task.status === "waiting"
|
|
925
711
|
? {
|
|
926
712
|
...task,
|
|
927
713
|
status: "failed" as const,
|
|
@@ -932,53 +718,28 @@ export async function executeTeamRun(
|
|
|
932
718
|
);
|
|
933
719
|
manifest = freshManifest;
|
|
934
720
|
try {
|
|
935
|
-
await terminateLiveAgentsForRun(
|
|
936
|
-
manifest.runId,
|
|
937
|
-
"failed",
|
|
938
|
-
appendEvent,
|
|
939
|
-
manifest.eventsPath,
|
|
940
|
-
);
|
|
721
|
+
await terminateLiveAgentsForRun(manifest.runId, "failed", appendEvent, manifest.eventsPath);
|
|
941
722
|
await saveRunTasksAsync(manifest, tasks);
|
|
942
|
-
const existingRuntimeByTask = new Map(
|
|
943
|
-
readCrewAgents(manifest).map((agent) => [
|
|
944
|
-
agent.taskId,
|
|
945
|
-
agent.runtime,
|
|
946
|
-
]),
|
|
947
|
-
);
|
|
723
|
+
const existingRuntimeByTask = new Map(readCrewAgents(manifest).map((agent) => [agent.taskId, agent.runtime]));
|
|
948
724
|
const globalRuntime = input.runtime?.kind ?? "child-process";
|
|
949
|
-
const runtimeForAgent = (
|
|
950
|
-
agent: ReturnType<typeof recordsForMaterializedTasks>[number],
|
|
951
|
-
): CrewRuntimeKind => {
|
|
725
|
+
const runtimeForAgent = (agent: ReturnType<typeof recordsForMaterializedTasks>[number]): CrewRuntimeKind => {
|
|
952
726
|
const task = tasks.find((item) => item.id === agent.taskId);
|
|
953
727
|
return (
|
|
954
728
|
existingRuntimeByTask.get(agent.taskId) ??
|
|
955
|
-
resolveTaskRuntimeKind(
|
|
956
|
-
globalRuntime,
|
|
957
|
-
task?.role ?? agent.role,
|
|
958
|
-
input.runtimeConfig?.isolationPolicy,
|
|
959
|
-
)
|
|
729
|
+
resolveTaskRuntimeKind(globalRuntime, task?.role ?? agent.role, input.runtimeConfig?.isolationPolicy)
|
|
960
730
|
);
|
|
961
731
|
};
|
|
962
732
|
saveCrewAgents(
|
|
963
733
|
manifest,
|
|
964
|
-
recordsForMaterializedTasks(manifest, tasks, globalRuntime).map(
|
|
965
|
-
(agent) => ({ ...agent, runtime: runtimeForAgent(agent) }),
|
|
966
|
-
),
|
|
967
|
-
);
|
|
968
|
-
manifest = updateRunStatus(
|
|
969
|
-
manifest,
|
|
970
|
-
"failed",
|
|
971
|
-
`Unhandled error in team runner: ${message}`,
|
|
734
|
+
recordsForMaterializedTasks(manifest, tasks, globalRuntime).map((agent) => ({ ...agent, runtime: runtimeForAgent(agent) })),
|
|
972
735
|
);
|
|
736
|
+
manifest = updateRunStatus(manifest, "failed", `Unhandled error in team runner: ${message}`);
|
|
973
737
|
await saveRunManifestAsync(manifest);
|
|
974
738
|
} catch {
|
|
975
739
|
// Best-effort — state write may also fail
|
|
976
740
|
}
|
|
977
741
|
const result = { manifest, tasks };
|
|
978
|
-
rejectRunPromise(
|
|
979
|
-
manifest.runId,
|
|
980
|
-
error instanceof Error ? error : new Error(message),
|
|
981
|
-
);
|
|
742
|
+
rejectRunPromise(manifest.runId, error instanceof Error ? error : new Error(message));
|
|
982
743
|
crewHooks.emit({
|
|
983
744
|
type: "run_failed",
|
|
984
745
|
timestamp: new Date().toISOString(),
|
|
@@ -986,6 +747,9 @@ export async function executeTeamRun(
|
|
|
986
747
|
data: { status: manifest.status, error: message },
|
|
987
748
|
});
|
|
988
749
|
cleanupUsage();
|
|
750
|
+
// M7: flush buffered events before returning on the error path so the
|
|
751
|
+
// final buffered progress events are durable alongside the failure state.
|
|
752
|
+
await flushEventLogBuffer();
|
|
989
753
|
return result;
|
|
990
754
|
}
|
|
991
755
|
}
|
|
@@ -1002,11 +766,7 @@ async function executeTeamRunCore(
|
|
|
1002
766
|
});
|
|
1003
767
|
appendHookEvent(manifest, beforeRunReport);
|
|
1004
768
|
if (beforeRunReport.outcome === "block") {
|
|
1005
|
-
manifest = updateRunStatus(
|
|
1006
|
-
manifest,
|
|
1007
|
-
"blocked",
|
|
1008
|
-
beforeRunReport.reason ?? "before_run_start hook blocked the run.",
|
|
1009
|
-
);
|
|
769
|
+
manifest = updateRunStatus(manifest, "blocked", beforeRunReport.reason ?? "before_run_start hook blocked the run.");
|
|
1010
770
|
return { manifest, tasks: input.tasks };
|
|
1011
771
|
}
|
|
1012
772
|
let tasks = refreshTaskGraphQueues(input.tasks);
|
|
@@ -1015,12 +775,7 @@ async function executeTeamRunCore(
|
|
|
1015
775
|
let adaptivePlanInjected = false;
|
|
1016
776
|
let adaptivePlanMissing = false;
|
|
1017
777
|
const attemptAdaptivePlan = () => {
|
|
1018
|
-
if (
|
|
1019
|
-
!canInjectAdaptivePlan ||
|
|
1020
|
-
adaptivePlanInjected ||
|
|
1021
|
-
adaptivePlanMissing
|
|
1022
|
-
)
|
|
1023
|
-
return { injected: false, missing: false };
|
|
778
|
+
if (!canInjectAdaptivePlan || adaptivePlanInjected || adaptivePlanMissing) return { injected: false, missing: false };
|
|
1024
779
|
const adaptivePlan = injectAdaptivePlanIfReady({
|
|
1025
780
|
manifest,
|
|
1026
781
|
tasks,
|
|
@@ -1038,69 +793,38 @@ async function executeTeamRunCore(
|
|
|
1038
793
|
};
|
|
1039
794
|
const initialAdaptive = attemptAdaptivePlan();
|
|
1040
795
|
if (initialAdaptive.missing) {
|
|
1041
|
-
tasks = markBlocked(
|
|
1042
|
-
tasks,
|
|
1043
|
-
"Adaptive planner did not produce a valid subagent plan.",
|
|
1044
|
-
);
|
|
796
|
+
tasks = markBlocked(tasks, "Adaptive planner did not produce a valid subagent plan.");
|
|
1045
797
|
await saveRunTasksAsync(manifest, tasks);
|
|
1046
|
-
manifest = updateRunStatus(
|
|
1047
|
-
manifest,
|
|
1048
|
-
"blocked",
|
|
1049
|
-
"Adaptive planner did not produce a valid subagent plan.",
|
|
1050
|
-
);
|
|
798
|
+
manifest = updateRunStatus(manifest, "blocked", "Adaptive planner did not produce a valid subagent plan.");
|
|
1051
799
|
return { manifest, tasks };
|
|
1052
800
|
}
|
|
1053
801
|
if (initialAdaptive.injected) {
|
|
1054
|
-
manifest = requiresPlanApproval(workflow, input.runtimeConfig)
|
|
1055
|
-
? ensurePlanApprovalRequested(manifest, tasks)
|
|
1056
|
-
: manifest;
|
|
802
|
+
manifest = requiresPlanApproval(workflow, input.runtimeConfig) ? ensurePlanApprovalRequested(manifest, tasks) : manifest;
|
|
1057
803
|
queueIndex = buildTaskGraphIndex(tasks);
|
|
1058
804
|
} else if (
|
|
1059
805
|
requiresPlanApproval(workflow, input.runtimeConfig) &&
|
|
1060
|
-
(hasPendingMutatingAdaptiveTask(tasks) ||
|
|
1061
|
-
hasPendingMutatingTaskAtBoundary(tasks))
|
|
806
|
+
(hasPendingMutatingAdaptiveTask(tasks) || hasPendingMutatingTaskAtBoundary(tasks))
|
|
1062
807
|
) {
|
|
1063
808
|
manifest = ensurePlanApprovalRequested(manifest, tasks);
|
|
1064
809
|
}
|
|
1065
810
|
if (manifest.planApproval?.status === "cancelled") {
|
|
1066
811
|
tasks = cancelPlanTasks(tasks, "Plan approval was cancelled.");
|
|
1067
812
|
await saveRunTasksAsync(manifest, tasks);
|
|
1068
|
-
manifest = updateRunStatus(
|
|
1069
|
-
manifest,
|
|
1070
|
-
"cancelled",
|
|
1071
|
-
"Plan approval was cancelled.",
|
|
1072
|
-
);
|
|
813
|
+
manifest = updateRunStatus(manifest, "cancelled", "Plan approval was cancelled.");
|
|
1073
814
|
return { manifest, tasks };
|
|
1074
815
|
}
|
|
1075
|
-
manifest = writeProgress(
|
|
1076
|
-
manifest,
|
|
1077
|
-
tasks,
|
|
1078
|
-
"team-runner",
|
|
1079
|
-
input.executeWorkers,
|
|
1080
|
-
input.runtimeConfig,
|
|
1081
|
-
);
|
|
816
|
+
manifest = writeProgress(manifest, tasks, "team-runner", input.executeWorkers, input.runtimeConfig);
|
|
1082
817
|
await saveRunManifestAsync(manifest);
|
|
1083
|
-
const runtimeKind =
|
|
1084
|
-
|
|
1085
|
-
(input.executeWorkers ? "child-process" : "scaffold");
|
|
1086
|
-
saveCrewAgents(
|
|
1087
|
-
manifest,
|
|
1088
|
-
recordsForMaterializedTasks(manifest, tasks, runtimeKind),
|
|
1089
|
-
);
|
|
818
|
+
const runtimeKind = input.runtime?.kind ?? (input.executeWorkers ? "child-process" : "scaffold");
|
|
819
|
+
saveCrewAgents(manifest, recordsForMaterializedTasks(manifest, tasks, runtimeKind));
|
|
1090
820
|
|
|
1091
821
|
// Build a workflow phase state machine from workflow steps for precondition tracking.
|
|
1092
822
|
const workflowPhases: PhaseState[] = workflow.steps.map(
|
|
1093
823
|
(step): PhaseState => ({
|
|
1094
824
|
name: step.id,
|
|
1095
825
|
status: "pending",
|
|
1096
|
-
inputs:
|
|
1097
|
-
|
|
1098
|
-
? []
|
|
1099
|
-
: Array.isArray(step.reads)
|
|
1100
|
-
? step.reads
|
|
1101
|
-
: [],
|
|
1102
|
-
outputs:
|
|
1103
|
-
step.output === false ? [] : step.output ? [step.output] : [],
|
|
826
|
+
inputs: step.reads === false ? [] : Array.isArray(step.reads) ? step.reads : [],
|
|
827
|
+
outputs: step.output === false ? [] : step.output ? [step.output] : [],
|
|
1104
828
|
}),
|
|
1105
829
|
);
|
|
1106
830
|
let wfMachine = createWorkflowStateMachine(workflowPhases);
|
|
@@ -1111,12 +835,7 @@ async function executeTeamRunCore(
|
|
|
1111
835
|
const message = `${cancelReason.message} (${cancelReason.code})`;
|
|
1112
836
|
const cancelledTaskIds: string[] = [];
|
|
1113
837
|
tasks = tasks.map((task) => {
|
|
1114
|
-
if (
|
|
1115
|
-
task.status !== "queued" &&
|
|
1116
|
-
task.status !== "running" &&
|
|
1117
|
-
task.status !== "waiting"
|
|
1118
|
-
)
|
|
1119
|
-
return task;
|
|
838
|
+
if (task.status !== "queued" && task.status !== "running" && task.status !== "waiting") return task;
|
|
1120
839
|
cancelledTaskIds.push(task.id);
|
|
1121
840
|
const base = {
|
|
1122
841
|
...task,
|
|
@@ -1129,11 +848,7 @@ async function executeTeamRunCore(
|
|
|
1129
848
|
...base,
|
|
1130
849
|
terminalEvidence: [
|
|
1131
850
|
...(task.terminalEvidence ?? []),
|
|
1132
|
-
buildSyntheticTerminalEvidence(
|
|
1133
|
-
"worker",
|
|
1134
|
-
cancelReason,
|
|
1135
|
-
task.startedAt,
|
|
1136
|
-
),
|
|
851
|
+
buildSyntheticTerminalEvidence("worker", cancelReason, task.startedAt),
|
|
1137
852
|
],
|
|
1138
853
|
};
|
|
1139
854
|
}
|
|
@@ -1191,20 +906,10 @@ async function executeTeamRunCore(
|
|
|
1191
906
|
});
|
|
1192
907
|
continue; // loop re-processes the re-queued task
|
|
1193
908
|
}
|
|
1194
|
-
tasks = markBlocked(
|
|
1195
|
-
tasks,
|
|
1196
|
-
`Blocked by failed task '${failed.id}'.`,
|
|
1197
|
-
);
|
|
909
|
+
tasks = markBlocked(tasks, `Blocked by failed task '${failed.id}'.`);
|
|
1198
910
|
await saveRunTasksAsync(manifest, tasks);
|
|
1199
|
-
saveCrewAgents(
|
|
1200
|
-
|
|
1201
|
-
recordsForMaterializedTasks(manifest, tasks, runtimeKind),
|
|
1202
|
-
);
|
|
1203
|
-
manifest = updateRunStatus(
|
|
1204
|
-
manifest,
|
|
1205
|
-
"failed",
|
|
1206
|
-
`Failed at task '${failed.id}'.`,
|
|
1207
|
-
);
|
|
911
|
+
saveCrewAgents(manifest, recordsForMaterializedTasks(manifest, tasks, runtimeKind));
|
|
912
|
+
manifest = updateRunStatus(manifest, "failed", `Failed at task '${failed.id}'.`);
|
|
1208
913
|
return { manifest, tasks };
|
|
1209
914
|
}
|
|
1210
915
|
|
|
@@ -1213,47 +918,27 @@ async function executeTeamRunCore(
|
|
|
1213
918
|
// DAG-based execution plan: when tasks have explicit dependsOn, use the
|
|
1214
919
|
// topological wave planner to determine ready tasks. Fall back to the
|
|
1215
920
|
// existing task-graph-scheduler when no explicit deps exist (backward compat).
|
|
1216
|
-
const completedIds = new Set(
|
|
1217
|
-
tasks
|
|
1218
|
-
.filter(
|
|
1219
|
-
(t) =>
|
|
1220
|
-
t.status === "completed" ||
|
|
1221
|
-
t.status === "needs_attention",
|
|
1222
|
-
)
|
|
1223
|
-
.map((t) => t.id),
|
|
1224
|
-
);
|
|
921
|
+
const completedIds = new Set(tasks.filter((t) => t.status === "completed" || t.status === "needs_attention").map((t) => t.id));
|
|
1225
922
|
const dagReady = dagReadyTaskIds(tasks, completedIds);
|
|
1226
|
-
const
|
|
923
|
+
const readyBeforeFilter = dagReady ?? snapshot.ready;
|
|
1227
924
|
|
|
1228
925
|
// Workflow phase precondition check (non-blocking: log warnings only).
|
|
1229
926
|
if (wfMachine.currentPhaseIndex < wfMachine.phases.length) {
|
|
1230
|
-
const completedArtifacts = manifest.artifacts
|
|
1231
|
-
.filter((a) => a.kind === "result" || a.kind === "summary")
|
|
1232
|
-
.map((a) => a.path);
|
|
927
|
+
const completedArtifacts = manifest.artifacts.filter((a) => a.kind === "result" || a.kind === "summary").map((a) => a.path);
|
|
1233
928
|
const previousPhaseStatus =
|
|
1234
|
-
wfMachine.currentPhaseIndex > 0
|
|
1235
|
-
? (wfMachine.phases[wfMachine.currentPhaseIndex - 1]
|
|
1236
|
-
?.status ?? "pending")
|
|
1237
|
-
: "completed";
|
|
929
|
+
wfMachine.currentPhaseIndex > 0 ? (wfMachine.phases[wfMachine.currentPhaseIndex - 1]?.status ?? "pending") : "completed";
|
|
1238
930
|
const wfContext: PhaseGuardContext = {
|
|
1239
931
|
completedArtifacts,
|
|
1240
932
|
previousPhaseStatus,
|
|
1241
933
|
taskResults: tasks
|
|
1242
|
-
.filter(
|
|
1243
|
-
(t) =>
|
|
1244
|
-
t.status === "completed" ||
|
|
1245
|
-
t.status === "needs_attention",
|
|
1246
|
-
)
|
|
934
|
+
.filter((t) => t.status === "completed" || t.status === "needs_attention")
|
|
1247
935
|
.map((t) => ({
|
|
1248
936
|
taskId: t.id,
|
|
1249
937
|
status: t.status,
|
|
1250
938
|
outputPath: t.resultArtifact?.path,
|
|
1251
939
|
})),
|
|
1252
940
|
};
|
|
1253
|
-
const preconditions = validatePhasePreconditions(
|
|
1254
|
-
wfMachine,
|
|
1255
|
-
wfContext,
|
|
1256
|
-
);
|
|
941
|
+
const preconditions = validatePhasePreconditions(wfMachine, wfContext);
|
|
1257
942
|
if (!preconditions.ready) {
|
|
1258
943
|
await appendEventAsync(manifest.eventsPath, {
|
|
1259
944
|
type: "workflow.preconditions",
|
|
@@ -1261,8 +946,7 @@ async function executeTeamRunCore(
|
|
|
1261
946
|
message: `Workflow phase '${wfMachine.phases[wfMachine.currentPhaseIndex]?.name}' is missing inputs: ${preconditions.blocking.join(", ")}`,
|
|
1262
947
|
data: {
|
|
1263
948
|
phaseIndex: wfMachine.currentPhaseIndex,
|
|
1264
|
-
phaseName:
|
|
1265
|
-
wfMachine.phases[wfMachine.currentPhaseIndex]?.name,
|
|
949
|
+
phaseName: wfMachine.phases[wfMachine.currentPhaseIndex]?.name,
|
|
1266
950
|
blocking: preconditions.blocking,
|
|
1267
951
|
},
|
|
1268
952
|
});
|
|
@@ -1270,8 +954,7 @@ async function executeTeamRunCore(
|
|
|
1270
954
|
// Advance the machine past completed phases.
|
|
1271
955
|
while (
|
|
1272
956
|
wfMachine.currentPhaseIndex < wfMachine.phases.length &&
|
|
1273
|
-
wfMachine.phases[wfMachine.currentPhaseIndex]?.status ===
|
|
1274
|
-
"completed"
|
|
957
|
+
wfMachine.phases[wfMachine.currentPhaseIndex]?.status === "completed"
|
|
1275
958
|
) {
|
|
1276
959
|
wfMachine = {
|
|
1277
960
|
...wfMachine,
|
|
@@ -1281,7 +964,7 @@ async function executeTeamRunCore(
|
|
|
1281
964
|
}
|
|
1282
965
|
}
|
|
1283
966
|
|
|
1284
|
-
const readyRoles =
|
|
967
|
+
const readyRoles = readyBeforeFilter
|
|
1285
968
|
.map((taskId) => tasks.find((task) => task.id === taskId)?.role)
|
|
1286
969
|
.filter((role): role is string => Boolean(role));
|
|
1287
970
|
const concurrency = resolveBatchConcurrency({
|
|
@@ -1290,16 +973,55 @@ async function executeTeamRunCore(
|
|
|
1290
973
|
teamMaxConcurrency: input.team.maxConcurrency,
|
|
1291
974
|
limitMaxConcurrentWorkers: input.limits?.maxConcurrentWorkers,
|
|
1292
975
|
allowUnboundedConcurrency: input.limits?.allowUnboundedConcurrency,
|
|
1293
|
-
readyCount:
|
|
976
|
+
readyCount: readyBeforeFilter.length,
|
|
1294
977
|
workspaceMode: manifest.workspaceMode,
|
|
1295
978
|
readyRoles,
|
|
1296
979
|
});
|
|
980
|
+
|
|
981
|
+
// Round 25 (M5): serialize on write-path overlap when opted in.
|
|
982
|
+
// Opt-in via limits.serializeOnPathOverlap; default off (= no behavior change).
|
|
983
|
+
// filterReadyByWriteOverlap returns the same array when enabled=false, so
|
|
984
|
+
// production runs pay nothing for the unused code path. When the flag is on,
|
|
985
|
+
// `serializedReady` MAY be a strict subset of `readyBeforeFilter` (conflicting tasks
|
|
986
|
+
// deferred to next cycle).
|
|
987
|
+
const serializedReady = filterReadyByWriteOverlap(
|
|
988
|
+
readyBeforeFilter,
|
|
989
|
+
tasks,
|
|
990
|
+
workflow,
|
|
991
|
+
concurrency.maxConcurrent,
|
|
992
|
+
input.limits?.serializeOnPathOverlap === true,
|
|
993
|
+
);
|
|
994
|
+
|
|
995
|
+
// Round 25 (M6): coalesce micro-tasks when opted in.
|
|
996
|
+
// Default off; when on, groups same-(role,cwd) tasks into coalesced groups
|
|
997
|
+
// (with write-path safety). In v0.9.17 first ship, we ONLY log the
|
|
998
|
+
// coalesced group count to the event stream (informational). Actual
|
|
999
|
+
// dispatching of one-multi-task worker instead of N workers is deferred
|
|
1000
|
+
// to a follow-up — it's a non-trivial prompt-construction change that
|
|
1001
|
+
// deserves its own PR. For now, every coalesced group => one info event.
|
|
1002
|
+
const coalesceEnabled = workflow.coalesceMicroTasks === true;
|
|
1003
|
+
if (coalesceEnabled) {
|
|
1004
|
+
const coalescedGroups = planCoalescedGroups(serializedReady, tasks, workflow, true);
|
|
1005
|
+
for (const group of coalescedGroups) {
|
|
1006
|
+
if (group.tasks.length < 2) continue; // singletons are not interesting
|
|
1007
|
+
await appendEventAsync(manifest.eventsPath, {
|
|
1008
|
+
type: "task.coalesced",
|
|
1009
|
+
runId: manifest.runId,
|
|
1010
|
+
message: `Coalesced ${group.tasks.length} micro-tasks (role=${group.role}, cwd=${group.cwd})`,
|
|
1011
|
+
data: {
|
|
1012
|
+
groupId: group.id,
|
|
1013
|
+
role: group.role,
|
|
1014
|
+
cwd: group.cwd,
|
|
1015
|
+
taskIds: group.tasks.map((task) => task.id),
|
|
1016
|
+
},
|
|
1017
|
+
});
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1297
1020
|
if (concurrency.reason.includes(";unbounded:")) {
|
|
1298
1021
|
await appendEventAsync(manifest.eventsPath, {
|
|
1299
1022
|
type: "limits.unbounded",
|
|
1300
1023
|
runId: manifest.runId,
|
|
1301
|
-
message:
|
|
1302
|
-
"Unbounded worker concurrency was explicitly enabled for this run.",
|
|
1024
|
+
message: "Unbounded worker concurrency was explicitly enabled for this run.",
|
|
1303
1025
|
data: {
|
|
1304
1026
|
concurrencyReason: concurrency.reason,
|
|
1305
1027
|
maxConcurrent: concurrency.maxConcurrent,
|
|
@@ -1307,50 +1029,29 @@ async function executeTeamRunCore(
|
|
|
1307
1029
|
});
|
|
1308
1030
|
}
|
|
1309
1031
|
const approvalPending = isPlanApprovalPending(manifest);
|
|
1310
|
-
const readyIds = approvalPending
|
|
1311
|
-
? effectiveReady
|
|
1312
|
-
: effectiveReady.slice(0, concurrency.selectedCount);
|
|
1032
|
+
const readyIds = approvalPending ? serializedReady : serializedReady.slice(0, concurrency.selectedCount);
|
|
1313
1033
|
const candidateBatch = readyIds
|
|
1314
1034
|
.map((id) => tasks.find((task) => task.id === id))
|
|
1315
1035
|
.filter((task): task is TeamTaskState => Boolean(task));
|
|
1316
1036
|
const readyBatch = approvalPending
|
|
1317
|
-
? candidateBatch
|
|
1318
|
-
.filter((task) => !isMutatingTask(task))
|
|
1319
|
-
.slice(0, concurrency.selectedCount)
|
|
1037
|
+
? candidateBatch.filter((task) => !isMutatingTask(task)).slice(0, concurrency.selectedCount)
|
|
1320
1038
|
: candidateBatch;
|
|
1321
1039
|
if (readyBatch.length === 0) {
|
|
1322
1040
|
if (approvalPending && candidateBatch.some(isMutatingTask)) {
|
|
1323
1041
|
await saveRunTasksAsync(manifest, tasks);
|
|
1324
|
-
saveCrewAgents(
|
|
1325
|
-
|
|
1326
|
-
recordsForMaterializedTasks(manifest, tasks, runtimeKind),
|
|
1327
|
-
);
|
|
1328
|
-
manifest = updateRunStatus(
|
|
1329
|
-
manifest,
|
|
1330
|
-
"blocked",
|
|
1331
|
-
"Plan approval required before mutating implementation tasks run.",
|
|
1332
|
-
);
|
|
1042
|
+
saveCrewAgents(manifest, recordsForMaterializedTasks(manifest, tasks, runtimeKind));
|
|
1043
|
+
manifest = updateRunStatus(manifest, "blocked", "Plan approval required before mutating implementation tasks run.");
|
|
1333
1044
|
return { manifest, tasks };
|
|
1334
1045
|
}
|
|
1335
|
-
tasks = markBlocked(
|
|
1336
|
-
tasks,
|
|
1337
|
-
"No ready queued task; dependency graph may be invalid.",
|
|
1338
|
-
);
|
|
1046
|
+
tasks = markBlocked(tasks, "No ready queued task; dependency graph may be invalid.");
|
|
1339
1047
|
await saveRunTasksAsync(manifest, tasks);
|
|
1340
|
-
saveCrewAgents(
|
|
1341
|
-
|
|
1342
|
-
recordsForMaterializedTasks(manifest, tasks, runtimeKind),
|
|
1343
|
-
);
|
|
1344
|
-
manifest = updateRunStatus(
|
|
1345
|
-
manifest,
|
|
1346
|
-
"blocked",
|
|
1347
|
-
"No ready queued task.",
|
|
1348
|
-
);
|
|
1048
|
+
saveCrewAgents(manifest, recordsForMaterializedTasks(manifest, tasks, runtimeKind));
|
|
1049
|
+
manifest = updateRunStatus(manifest, "blocked", "No ready queued task.");
|
|
1349
1050
|
return { manifest, tasks };
|
|
1350
1051
|
}
|
|
1351
1052
|
|
|
1352
|
-
// 2.2 caller migration: batch progress is high-frequency informational.
|
|
1353
|
-
|
|
1053
|
+
// 2.2 caller migration: batch progress is high-frequency informational (M7 wire).
|
|
1054
|
+
void appendEventBuffered(manifest.eventsPath, {
|
|
1354
1055
|
type: "task.progress",
|
|
1355
1056
|
runId: manifest.runId,
|
|
1356
1057
|
message: `Starting ready batch with ${readyBatch.length} task(s).`,
|
|
@@ -1363,9 +1064,7 @@ async function executeTeamRunCore(
|
|
|
1363
1064
|
selectedCount: readyBatch.length,
|
|
1364
1065
|
maxConcurrent: concurrency.maxConcurrent,
|
|
1365
1066
|
defaultConcurrency: concurrency.defaultConcurrency,
|
|
1366
|
-
concurrencyReason: approvalPending
|
|
1367
|
-
? `${concurrency.reason};plan-approval-read-only`
|
|
1368
|
-
: concurrency.reason,
|
|
1067
|
+
concurrencyReason: approvalPending ? `${concurrency.reason};plan-approval-read-only` : concurrency.reason,
|
|
1369
1068
|
},
|
|
1370
1069
|
});
|
|
1371
1070
|
// Execute before_task_start hooks for the batch
|
|
@@ -1382,22 +1081,14 @@ async function executeTeamRunCore(
|
|
|
1382
1081
|
? {
|
|
1383
1082
|
...t,
|
|
1384
1083
|
status: "skipped" as const,
|
|
1385
|
-
error:
|
|
1386
|
-
taskReport.reason ??
|
|
1387
|
-
"before_task_start hook blocked execution.",
|
|
1084
|
+
error: taskReport.reason ?? "before_task_start hook blocked execution.",
|
|
1388
1085
|
}
|
|
1389
1086
|
: t,
|
|
1390
1087
|
);
|
|
1391
|
-
manifest = updateRunStatus(
|
|
1392
|
-
manifest,
|
|
1393
|
-
manifest.status,
|
|
1394
|
-
`Task '${task.id}' blocked by hook.`,
|
|
1395
|
-
);
|
|
1088
|
+
manifest = updateRunStatus(manifest, manifest.status, `Task '${task.id}' blocked by hook.`);
|
|
1396
1089
|
}
|
|
1397
1090
|
}
|
|
1398
|
-
const batchTasks = readyBatch.filter((task) =>
|
|
1399
|
-
tasks.find((t) => t.id === task.id && t.status !== "skipped"),
|
|
1400
|
-
);
|
|
1091
|
+
const batchTasks = readyBatch.filter((task) => tasks.find((t) => t.id === task.id && t.status !== "skipped"));
|
|
1401
1092
|
if (batchTasks.length > 1) {
|
|
1402
1093
|
await appendEventAsync(manifest.eventsPath, {
|
|
1403
1094
|
type: "task.parallel_start",
|
|
@@ -1410,327 +1101,253 @@ async function executeTeamRunCore(
|
|
|
1410
1101
|
},
|
|
1411
1102
|
});
|
|
1412
1103
|
}
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1104
|
+
|
|
1105
|
+
// M6 real dispatch: when coalesceMicroTasks is enabled, batch the
|
|
1106
|
+
// ready tasks into dispatch units. Multi-task groups are dispatched
|
|
1107
|
+
// as one worker (single cold-start) instead of N. Singletons fall
|
|
1108
|
+
// through to per-task dispatch.
|
|
1109
|
+
const coalescedGroups = planCoalescedGroups(
|
|
1110
|
+
batchTasks.map((t) => t.id),
|
|
1111
|
+
tasks,
|
|
1112
|
+
workflow,
|
|
1113
|
+
coalesceEnabled,
|
|
1114
|
+
);
|
|
1115
|
+
const dispatchUnits = buildDispatchUnits(
|
|
1116
|
+
batchTasks.map((t) => t.id),
|
|
1117
|
+
coalescedGroups,
|
|
1118
|
+
);
|
|
1119
|
+
|
|
1120
|
+
const results = await mapConcurrent(dispatchUnits, concurrency.selectedCount, async (unit) => {
|
|
1121
|
+
// M6 real dispatch path: single worker for N tasks.
|
|
1122
|
+
if (unit.kind === "group") {
|
|
1123
|
+
const groupTasks = unit.group.tasks;
|
|
1124
|
+
const firstTask = groupTasks[0]!;
|
|
1125
|
+
const step = findStep(workflow, firstTask);
|
|
1126
|
+
const agent = findAgent(input.agents, firstTask);
|
|
1127
|
+
const teamRole = input.team.roles.find((role) => role.name === firstTask.role);
|
|
1128
|
+
const perTaskRuntime = resolveTaskRuntimeKind(runtimeKind, firstTask.role, input.runtimeConfig?.isolationPolicy);
|
|
1129
|
+
return runCoalescedTaskGroup({
|
|
1428
1130
|
manifest,
|
|
1429
1131
|
tasks,
|
|
1430
|
-
|
|
1132
|
+
groupTasks,
|
|
1431
1133
|
step,
|
|
1432
1134
|
agent,
|
|
1433
1135
|
signal: input.signal,
|
|
1434
1136
|
executeWorkers: input.executeWorkers,
|
|
1435
|
-
runtimeKind
|
|
1436
|
-
taskRuntimeOverride:
|
|
1437
|
-
perTaskRuntime !== runtimeKind
|
|
1438
|
-
? perTaskRuntime
|
|
1439
|
-
: undefined,
|
|
1440
|
-
runtimeConfig: input.runtimeConfig,
|
|
1441
|
-
parentContext: input.parentContext,
|
|
1442
|
-
parentModel: input.parentModel,
|
|
1443
|
-
modelRegistry: input.modelRegistry,
|
|
1444
|
-
modelOverride: input.modelOverride,
|
|
1445
|
-
teamRoleModel: teamRole?.model,
|
|
1446
|
-
teamRoleSkills: teamRole?.skills,
|
|
1447
|
-
skillOverride: input.skillOverride,
|
|
1448
|
-
limits: input.limits,
|
|
1449
|
-
onJsonEvent: input.onJsonEvent,
|
|
1137
|
+
runtimeKind,
|
|
1450
1138
|
workspaceId: input.workspaceId,
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1139
|
+
onJsonEvent: input.onJsonEvent,
|
|
1140
|
+
teamRole,
|
|
1141
|
+
perTaskRuntime,
|
|
1142
|
+
});
|
|
1143
|
+
}
|
|
1144
|
+
// Singleton path: original per-task dispatch.
|
|
1145
|
+
const task = batchTasks.find((t) => t.id === unit.taskId)!;
|
|
1146
|
+
const step = findStep(workflow, task);
|
|
1147
|
+
const agent = findAgent(input.agents, task);
|
|
1148
|
+
const teamRole = input.team.roles.find((role) => role.name === task.role);
|
|
1149
|
+
const perTaskRuntime = resolveTaskRuntimeKind(runtimeKind, task.role, input.runtimeConfig?.isolationPolicy);
|
|
1150
|
+
const baseInput = {
|
|
1151
|
+
manifest,
|
|
1152
|
+
tasks,
|
|
1153
|
+
task,
|
|
1154
|
+
step,
|
|
1155
|
+
agent,
|
|
1156
|
+
signal: input.signal,
|
|
1157
|
+
executeWorkers: input.executeWorkers,
|
|
1158
|
+
runtimeKind: runtimeKind,
|
|
1159
|
+
taskRuntimeOverride: perTaskRuntime !== runtimeKind ? perTaskRuntime : undefined,
|
|
1160
|
+
runtimeConfig: input.runtimeConfig,
|
|
1161
|
+
parentContext: input.parentContext,
|
|
1162
|
+
parentModel: input.parentModel,
|
|
1163
|
+
modelRegistry: input.modelRegistry,
|
|
1164
|
+
modelOverride: input.modelOverride,
|
|
1165
|
+
teamRoleModel: teamRole?.model,
|
|
1166
|
+
teamRoleSkills: teamRole?.skills,
|
|
1167
|
+
skillOverride: input.skillOverride,
|
|
1168
|
+
limits: input.limits,
|
|
1169
|
+
onJsonEvent: input.onJsonEvent,
|
|
1170
|
+
workspaceId: input.workspaceId,
|
|
1171
|
+
};
|
|
1172
|
+
// #1 (assessment): autoRetry now defaults ON (opt-out via reliability.autoRetry=false).
|
|
1173
|
+
// The dominant v0.9.13 failure was ChildTimeout ("worker became unresponsive") with
|
|
1174
|
+
// ZERO retries because this gate was opt-in. isRetryable() defaults to true when
|
|
1175
|
+
// retryableErrors is empty, so transient hangs now retry up to maxAttempts (3) with
|
|
1176
|
+
// exponential backoff. Set reliability.autoRetry=false to restore old single-shot behavior.
|
|
1177
|
+
if (!shouldUseRetry(input.reliability))
|
|
1178
|
+
return withCorrelation(childCorrelation(manifest.runId, task.id), () => runTeamTask(baseInput));
|
|
1179
|
+
let lastFailed: { manifest: TeamRunManifest; tasks: TeamTaskState[] } | undefined;
|
|
1180
|
+
let lastAttemptId: string | undefined;
|
|
1181
|
+
const attemptsSoFar: TaskAttemptState[] = [...(task.attempts ?? [])];
|
|
1182
|
+
const policy = retryPolicyFromConfig(input.reliability);
|
|
1183
|
+
try {
|
|
1184
|
+
return await executeWithRetry(
|
|
1185
|
+
async (attempt, info) => {
|
|
1186
|
+
const startedAt = new Date().toISOString();
|
|
1187
|
+
const inFlightAttempts: TaskAttemptState[] = [...attemptsSoFar, { attemptId: info.attemptId, startedAt }];
|
|
1188
|
+
input.metricRegistry?.counter("crew.task.retry_attempt_total", "Retry attempts by run and task").inc({
|
|
1189
|
+
runId: manifest.runId,
|
|
1190
|
+
taskId: task.id,
|
|
1191
|
+
});
|
|
1192
|
+
// NOTE: no withRunLock — best-effort only; concurrent writes may cause inconsistency
|
|
1193
|
+
const fresh = loadRunManifestById(manifest.cwd, manifest.runId);
|
|
1194
|
+
const freshManifest = fresh?.manifest ?? manifest;
|
|
1195
|
+
const freshTasks = fresh?.tasks ?? tasks;
|
|
1196
|
+
const freshTask = freshTasks.find((item) => item.id === task.id) ?? task;
|
|
1197
|
+
if (freshTask.status !== "queued" && freshTask.status !== "running")
|
|
1198
|
+
return {
|
|
1199
|
+
manifest: freshManifest,
|
|
1200
|
+
tasks: freshTasks,
|
|
1201
|
+
};
|
|
1202
|
+
const taskWithAttempt: TeamTaskState = {
|
|
1203
|
+
...freshTask,
|
|
1204
|
+
attempts: inFlightAttempts,
|
|
1205
|
+
};
|
|
1206
|
+
const result = await withCorrelation(childCorrelation(freshManifest.runId, task.id), () =>
|
|
1207
|
+
runTeamTask({
|
|
1208
|
+
...baseInput,
|
|
1209
|
+
manifest: freshManifest,
|
|
1210
|
+
tasks: freshTasks,
|
|
1211
|
+
task: taskWithAttempt,
|
|
1212
|
+
}),
|
|
1213
|
+
);
|
|
1214
|
+
const failed = failedTaskFrom(result, task.id);
|
|
1215
|
+
const endedAt = new Date().toISOString();
|
|
1216
|
+
const finishedAttempt: TaskAttemptState = {
|
|
1217
|
+
attemptId: info.attemptId,
|
|
1218
|
+
startedAt,
|
|
1219
|
+
endedAt,
|
|
1220
|
+
...(failed?.error ? { error: failed.error } : {}),
|
|
1221
|
+
};
|
|
1222
|
+
attemptsSoFar.push(finishedAttempt);
|
|
1223
|
+
const withAttempt = result.tasks.map((item) =>
|
|
1224
|
+
item.id === task.id ? { ...item, attempts: [...attemptsSoFar] } : item,
|
|
1225
|
+
);
|
|
1226
|
+
const enriched = {
|
|
1227
|
+
manifest: result.manifest,
|
|
1228
|
+
tasks: withAttempt,
|
|
1229
|
+
};
|
|
1230
|
+
if (failed) {
|
|
1231
|
+
lastFailed = enriched;
|
|
1232
|
+
throw new Error(failed.error ?? `Task ${task.id} failed.`);
|
|
1233
|
+
}
|
|
1234
|
+
input.metricRegistry?.histogram("crew.task.retry_count", "Retries per task", [0, 1, 2, 3, 5, 10]).observe(
|
|
1235
|
+
{
|
|
1236
|
+
runId: manifest.runId,
|
|
1237
|
+
team: input.team.name,
|
|
1238
|
+
},
|
|
1239
|
+
Math.max(0, attempt - 1),
|
|
1240
|
+
);
|
|
1241
|
+
return enriched;
|
|
1242
|
+
},
|
|
1243
|
+
policy,
|
|
1244
|
+
{
|
|
1245
|
+
signal: input.signal,
|
|
1246
|
+
attemptId: (attempt) => `${manifest.runId}:${task.id}:attempt-${attempt}`,
|
|
1247
|
+
onAttemptFailed: (attempt, error, delayMs, info) => {
|
|
1248
|
+
lastAttemptId = info.attemptId;
|
|
1249
|
+
appendEventAsync(manifest.eventsPath, {
|
|
1250
|
+
type: "crew.task.retry_attempt",
|
|
1251
|
+
runId: manifest.runId,
|
|
1252
|
+
taskId: task.id,
|
|
1253
|
+
message: error.message,
|
|
1254
|
+
data: {
|
|
1255
|
+
attempt,
|
|
1256
|
+
attemptId: info.attemptId,
|
|
1257
|
+
delayMs,
|
|
1258
|
+
},
|
|
1259
|
+
metadata: { attemptId: info.attemptId },
|
|
1260
|
+
}).catch((error) => logInternalError("team-runner.retry-attempt", error, `taskId=${task.id}`));
|
|
1261
|
+
input.metricRegistry?.histogram("crew.task.retry_delay_ms", "Retry backoff delay, milliseconds").observe(
|
|
1262
|
+
{
|
|
1484
1263
|
runId: manifest.runId,
|
|
1485
1264
|
taskId: task.id,
|
|
1486
|
-
}
|
|
1487
|
-
|
|
1488
|
-
const fresh = loadRunManifestById(
|
|
1489
|
-
manifest.cwd,
|
|
1490
|
-
manifest.runId,
|
|
1491
|
-
);
|
|
1492
|
-
const freshManifest = fresh?.manifest ?? manifest;
|
|
1493
|
-
const freshTasks = fresh?.tasks ?? tasks;
|
|
1494
|
-
const freshTask =
|
|
1495
|
-
freshTasks.find(
|
|
1496
|
-
(item) => item.id === task.id,
|
|
1497
|
-
) ?? task;
|
|
1498
|
-
if (
|
|
1499
|
-
freshTask.status !== "queued" &&
|
|
1500
|
-
freshTask.status !== "running"
|
|
1501
|
-
)
|
|
1502
|
-
return {
|
|
1503
|
-
manifest: freshManifest,
|
|
1504
|
-
tasks: freshTasks,
|
|
1505
|
-
};
|
|
1506
|
-
const taskWithAttempt: TeamTaskState = {
|
|
1507
|
-
...freshTask,
|
|
1508
|
-
attempts: inFlightAttempts,
|
|
1509
|
-
};
|
|
1510
|
-
const result = await withCorrelation(
|
|
1511
|
-
childCorrelation(freshManifest.runId, task.id),
|
|
1512
|
-
() =>
|
|
1513
|
-
runTeamTask({
|
|
1514
|
-
...baseInput,
|
|
1515
|
-
manifest: freshManifest,
|
|
1516
|
-
tasks: freshTasks,
|
|
1517
|
-
task: taskWithAttempt,
|
|
1518
|
-
}),
|
|
1265
|
+
},
|
|
1266
|
+
delayMs,
|
|
1519
1267
|
);
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1268
|
+
},
|
|
1269
|
+
onRetryGivenUp: (attempts, error, info) => {
|
|
1270
|
+
lastAttemptId = info.attemptId;
|
|
1271
|
+
appendDeadletter(manifest, {
|
|
1272
|
+
runId: manifest.runId,
|
|
1273
|
+
taskId: task.id,
|
|
1274
|
+
reason: "max-retries",
|
|
1275
|
+
attempts,
|
|
1523
1276
|
attemptId: info.attemptId,
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
? { error: failed.error }
|
|
1528
|
-
: {}),
|
|
1529
|
-
};
|
|
1530
|
-
attemptsSoFar.push(finishedAttempt);
|
|
1531
|
-
const withAttempt = result.tasks.map((item) =>
|
|
1532
|
-
item.id === task.id
|
|
1533
|
-
? { ...item, attempts: [...attemptsSoFar] }
|
|
1534
|
-
: item,
|
|
1535
|
-
);
|
|
1536
|
-
const enriched = {
|
|
1537
|
-
manifest: result.manifest,
|
|
1538
|
-
tasks: withAttempt,
|
|
1539
|
-
};
|
|
1540
|
-
if (failed) {
|
|
1541
|
-
lastFailed = enriched;
|
|
1542
|
-
throw new Error(
|
|
1543
|
-
failed.error ?? `Task ${task.id} failed.`,
|
|
1544
|
-
);
|
|
1545
|
-
}
|
|
1277
|
+
lastError: error.message,
|
|
1278
|
+
timestamp: new Date().toISOString(),
|
|
1279
|
+
});
|
|
1546
1280
|
input.metricRegistry
|
|
1547
|
-
?.
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
)
|
|
1552
|
-
.observe(
|
|
1553
|
-
{
|
|
1554
|
-
runId: manifest.runId,
|
|
1555
|
-
team: input.team.name,
|
|
1556
|
-
},
|
|
1557
|
-
Math.max(0, attempt - 1),
|
|
1558
|
-
);
|
|
1559
|
-
return enriched;
|
|
1560
|
-
},
|
|
1561
|
-
policy,
|
|
1562
|
-
{
|
|
1563
|
-
signal: input.signal,
|
|
1564
|
-
attemptId: (attempt) =>
|
|
1565
|
-
`${manifest.runId}:${task.id}:attempt-${attempt}`,
|
|
1566
|
-
onAttemptFailed: (
|
|
1567
|
-
attempt,
|
|
1568
|
-
error,
|
|
1569
|
-
delayMs,
|
|
1570
|
-
info,
|
|
1571
|
-
) => {
|
|
1572
|
-
lastAttemptId = info.attemptId;
|
|
1573
|
-
appendEventAsync(manifest.eventsPath, {
|
|
1574
|
-
type: "crew.task.retry_attempt",
|
|
1575
|
-
runId: manifest.runId,
|
|
1576
|
-
taskId: task.id,
|
|
1577
|
-
message: error.message,
|
|
1578
|
-
data: {
|
|
1579
|
-
attempt,
|
|
1580
|
-
attemptId: info.attemptId,
|
|
1581
|
-
delayMs,
|
|
1582
|
-
},
|
|
1583
|
-
metadata: { attemptId: info.attemptId },
|
|
1584
|
-
}).catch((error) =>
|
|
1585
|
-
logInternalError(
|
|
1586
|
-
"team-runner.retry-attempt",
|
|
1587
|
-
error,
|
|
1588
|
-
`taskId=${task.id}`,
|
|
1589
|
-
),
|
|
1590
|
-
);
|
|
1591
|
-
input.metricRegistry
|
|
1592
|
-
?.histogram(
|
|
1593
|
-
"crew.task.retry_delay_ms",
|
|
1594
|
-
"Retry backoff delay, milliseconds",
|
|
1595
|
-
)
|
|
1596
|
-
.observe(
|
|
1597
|
-
{
|
|
1598
|
-
runId: manifest.runId,
|
|
1599
|
-
taskId: task.id,
|
|
1600
|
-
},
|
|
1601
|
-
delayMs,
|
|
1602
|
-
);
|
|
1603
|
-
},
|
|
1604
|
-
onRetryGivenUp: (attempts, error, info) => {
|
|
1605
|
-
lastAttemptId = info.attemptId;
|
|
1606
|
-
appendDeadletter(manifest, {
|
|
1281
|
+
?.counter("crew.task.deadletter_total", "Deadletter triggers by reason")
|
|
1282
|
+
.inc({ reason: "max-retries" });
|
|
1283
|
+
input.metricRegistry?.histogram("crew.task.retry_count", "Retries per task", [0, 1, 2, 3, 5, 10]).observe(
|
|
1284
|
+
{
|
|
1607
1285
|
runId: manifest.runId,
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
lastError: error.message,
|
|
1613
|
-
timestamp: new Date().toISOString(),
|
|
1614
|
-
});
|
|
1615
|
-
input.metricRegistry
|
|
1616
|
-
?.counter(
|
|
1617
|
-
"crew.task.deadletter_total",
|
|
1618
|
-
"Deadletter triggers by reason",
|
|
1619
|
-
)
|
|
1620
|
-
.inc({ reason: "max-retries" });
|
|
1621
|
-
input.metricRegistry
|
|
1622
|
-
?.histogram(
|
|
1623
|
-
"crew.task.retry_count",
|
|
1624
|
-
"Retries per task",
|
|
1625
|
-
[0, 1, 2, 3, 5, 10],
|
|
1626
|
-
)
|
|
1627
|
-
.observe(
|
|
1628
|
-
{
|
|
1629
|
-
runId: manifest.runId,
|
|
1630
|
-
team: input.team.name,
|
|
1631
|
-
},
|
|
1632
|
-
Math.max(0, attempts - 1),
|
|
1633
|
-
);
|
|
1634
|
-
},
|
|
1286
|
+
team: input.team.name,
|
|
1287
|
+
},
|
|
1288
|
+
Math.max(0, attempts - 1),
|
|
1289
|
+
);
|
|
1635
1290
|
},
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
const reason =
|
|
1643
|
-
retryError instanceof CrewCancellationError
|
|
1644
|
-
? retryError.reason
|
|
1645
|
-
: cancellationReasonFromSignal(input.signal);
|
|
1646
|
-
// NOTE: no withRunLock — best-effort only; concurrent writes may cause inconsistency
|
|
1647
|
-
const fresh = loadRunManifestById(
|
|
1648
|
-
manifest.cwd,
|
|
1649
|
-
manifest.runId,
|
|
1650
|
-
);
|
|
1651
|
-
const freshManifest = fresh?.manifest ?? manifest;
|
|
1652
|
-
const freshTasks = fresh?.tasks ?? tasks;
|
|
1653
|
-
const cancelledTasks = freshTasks.map((item) =>
|
|
1654
|
-
item.id === task.id &&
|
|
1655
|
-
(item.status === "queued" ||
|
|
1656
|
-
item.status === "running")
|
|
1657
|
-
? {
|
|
1658
|
-
...item,
|
|
1659
|
-
status: "cancelled" as const,
|
|
1660
|
-
finishedAt: new Date().toISOString(),
|
|
1661
|
-
error: `${reason.message} (${reason.code})`,
|
|
1662
|
-
}
|
|
1663
|
-
: item,
|
|
1664
|
-
);
|
|
1665
|
-
appendEventAsync(freshManifest.eventsPath, {
|
|
1666
|
-
type: "task.cancelled",
|
|
1667
|
-
runId: freshManifest.runId,
|
|
1668
|
-
taskId: task.id,
|
|
1669
|
-
message: reason.message,
|
|
1670
|
-
data: { reason, phase: "retry" },
|
|
1671
|
-
metadata: lastAttemptId
|
|
1672
|
-
? { attemptId: lastAttemptId }
|
|
1673
|
-
: undefined,
|
|
1674
|
-
}).catch((error) =>
|
|
1675
|
-
logInternalError(
|
|
1676
|
-
"team-runner.cancelled",
|
|
1677
|
-
error,
|
|
1678
|
-
`taskId=${task.id}`,
|
|
1679
|
-
),
|
|
1680
|
-
);
|
|
1681
|
-
return {
|
|
1682
|
-
manifest: updateRunStatus(
|
|
1683
|
-
freshManifest,
|
|
1684
|
-
"cancelled",
|
|
1685
|
-
reason.message,
|
|
1686
|
-
),
|
|
1687
|
-
tasks: cancelledTasks,
|
|
1688
|
-
};
|
|
1689
|
-
}
|
|
1690
|
-
if (lastFailed) return lastFailed;
|
|
1291
|
+
},
|
|
1292
|
+
);
|
|
1293
|
+
} catch (retryError) {
|
|
1294
|
+
if (retryError instanceof CrewCancellationError || input.signal?.aborted) {
|
|
1295
|
+
const reason =
|
|
1296
|
+
retryError instanceof CrewCancellationError ? retryError.reason : cancellationReasonFromSignal(input.signal);
|
|
1691
1297
|
// NOTE: no withRunLock — best-effort only; concurrent writes may cause inconsistency
|
|
1692
|
-
const fresh = loadRunManifestById(
|
|
1693
|
-
manifest.cwd,
|
|
1694
|
-
manifest.runId,
|
|
1695
|
-
);
|
|
1298
|
+
const fresh = loadRunManifestById(manifest.cwd, manifest.runId);
|
|
1696
1299
|
const freshManifest = fresh?.manifest ?? manifest;
|
|
1697
1300
|
const freshTasks = fresh?.tasks ?? tasks;
|
|
1698
|
-
const
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
() =>
|
|
1708
|
-
runTeamTask({
|
|
1709
|
-
...baseInput,
|
|
1710
|
-
manifest: freshManifest,
|
|
1711
|
-
tasks: freshTasks,
|
|
1712
|
-
task: freshTask,
|
|
1713
|
-
}),
|
|
1301
|
+
const cancelledTasks = freshTasks.map((item) =>
|
|
1302
|
+
item.id === task.id && (item.status === "queued" || item.status === "running")
|
|
1303
|
+
? {
|
|
1304
|
+
...item,
|
|
1305
|
+
status: "cancelled" as const,
|
|
1306
|
+
finishedAt: new Date().toISOString(),
|
|
1307
|
+
error: `${reason.message} (${reason.code})`,
|
|
1308
|
+
}
|
|
1309
|
+
: item,
|
|
1714
1310
|
);
|
|
1311
|
+
appendEventAsync(freshManifest.eventsPath, {
|
|
1312
|
+
type: "task.cancelled",
|
|
1313
|
+
runId: freshManifest.runId,
|
|
1314
|
+
taskId: task.id,
|
|
1315
|
+
message: reason.message,
|
|
1316
|
+
data: { reason, phase: "retry" },
|
|
1317
|
+
metadata: lastAttemptId ? { attemptId: lastAttemptId } : undefined,
|
|
1318
|
+
}).catch((error) => logInternalError("team-runner.cancelled", error, `taskId=${task.id}`));
|
|
1319
|
+
return {
|
|
1320
|
+
manifest: updateRunStatus(freshManifest, "cancelled", reason.message),
|
|
1321
|
+
tasks: cancelledTasks,
|
|
1322
|
+
};
|
|
1715
1323
|
}
|
|
1716
|
-
|
|
1717
|
-
|
|
1324
|
+
if (lastFailed) return lastFailed;
|
|
1325
|
+
// NOTE: no withRunLock — best-effort only; concurrent writes may cause inconsistency
|
|
1326
|
+
const fresh = loadRunManifestById(manifest.cwd, manifest.runId);
|
|
1327
|
+
const freshManifest = fresh?.manifest ?? manifest;
|
|
1328
|
+
const freshTasks = fresh?.tasks ?? tasks;
|
|
1329
|
+
const freshTask = freshTasks.find((item) => item.id === task.id) ?? task;
|
|
1330
|
+
if (freshTask.status !== "queued" && freshTask.status !== "running") return { manifest: freshManifest, tasks: freshTasks };
|
|
1331
|
+
return withCorrelation(childCorrelation(freshManifest.runId, task.id), () =>
|
|
1332
|
+
runTeamTask({
|
|
1333
|
+
...baseInput,
|
|
1334
|
+
manifest: freshManifest,
|
|
1335
|
+
tasks: freshTasks,
|
|
1336
|
+
task: freshTask,
|
|
1337
|
+
}),
|
|
1338
|
+
);
|
|
1339
|
+
}
|
|
1340
|
+
});
|
|
1718
1341
|
if (results.length === 0) break;
|
|
1719
1342
|
// FIX: Filter out undefined entries from partial results when error occurred
|
|
1720
1343
|
// during parallel execution. Other workers may have written partial results
|
|
1721
1344
|
// before one threw. Results may be partial - some tasks in-flight at error
|
|
1722
1345
|
// time will not have entries in the results array.
|
|
1723
|
-
const validResults = results.filter(
|
|
1724
|
-
(item): item is NonNullable<typeof item> => item !== undefined,
|
|
1725
|
-
);
|
|
1346
|
+
const validResults = results.filter((item): item is NonNullable<typeof item> => item !== undefined);
|
|
1726
1347
|
// Guard: if ALL parallel workers threw before returning, validResults is empty.
|
|
1727
1348
|
// at(-1)! would crash. Mark the run failed rather than crashing.
|
|
1728
1349
|
if (validResults.length === 0) {
|
|
1729
|
-
manifest = updateRunStatus(
|
|
1730
|
-
manifest,
|
|
1731
|
-
"failed",
|
|
1732
|
-
"All parallel tasks failed catastrophically.",
|
|
1733
|
-
);
|
|
1350
|
+
manifest = updateRunStatus(manifest, "failed", "All parallel tasks failed catastrophically.");
|
|
1734
1351
|
return { manifest, tasks };
|
|
1735
1352
|
}
|
|
1736
1353
|
// Reconstruct manifest from the last worker's snapshot. The .artifacts field
|
|
@@ -1750,21 +1367,13 @@ async function executeTeamRunCore(
|
|
|
1750
1367
|
const disk = loadRunManifestById(manifest.cwd, manifest.runId);
|
|
1751
1368
|
const diskManifest = disk?.manifest ?? manifest;
|
|
1752
1369
|
const diskArtifacts = diskManifest.artifacts;
|
|
1753
|
-
const reconciledArtifacts = mergeArtifacts(
|
|
1754
|
-
[
|
|
1755
|
-
...diskArtifacts,
|
|
1756
|
-
...validResults.map((item) => item.manifest.artifacts),
|
|
1757
|
-
].flat(),
|
|
1758
|
-
);
|
|
1370
|
+
const reconciledArtifacts = mergeArtifacts([...diskArtifacts, ...validResults.map((item) => item.manifest.artifacts)].flat());
|
|
1759
1371
|
const resultManifest = updateRunStatus(
|
|
1760
1372
|
{ ...diskManifest, artifacts: reconciledArtifacts },
|
|
1761
1373
|
"running",
|
|
1762
1374
|
"Merged task updates from parallel batch.",
|
|
1763
1375
|
);
|
|
1764
|
-
const resultTasks = mergeTaskUpdatesPreservingTerminal(
|
|
1765
|
-
tasks,
|
|
1766
|
-
validResults,
|
|
1767
|
-
);
|
|
1376
|
+
const resultTasks = mergeTaskUpdatesPreservingTerminal(tasks, validResults);
|
|
1768
1377
|
await saveRunManifestAsync(resultManifest);
|
|
1769
1378
|
await saveRunTasksAsync(resultManifest, resultTasks);
|
|
1770
1379
|
return { resultManifest, resultTasks };
|
|
@@ -1773,13 +1382,7 @@ async function executeTeamRunCore(
|
|
|
1773
1382
|
tasks = mergeResult.resultTasks;
|
|
1774
1383
|
|
|
1775
1384
|
// Advance workflow phases whose tasks are all in terminal state
|
|
1776
|
-
const terminalStatuses = new Set([
|
|
1777
|
-
"completed",
|
|
1778
|
-
"failed",
|
|
1779
|
-
"skipped",
|
|
1780
|
-
"cancelled",
|
|
1781
|
-
"needs_attention",
|
|
1782
|
-
]);
|
|
1385
|
+
const terminalStatuses = new Set(["completed", "failed", "skipped", "cancelled", "needs_attention"]);
|
|
1783
1386
|
const phaseTaskMap = new Map<string, string[]>();
|
|
1784
1387
|
for (const task of tasks) {
|
|
1785
1388
|
if (!task.stepId) continue;
|
|
@@ -1787,11 +1390,7 @@ async function executeTeamRunCore(
|
|
|
1787
1390
|
existing.push(task.id);
|
|
1788
1391
|
phaseTaskMap.set(task.stepId, existing);
|
|
1789
1392
|
}
|
|
1790
|
-
for (
|
|
1791
|
-
let pi = wfMachine.currentPhaseIndex;
|
|
1792
|
-
pi < wfMachine.phases.length;
|
|
1793
|
-
pi++
|
|
1794
|
-
) {
|
|
1393
|
+
for (let pi = wfMachine.currentPhaseIndex; pi < wfMachine.phases.length; pi++) {
|
|
1795
1394
|
const phase = wfMachine.phases[pi]!;
|
|
1796
1395
|
const phaseTaskIds = phaseTaskMap.get(phase.name) ?? [];
|
|
1797
1396
|
if (phaseTaskIds.length === 0) continue;
|
|
@@ -1800,27 +1399,14 @@ async function executeTeamRunCore(
|
|
|
1800
1399
|
return task ? terminalStatuses.has(task.status) : false;
|
|
1801
1400
|
});
|
|
1802
1401
|
if (!allTerminal) break;
|
|
1803
|
-
if (
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
phase.status !== "skipped"
|
|
1807
|
-
) {
|
|
1808
|
-
const completedArtifacts = manifest.artifacts
|
|
1809
|
-
.filter((a) => a.kind === "result" || a.kind === "summary")
|
|
1810
|
-
.map((a) => a.path);
|
|
1811
|
-
const previousPhaseStatus =
|
|
1812
|
-
pi > 0
|
|
1813
|
-
? (wfMachine.phases[pi - 1]?.status ?? "pending")
|
|
1814
|
-
: "completed";
|
|
1402
|
+
if (phase.status !== "completed" && phase.status !== "failed" && phase.status !== "skipped") {
|
|
1403
|
+
const completedArtifacts = manifest.artifacts.filter((a) => a.kind === "result" || a.kind === "summary").map((a) => a.path);
|
|
1404
|
+
const previousPhaseStatus = pi > 0 ? (wfMachine.phases[pi - 1]?.status ?? "pending") : "completed";
|
|
1815
1405
|
const wfContext: PhaseGuardContext = {
|
|
1816
1406
|
completedArtifacts,
|
|
1817
1407
|
previousPhaseStatus,
|
|
1818
1408
|
taskResults: tasks
|
|
1819
|
-
.filter(
|
|
1820
|
-
(t) =>
|
|
1821
|
-
t.status === "completed" ||
|
|
1822
|
-
t.status === "needs_attention",
|
|
1823
|
-
)
|
|
1409
|
+
.filter((t) => t.status === "completed" || t.status === "needs_attention")
|
|
1824
1410
|
.map((t) => ({
|
|
1825
1411
|
taskId: t.id,
|
|
1826
1412
|
status: t.status,
|
|
@@ -1831,18 +1417,9 @@ async function executeTeamRunCore(
|
|
|
1831
1417
|
const phaseTasks = phaseTaskIds
|
|
1832
1418
|
.map((taskId) => tasks.find((t) => t.id === taskId))
|
|
1833
1419
|
.filter((t): t is NonNullable<typeof t> => t !== undefined);
|
|
1834
|
-
const hasFailedOrCancelled = phaseTasks.some(
|
|
1835
|
-
|
|
1836
|
-
);
|
|
1837
|
-
const phaseStatus = hasFailedOrCancelled
|
|
1838
|
-
? "failed"
|
|
1839
|
-
: "completed";
|
|
1840
|
-
const transition = transitionPhase(
|
|
1841
|
-
wfMachine,
|
|
1842
|
-
pi,
|
|
1843
|
-
phaseStatus,
|
|
1844
|
-
wfContext,
|
|
1845
|
-
);
|
|
1420
|
+
const hasFailedOrCancelled = phaseTasks.some((t) => t.status === "failed" || t.status === "cancelled");
|
|
1421
|
+
const phaseStatus = hasFailedOrCancelled ? "failed" : "completed";
|
|
1422
|
+
const transition = transitionPhase(wfMachine, pi, phaseStatus, wfContext);
|
|
1846
1423
|
wfMachine = transition.machine;
|
|
1847
1424
|
if (transition.guardResult && !transition.guardResult.allowed) {
|
|
1848
1425
|
await appendEventAsync(manifest.eventsPath, {
|
|
@@ -1858,10 +1435,7 @@ async function executeTeamRunCore(
|
|
|
1858
1435
|
break;
|
|
1859
1436
|
}
|
|
1860
1437
|
await appendEventAsync(manifest.eventsPath, {
|
|
1861
|
-
type:
|
|
1862
|
-
phaseStatus === "failed"
|
|
1863
|
-
? "workflow.phase_failed"
|
|
1864
|
-
: "workflow.phase_completed",
|
|
1438
|
+
type: phaseStatus === "failed" ? "workflow.phase_failed" : "workflow.phase_completed",
|
|
1865
1439
|
runId: manifest.runId,
|
|
1866
1440
|
message: `Workflow phase '${phase.name}' ${phaseStatus}.`,
|
|
1867
1441
|
data: { phaseIndex: pi, phaseStatus },
|
|
@@ -1870,24 +1444,14 @@ async function executeTeamRunCore(
|
|
|
1870
1444
|
wfMachine = { ...wfMachine, currentPhaseIndex: pi + 1 };
|
|
1871
1445
|
}
|
|
1872
1446
|
|
|
1873
|
-
const cancelledResult = results.find(
|
|
1874
|
-
(item) => item.manifest.status === "cancelled",
|
|
1875
|
-
);
|
|
1447
|
+
const cancelledResult = results.find((item) => item.manifest.status === "cancelled");
|
|
1876
1448
|
if (cancelledResult || input.signal?.aborted) {
|
|
1877
|
-
const reason = input.signal?.aborted
|
|
1878
|
-
|
|
1879
|
-
: undefined;
|
|
1880
|
-
const message =
|
|
1881
|
-
reason?.message ??
|
|
1882
|
-
cancelledResult?.manifest.summary ??
|
|
1883
|
-
"Run cancelled during task execution.";
|
|
1449
|
+
const reason = input.signal?.aborted ? cancellationReasonFromSignal(input.signal) : undefined;
|
|
1450
|
+
const message = reason?.message ?? cancelledResult?.manifest.summary ?? "Run cancelled during task execution.";
|
|
1884
1451
|
manifest = { ...manifest, status: "running" };
|
|
1885
1452
|
manifest = updateRunStatus(manifest, "cancelled", message);
|
|
1886
1453
|
await saveRunTasksAsync(manifest, tasks);
|
|
1887
|
-
saveCrewAgents(
|
|
1888
|
-
manifest,
|
|
1889
|
-
recordsForMaterializedTasks(manifest, tasks, runtimeKind),
|
|
1890
|
-
);
|
|
1454
|
+
saveCrewAgents(manifest, recordsForMaterializedTasks(manifest, tasks, runtimeKind));
|
|
1891
1455
|
await saveRunManifestAsync(manifest);
|
|
1892
1456
|
await appendEventAsync(manifest.eventsPath, {
|
|
1893
1457
|
type: "run.cancelled",
|
|
@@ -1904,56 +1468,31 @@ async function executeTeamRunCore(
|
|
|
1904
1468
|
queueIndex = buildTaskGraphIndex(tasks);
|
|
1905
1469
|
const injectedAfterBatch = attemptAdaptivePlan();
|
|
1906
1470
|
if (injectedAfterBatch.missing) {
|
|
1907
|
-
tasks = markBlocked(
|
|
1908
|
-
tasks,
|
|
1909
|
-
"Adaptive planner did not produce a valid subagent plan.",
|
|
1910
|
-
);
|
|
1471
|
+
tasks = markBlocked(tasks, "Adaptive planner did not produce a valid subagent plan.");
|
|
1911
1472
|
await saveRunTasksAsync(manifest, tasks);
|
|
1912
|
-
saveCrewAgents(
|
|
1913
|
-
|
|
1914
|
-
recordsForMaterializedTasks(manifest, tasks, runtimeKind),
|
|
1915
|
-
);
|
|
1916
|
-
manifest = updateRunStatus(
|
|
1917
|
-
manifest,
|
|
1918
|
-
"blocked",
|
|
1919
|
-
"Adaptive planner did not produce a valid subagent plan.",
|
|
1920
|
-
);
|
|
1473
|
+
saveCrewAgents(manifest, recordsForMaterializedTasks(manifest, tasks, runtimeKind));
|
|
1474
|
+
manifest = updateRunStatus(manifest, "blocked", "Adaptive planner did not produce a valid subagent plan.");
|
|
1921
1475
|
return { manifest, tasks };
|
|
1922
1476
|
}
|
|
1923
1477
|
if (injectedAfterBatch.injected) {
|
|
1924
|
-
manifest = requiresPlanApproval(workflow, input.runtimeConfig)
|
|
1925
|
-
? ensurePlanApprovalRequested(manifest, tasks)
|
|
1926
|
-
: manifest;
|
|
1478
|
+
manifest = requiresPlanApproval(workflow, input.runtimeConfig) ? ensurePlanApprovalRequested(manifest, tasks) : manifest;
|
|
1927
1479
|
queueIndex = buildTaskGraphIndex(tasks);
|
|
1928
1480
|
} else if (
|
|
1929
1481
|
requiresPlanApproval(workflow, input.runtimeConfig) &&
|
|
1930
|
-
(hasPendingMutatingAdaptiveTask(tasks) ||
|
|
1931
|
-
hasPendingMutatingTaskAtBoundary(tasks))
|
|
1482
|
+
(hasPendingMutatingAdaptiveTask(tasks) || hasPendingMutatingTaskAtBoundary(tasks))
|
|
1932
1483
|
) {
|
|
1933
1484
|
manifest = ensurePlanApprovalRequested(manifest, tasks);
|
|
1934
1485
|
}
|
|
1935
1486
|
if (manifest.planApproval?.status === "cancelled") {
|
|
1936
1487
|
tasks = cancelPlanTasks(tasks, "Plan approval was cancelled.");
|
|
1937
1488
|
await saveRunTasksAsync(manifest, tasks);
|
|
1938
|
-
saveCrewAgents(
|
|
1939
|
-
|
|
1940
|
-
recordsForMaterializedTasks(manifest, tasks, runtimeKind),
|
|
1941
|
-
);
|
|
1942
|
-
manifest = updateRunStatus(
|
|
1943
|
-
manifest,
|
|
1944
|
-
"cancelled",
|
|
1945
|
-
"Plan approval was cancelled.",
|
|
1946
|
-
);
|
|
1489
|
+
saveCrewAgents(manifest, recordsForMaterializedTasks(manifest, tasks, runtimeKind));
|
|
1490
|
+
manifest = updateRunStatus(manifest, "cancelled", "Plan approval was cancelled.");
|
|
1947
1491
|
return { manifest, tasks };
|
|
1948
1492
|
}
|
|
1949
1493
|
await saveRunTasksAsync(manifest, tasks);
|
|
1950
|
-
saveCrewAgents(
|
|
1951
|
-
|
|
1952
|
-
recordsForMaterializedTasks(manifest, tasks, runtimeKind),
|
|
1953
|
-
);
|
|
1954
|
-
const completedBatch = tasks.filter((t) =>
|
|
1955
|
-
batchTasks.some((bt) => bt.id === t.id),
|
|
1956
|
-
);
|
|
1494
|
+
saveCrewAgents(manifest, recordsForMaterializedTasks(manifest, tasks, runtimeKind));
|
|
1495
|
+
const completedBatch = tasks.filter((t) => batchTasks.some((bt) => bt.id === t.id));
|
|
1957
1496
|
const batchArtifact = writeArtifact(manifest.artifactsRoot, {
|
|
1958
1497
|
kind: "summary",
|
|
1959
1498
|
relativePath: `batches/${batchTasks.map((task) => task.id).join("+")}.md`,
|
|
@@ -1968,19 +1507,9 @@ async function executeTeamRunCore(
|
|
|
1968
1507
|
});
|
|
1969
1508
|
manifest = {
|
|
1970
1509
|
...manifest,
|
|
1971
|
-
artifacts: mergeArtifacts([
|
|
1972
|
-
...manifest.artifacts,
|
|
1973
|
-
batchArtifact,
|
|
1974
|
-
...(groupDelivery?.artifact ? [groupDelivery.artifact] : []),
|
|
1975
|
-
]),
|
|
1510
|
+
artifacts: mergeArtifacts([...manifest.artifacts, batchArtifact, ...(groupDelivery?.artifact ? [groupDelivery.artifact] : [])]),
|
|
1976
1511
|
};
|
|
1977
|
-
manifest = writeProgress(
|
|
1978
|
-
manifest,
|
|
1979
|
-
tasks,
|
|
1980
|
-
"team-runner",
|
|
1981
|
-
input.executeWorkers,
|
|
1982
|
-
input.runtimeConfig,
|
|
1983
|
-
);
|
|
1512
|
+
manifest = writeProgress(manifest, tasks, "team-runner", input.executeWorkers, input.runtimeConfig);
|
|
1984
1513
|
await saveRunManifestAsync(manifest);
|
|
1985
1514
|
}
|
|
1986
1515
|
|
|
@@ -1998,10 +1527,7 @@ async function executeTeamRunCore(
|
|
|
1998
1527
|
if (effectivenessDecision) {
|
|
1999
1528
|
manifest = {
|
|
2000
1529
|
...manifest,
|
|
2001
|
-
policyDecisions: [
|
|
2002
|
-
...(manifest.policyDecisions ?? []),
|
|
2003
|
-
effectivenessDecision,
|
|
2004
|
-
],
|
|
1530
|
+
policyDecisions: [...(manifest.policyDecisions ?? []), effectivenessDecision],
|
|
2005
1531
|
updatedAt: new Date().toISOString(),
|
|
2006
1532
|
};
|
|
2007
1533
|
await appendEventAsync(manifest.eventsPath, {
|
|
@@ -2011,62 +1537,27 @@ async function executeTeamRunCore(
|
|
|
2011
1537
|
data: { effectiveness, policyDecision: effectivenessDecision },
|
|
2012
1538
|
});
|
|
2013
1539
|
}
|
|
2014
|
-
const blockingDecision = manifest.policyDecisions?.find(
|
|
2015
|
-
(item) => item.action === "block" || item.action === "escalate",
|
|
2016
|
-
);
|
|
1540
|
+
const blockingDecision = manifest.policyDecisions?.find((item) => item.action === "block" || item.action === "escalate");
|
|
2017
1541
|
if (failed) {
|
|
2018
|
-
manifest = updateRunStatus(
|
|
2019
|
-
manifest,
|
|
2020
|
-
"failed",
|
|
2021
|
-
`Failed at task '${failed.id}'.`,
|
|
2022
|
-
);
|
|
1542
|
+
manifest = updateRunStatus(manifest, "failed", `Failed at task '${failed.id}'.`);
|
|
2023
1543
|
} else if (waiting) {
|
|
2024
|
-
manifest = updateRunStatus(
|
|
2025
|
-
manifest,
|
|
2026
|
-
"blocked",
|
|
2027
|
-
`Waiting for response to task '${waiting.id}'.`,
|
|
2028
|
-
);
|
|
1544
|
+
manifest = updateRunStatus(manifest, "blocked", `Waiting for response to task '${waiting.id}'.`);
|
|
2029
1545
|
} else if (running) {
|
|
2030
|
-
manifest = updateRunStatus(
|
|
2031
|
-
manifest,
|
|
2032
|
-
"blocked",
|
|
2033
|
-
`Task '${running.id}' is still running.`,
|
|
2034
|
-
);
|
|
1546
|
+
manifest = updateRunStatus(manifest, "blocked", `Task '${running.id}' is still running.`);
|
|
2035
1547
|
} else if (effectiveness.severity === "failed") {
|
|
2036
|
-
manifest = updateRunStatus(
|
|
2037
|
-
manifest,
|
|
2038
|
-
"failed",
|
|
2039
|
-
effectivenessDecision?.message ?? "Run effectiveness guard failed.",
|
|
2040
|
-
);
|
|
1548
|
+
manifest = updateRunStatus(manifest, "failed", effectivenessDecision?.message ?? "Run effectiveness guard failed.");
|
|
2041
1549
|
} else if (effectiveness.severity === "blocked") {
|
|
2042
|
-
manifest = updateRunStatus(
|
|
2043
|
-
manifest,
|
|
2044
|
-
"blocked",
|
|
2045
|
-
effectivenessDecision?.message ??
|
|
2046
|
-
"Run effectiveness guard blocked completion.",
|
|
2047
|
-
);
|
|
1550
|
+
manifest = updateRunStatus(manifest, "blocked", effectivenessDecision?.message ?? "Run effectiveness guard blocked completion.");
|
|
2048
1551
|
} else if (blockingDecision) {
|
|
2049
|
-
manifest = updateRunStatus(
|
|
2050
|
-
manifest,
|
|
2051
|
-
"blocked",
|
|
2052
|
-
blockingDecision.message,
|
|
2053
|
-
);
|
|
1552
|
+
manifest = updateRunStatus(manifest, "blocked", blockingDecision.message);
|
|
2054
1553
|
} else {
|
|
2055
1554
|
manifest = updateRunStatus(
|
|
2056
1555
|
manifest,
|
|
2057
1556
|
"completed",
|
|
2058
|
-
input.executeWorkers
|
|
2059
|
-
? "Team workflow completed."
|
|
2060
|
-
: "Team workflow scaffold completed without launching child workers.",
|
|
1557
|
+
input.executeWorkers ? "Team workflow completed." : "Team workflow scaffold completed without launching child workers.",
|
|
2061
1558
|
);
|
|
2062
1559
|
}
|
|
2063
|
-
manifest = writeProgress(
|
|
2064
|
-
manifest,
|
|
2065
|
-
tasks,
|
|
2066
|
-
"team-runner",
|
|
2067
|
-
input.executeWorkers,
|
|
2068
|
-
input.runtimeConfig,
|
|
2069
|
-
);
|
|
1560
|
+
manifest = writeProgress(manifest, tasks, "team-runner", input.executeWorkers, input.runtimeConfig);
|
|
2070
1561
|
await saveRunManifestAsync(manifest);
|
|
2071
1562
|
const usage = aggregateUsage(tasks);
|
|
2072
1563
|
const summaryArtifact = writeArtifact(manifest.artifactsRoot, {
|
|
@@ -2086,17 +1577,10 @@ async function executeTeamRunCore(
|
|
|
2086
1577
|
...tasks.map(formatTaskProgress),
|
|
2087
1578
|
"",
|
|
2088
1579
|
"## Effectiveness",
|
|
2089
|
-
...runEffectivenessLines(
|
|
2090
|
-
manifest,
|
|
2091
|
-
tasks,
|
|
2092
|
-
input.executeWorkers,
|
|
2093
|
-
input.runtimeConfig,
|
|
2094
|
-
),
|
|
1580
|
+
...runEffectivenessLines(manifest, tasks, input.executeWorkers, input.runtimeConfig),
|
|
2095
1581
|
"",
|
|
2096
1582
|
"## Policy decisions",
|
|
2097
|
-
...(manifest.policyDecisions?.length
|
|
2098
|
-
? summarizePolicyDecisions(manifest.policyDecisions)
|
|
2099
|
-
: ["- (none)"]),
|
|
1583
|
+
...(manifest.policyDecisions?.length ? summarizePolicyDecisions(manifest.policyDecisions) : ["- (none)"]),
|
|
2100
1584
|
"",
|
|
2101
1585
|
].join("\n"),
|
|
2102
1586
|
});
|
|
@@ -2125,9 +1609,7 @@ async function executeTeamRunCore(
|
|
|
2125
1609
|
// health feature) AND created junk dirs that the recursive state watcher then
|
|
2126
1610
|
// attached extra inotify watches to. Fix: compute the real crew root (3 up)
|
|
2127
1611
|
// and make HEALTH_DIR relative to it.
|
|
2128
|
-
const crewRoot = path.dirname(
|
|
2129
|
-
path.dirname(path.dirname(finalManifest.stateRoot)),
|
|
2130
|
-
);
|
|
1612
|
+
const crewRoot = path.dirname(path.dirname(path.dirname(finalManifest.stateRoot)));
|
|
2131
1613
|
const healthStore = new HealthStore(crewRoot);
|
|
2132
1614
|
healthStore.saveSnapshot({
|
|
2133
1615
|
runId: finalManifest.runId,
|