pi-crew 0.9.56 → 0.9.57
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +1 -1
- package/CHANGELOG.md +29 -0
- package/README.md +53 -5
- package/dist/index.mjs +5350 -4350
- package/docs/README.md +54 -0
- package/package.json +12 -12
- package/skills/real-test-pi-crew/SKILL.md +93 -3
- package/src/agents/discover-agents.ts +68 -10
- package/src/benchmark/feedback-loop.ts +1 -1
- package/src/config/config.ts +2 -1
- package/src/config/types.ts +12 -0
- package/src/extension/action-suggestions.ts +16 -63
- package/src/extension/async-notifier.ts +3 -3
- package/src/extension/command-completions.ts +2 -2
- package/src/extension/crew-cleanup.ts +3 -3
- package/src/extension/crew-vibes/footer.ts +4 -2
- package/src/extension/cross-extension-rpc.ts +10 -5
- package/src/extension/knowledge-injection.ts +57 -10
- package/src/extension/register.ts +2 -2
- package/src/extension/registration/artifact-cleanup.ts +41 -3
- package/src/extension/registration/commands.ts +4 -4
- package/src/extension/registration/context-builder.ts +10 -10
- package/src/extension/registration/crash-recovery-cache.ts +3 -3
- package/src/extension/registration/foreground-run-controller.ts +1 -1
- package/src/extension/registration/lazy-configurers.ts +2 -2
- package/src/extension/registration/lifecycle-handlers.ts +12 -12
- package/src/extension/registration/lifecycle.ts +2 -2
- package/src/extension/registration/observability.ts +2 -2
- package/src/extension/registration/registration-types.ts +3 -3
- package/src/extension/registration/runtime-cleanup.ts +1 -1
- package/src/extension/registration/subagent-helpers.ts +2 -2
- package/src/extension/registration/subagent-manager-setup.ts +2 -2
- package/src/extension/registration/subagent-tools.ts +21 -16
- package/src/extension/registration/team-tool.ts +108 -17
- package/src/extension/registration/viewers.ts +3 -4
- package/src/extension/registration/wire-cross-extension.ts +7 -7
- package/src/extension/rpc-hmac.ts +3 -2
- package/src/extension/run-bundle-schema.ts +13 -4
- package/src/extension/run-export.ts +2 -2
- package/src/extension/run-index.ts +2 -3
- package/src/extension/run-maintenance.ts +57 -1
- package/src/extension/team-manager-command.ts +3 -1
- package/src/extension/team-tool/anchor.ts +8 -2
- package/src/extension/team-tool/api.ts +117 -39
- package/src/extension/team-tool/cancel.ts +23 -8
- package/src/extension/team-tool/chain-executor.ts +1 -1
- package/src/extension/team-tool/dispatch/automate.ts +17 -2
- package/src/extension/team-tool/dispatch/control.ts +17 -2
- package/src/extension/team-tool/dispatch/manage.ts +34 -2
- package/src/extension/team-tool/dispatch/run.ts +17 -2
- package/src/extension/team-tool/dispatch/status.ts +37 -5
- package/src/extension/team-tool/doctor.ts +2 -2
- package/src/extension/team-tool/explain.ts +1 -1
- package/src/extension/team-tool/goal-wrap.ts +5 -5
- package/src/extension/team-tool/goal.ts +30 -9
- package/src/extension/team-tool/handle-schedule.ts +37 -19
- package/src/extension/team-tool/inspect.ts +21 -5
- package/src/extension/team-tool/lifecycle-actions.ts +53 -10
- package/src/extension/team-tool/parallel-dispatch.ts +14 -5
- package/src/extension/team-tool/param-error.ts +24 -0
- package/src/extension/team-tool/respond.ts +20 -6
- package/src/extension/team-tool/run.ts +27 -17
- package/src/extension/team-tool/status.ts +11 -5
- package/src/extension/team-tool.ts +26 -19
- package/src/hooks/registry.ts +1 -1
- package/src/i18n.ts +22 -0
- package/src/observability/event-bus.ts +1 -1
- package/src/prompt/prompt-runtime.ts +2 -2
- package/src/runtime/README.md +35 -0
- package/src/runtime/agent-control.ts +1 -1
- package/src/runtime/anchor-manager.ts +0 -2
- package/src/runtime/async-runner.ts +1 -1
- package/src/runtime/attention-events.ts +1 -1
- package/src/runtime/auto-summarize.ts +0 -2
- package/src/runtime/background-runner.ts +103 -30
- package/src/runtime/{crew-broker-child.ts → broker/crew-broker-child.ts} +2 -2
- package/src/runtime/{crew-broker-client.ts → broker/crew-broker-client.ts} +4 -7
- package/src/runtime/{crew-broker.ts → broker/crew-broker.ts} +13 -21
- package/src/runtime/chain-runner.ts +50 -5
- package/src/runtime/{child-pi-constants.ts → child-pi/child-pi-constants.ts} +1 -1
- package/src/runtime/{child-pi-kill.ts → child-pi/child-pi-kill.ts} +3 -3
- package/src/runtime/{child-pi-spawn.ts → child-pi/child-pi-spawn.ts} +28 -12
- package/src/runtime/{child-pi-steering.ts → child-pi/child-pi-steering.ts} +1 -1
- package/src/runtime/{child-pi-streams.ts → child-pi/child-pi-streams.ts} +11 -4
- package/src/runtime/{child-pi-transcript.ts → child-pi/child-pi-transcript.ts} +6 -6
- package/src/runtime/{child-pi.ts → child-pi/child-pi.ts} +28 -72
- package/src/runtime/{compact-stages → compaction/compact-stages}/bounded-tail.ts +1 -1
- package/src/runtime/{compact-stages → compaction/compact-stages}/index.ts +2 -2
- package/src/runtime/{compaction-summary.ts → compaction/compaction-summary.ts} +3 -3
- package/src/runtime/{tool-output-pruner.ts → compaction/tool-output-pruner.ts} +1 -1
- package/src/runtime/custom-tools/irc-tool.ts +2 -2
- package/src/runtime/delivery-coordinator.ts +0 -3
- package/src/runtime/diagnostic-export.ts +3 -3
- package/src/runtime/foreground-control.ts +1 -1
- package/src/runtime/foreground-watchdog.ts +1 -1
- package/src/runtime/{adaptive-plan.ts → goal-workflow/adaptive-plan.ts} +8 -8
- package/src/runtime/{dynamic-workflow-context.ts → goal-workflow/dynamic-workflow-context.ts} +16 -16
- package/src/runtime/{dynamic-workflow-runner.ts → goal-workflow/dynamic-workflow-runner.ts} +10 -10
- package/src/runtime/{goal-achievement.ts → goal-workflow/goal-achievement.ts} +3 -3
- package/src/runtime/{goal-evaluator.ts → goal-workflow/goal-evaluator.ts} +8 -8
- package/src/runtime/{goal-loop-runner.ts → goal-workflow/goal-loop-runner.ts} +27 -25
- package/src/runtime/{goal-state-store.ts → goal-workflow/goal-state-store.ts} +8 -8
- package/src/runtime/{plan-templates.ts → goal-workflow/plan-templates.ts} +1 -1
- package/src/runtime/group-join.ts +3 -3
- package/src/runtime/handoff-manager.ts +0 -2
- package/src/runtime/{heartbeat-watcher.ts → heartbeat/heartbeat-watcher.ts} +7 -7
- package/src/runtime/iteration-hooks.ts +1 -1
- package/src/runtime/{live-agent-control.ts → live-session/live-agent-control.ts} +2 -2
- package/src/runtime/{live-agent-manager.ts → live-session/live-agent-manager.ts} +4 -4
- package/src/runtime/{live-session-runtime.ts → live-session/live-session-runtime.ts} +39 -41
- package/src/runtime/manifest-cache.ts +1 -1
- package/src/runtime/{model-fallback.ts → model/model-fallback.ts} +1 -1
- package/src/runtime/{pi-args.ts → model/pi-args.ts} +20 -5
- package/src/runtime/model/provider-extensions.ts +116 -0
- package/src/runtime/{runtime-policy.ts → model/runtime-policy.ts} +2 -2
- package/src/runtime/{runtime-resolver.ts → model/runtime-resolver.ts} +3 -3
- package/src/runtime/{runtime-warmup.ts → model/runtime-warmup.ts} +8 -8
- package/src/runtime/orphan-worker-registry.ts +1 -1
- package/src/runtime/{progress-tracker.ts → output/progress-tracker.ts} +1 -1
- package/src/runtime/{result-extractor.ts → output/result-extractor.ts} +4 -4
- package/src/runtime/{sidechain-output.ts → output/sidechain-output.ts} +2 -2
- package/src/runtime/{stream-preview.ts → output/stream-preview.ts} +1 -1
- package/src/runtime/{streaming-output.ts → output/streaming-output.ts} +2 -2
- package/src/runtime/{tool-progress.ts → output/tool-progress.ts} +1 -1
- package/src/runtime/parent-guard.ts +30 -10
- package/src/runtime/pipeline-runner.ts +2 -2
- package/src/runtime/policy-engine.ts +2 -2
- package/src/runtime/{cancellation.ts → process/cancellation.ts} +1 -1
- package/src/runtime/{post-exit-stdio-guard.ts → process/post-exit-stdio-guard.ts} +1 -1
- package/src/runtime/{process-lifecycle.ts → process/process-lifecycle.ts} +3 -3
- package/src/runtime/{checkpoint.ts → recovery/checkpoint.ts} +4 -4
- package/src/runtime/{crash-recovery.ts → recovery/crash-recovery.ts} +93 -21
- package/src/runtime/{overflow-recovery.ts → recovery/overflow-recovery.ts} +1 -1
- package/src/runtime/{recovery-recipes.ts → recovery/recovery-recipes.ts} +1 -1
- package/src/runtime/{retry-executor.ts → recovery/retry-executor.ts} +2 -2
- package/src/runtime/{retry-runner.ts → recovery/retry-runner.ts} +2 -8
- package/src/runtime/run-tracker.ts +1 -1
- package/src/runtime/run-worker.ts +3 -3
- package/src/runtime/{coalesce-tasks.ts → scheduling/coalesce-tasks.ts} +3 -3
- package/src/runtime/{concurrency.ts → scheduling/concurrency.ts} +1 -1
- package/src/runtime/{parallel-research.ts → scheduling/parallel-research.ts} +4 -2
- package/src/runtime/{run-coalesced-task-group.ts → scheduling/run-coalesced-task-group.ts} +95 -36
- package/src/runtime/{task-graph-scheduler.ts → scheduling/task-graph-scheduler.ts} +1 -1
- package/src/runtime/session-resources.ts +1 -1
- package/src/runtime/settings-store.ts +1 -1
- package/src/runtime/single-agent-compose.ts +1 -0
- package/src/runtime/stale-reconciler.ts +1 -1
- package/src/runtime/subagent-manager.ts +1 -1
- package/src/runtime/supervisor-contact.ts +1 -1
- package/src/runtime/task-output-context.ts +7 -5
- package/src/runtime/task-runner/child-executor.ts +62 -22
- package/src/runtime/task-runner/live-executor.ts +8 -8
- package/src/runtime/task-runner/post-execution.ts +12 -12
- package/src/runtime/task-runner/pre-execution.ts +6 -6
- package/src/runtime/task-runner/progress.ts +1 -1
- package/src/runtime/task-runner/run-projection.ts +1 -1
- package/src/runtime/task-runner/scaffold-executor.ts +1 -1
- package/src/runtime/task-runner/state-helpers.ts +2 -2
- package/src/runtime/task-runner.ts +4 -4
- package/src/runtime/team-runner.ts +380 -139
- package/src/runtime/{completion-guard.ts → verification/completion-guard.ts} +1 -1
- package/src/runtime/{green-contract.ts → verification/green-contract.ts} +1 -1
- package/src/runtime/{post-checks.ts → verification/post-checks.ts} +4 -4
- package/src/runtime/{verification-gates.ts → verification/verification-gates.ts} +5 -5
- package/src/runtime/{verification-integrity.ts → verification/verification-integrity.ts} +1 -1
- package/src/schema/config-schema.ts +1 -0
- package/src/schema/team-tool-schema.ts +133 -177
- package/src/state/README.md +44 -0
- package/src/state/atomic-write.ts +24 -3
- package/src/state/{locks.ts → coordination/locks.ts} +118 -38
- package/src/state/{mailbox.ts → coordination/mailbox.ts} +14 -8
- package/src/state/{schedule.ts → coordination/schedule.ts} +3 -3
- package/src/state/{task-claims.ts → coordination/task-claims.ts} +1 -1
- package/src/state/decision-ledger.ts +1 -1
- package/src/state/{event-log-rotation.ts → event-log/event-log-rotation.ts} +161 -31
- package/src/state/{event-log.ts → event-log/event-log.ts} +38 -26
- package/src/state/{jsonl-writer.ts → event-log/jsonl-writer.ts} +14 -6
- package/src/state/hook-instinct-bridge.ts +2 -2
- package/src/state/{active-run-registry.ts → stores/active-run-registry.ts} +7 -7
- package/src/state/{artifact-store.ts → stores/artifact-store.ts} +62 -4
- package/src/state/{blob-store.ts → stores/blob-store.ts} +3 -3
- package/src/state/{health-store.ts → stores/health-store.ts} +3 -3
- package/src/state/{instinct-store.ts → stores/instinct-store.ts} +3 -3
- package/src/state/{observation-store.ts → stores/observation-store.ts} +2 -2
- package/src/state/{run-cache.ts → stores/run-cache.ts} +4 -4
- package/src/state/{run-graph.ts → stores/run-graph.ts} +4 -4
- package/src/state/{run-metrics.ts → stores/run-metrics.ts} +3 -3
- package/src/state/{state-store.ts → stores/state-store.ts} +236 -17
- package/src/state/types.ts +27 -3
- package/src/ui/component.ts +55 -0
- package/src/ui/crew-select-list.ts +3 -1
- package/src/ui/dashboard-panes/agents-pane.ts +1 -1
- package/src/ui/dwf-phase-display.ts +1 -1
- package/src/ui/heartbeat-aggregator.ts +1 -1
- package/src/ui/keybinding-map.ts +179 -6
- package/src/ui/layout-primitives.ts +9 -5
- package/src/ui/live-conversation-overlay.ts +1 -1
- package/src/ui/live-run-sidebar.ts +1 -1
- package/src/ui/overlay-stack.ts +148 -0
- package/src/ui/overlays/agent-picker-overlay.ts +1 -1
- package/src/ui/overlays/mailbox-compose-overlay.ts +1 -1
- package/src/ui/overlays/mailbox-detail-overlay.ts +2 -2
- package/src/ui/powerbar-publisher.ts +1 -1
- package/src/ui/render-scheduler.ts +1 -1
- package/src/ui/run-action-dispatcher.ts +3 -3
- package/src/ui/run-dashboard.ts +5 -33
- package/src/ui/run-event-bus.ts +3 -3
- package/src/ui/run-snapshot-cache.ts +37 -18
- package/src/ui/settings-overlay.ts +13 -21
- package/src/ui/snapshot-types.ts +1 -1
- package/src/ui/status-colors.ts +0 -2
- package/src/ui/terminal-status.ts +1 -1
- package/src/ui/theme-adapter.ts +33 -13
- package/src/ui/tool-renderers/index.ts +8 -0
- package/src/ui/transcript-viewer.ts +37 -13
- package/src/ui/widget/index.ts +32 -9
- package/src/ui/widget/widget-formatters.ts +62 -4
- package/src/ui/widget/widget-model.ts +2 -2
- package/src/ui/widget/widget-renderer.ts +1 -1
- package/src/utils/conflict-detect.ts +58 -5
- package/src/utils/ndjson.ts +2 -2
- package/src/utils/socket-path.ts +1 -1
- package/src/workflows/workflow-config.ts +1 -1
- package/src/worktree/cleanup.ts +1 -1
- package/src/worktree/worktree-manager.ts +88 -39
- package/workflows/chain.workflow.md +2 -2
- package/index.bundle.ts +0 -25
- package/scripts/bench-check.mjs +0 -96
- package/scripts/bench-cold-start.mjs +0 -216
- package/scripts/build-bundle.mjs +0 -92
- package/scripts/check-all-skills.ts +0 -294
- package/scripts/check-bundle-size.mjs +0 -38
- package/scripts/check-bundle-staleness.mjs +0 -97
- package/scripts/check-conflict-markers.mjs +0 -91
- package/scripts/check-lazy-imports.mjs +0 -28
- package/scripts/dev-runner.mjs +0 -106
- package/scripts/profile-startup.mjs +0 -125
- package/scripts/release-smoke.mjs +0 -74
- package/scripts/run-bench.mjs +0 -47
- package/scripts/run-real-chain.ts +0 -41
- package/scripts/smoke-team-perf.ts +0 -43
- package/scripts/test-issue-29-crash.ts +0 -111
- package/scripts/test-issue-29-e2e.ts +0 -183
- package/scripts/test-issue-29-real-runtime.ts +0 -330
- package/scripts/test-issue-29-real-tasks.ts +0 -387
- package/scripts/test-issue-29-team-tool.ts +0 -105
- package/scripts/test-runner.mjs +0 -84
- package/scripts/verify-flicker-fix.ts +0 -109
- package/scripts/verify-skill.ts +0 -550
- package/scripts/watch-bundle.mjs +0 -259
- package/scripts/watchdog-harness.ts +0 -119
- package/src/adapters/claude-adapter.ts +0 -23
- package/src/adapters/codex-adapter.ts +0 -21
- package/src/adapters/cursor-adapter.ts +0 -17
- package/src/adapters/export-util.ts +0 -143
- package/src/adapters/index.ts +0 -15
- package/src/adapters/registry.ts +0 -18
- package/src/adapters/types.ts +0 -23
- package/src/runtime/code-summary.ts +0 -292
- package/src/runtime/cross-extension-rpc.ts +0 -143
- package/src/runtime/hidden-handoff.ts +0 -407
- package/src/runtime/prose-compressor.ts +0 -162
- package/src/subagents/async-entry.ts +0 -1
- package/src/subagents/index.ts +0 -3
- package/src/subagents/live/control.ts +0 -1
- package/src/subagents/live/manager.ts +0 -1
- package/src/subagents/live/realtime.ts +0 -1
- package/src/subagents/live/session-runtime.ts +0 -1
- package/src/subagents/manager.ts +0 -1
- package/src/subagents/spawn.ts +0 -1
- package/test-integration-check.ts +0 -118
- /package/src/runtime/{broker-issuer.ts → broker/broker-issuer.ts} +0 -0
- /package/src/runtime/{crew-broker-tokens.ts → broker/crew-broker-tokens.ts} +0 -0
- /package/src/runtime/{child-pi-pool.ts → child-pi/child-pi-pool.ts} +0 -0
- /package/src/runtime/{compact-pipeline.ts → compaction/compact-pipeline.ts} +0 -0
- /package/src/runtime/{compact-stages → compaction/compact-stages}/ansi-strip-stage.ts +0 -0
- /package/src/runtime/{compact-stages → compaction/compact-stages}/blank-collapse-stage.ts +0 -0
- /package/src/runtime/{compact-stages → compaction/compact-stages}/deduplicate-stage.ts +0 -0
- /package/src/runtime/{compact-stages → compaction/compact-stages}/head-snap-stage.ts +0 -0
- /package/src/runtime/{compact-stages → compaction/compact-stages}/tail-capture-stage.ts +0 -0
- /package/src/runtime/{compact-stages → compaction/compact-stages}/truncation-stage.ts +0 -0
- /package/src/runtime/{important-line-classifier.ts → compaction/important-line-classifier.ts} +0 -0
- /package/src/runtime/{heartbeat-gradient.ts → heartbeat/heartbeat-gradient.ts} +0 -0
- /package/src/runtime/{worker-heartbeat.ts → heartbeat/worker-heartbeat.ts} +0 -0
- /package/src/runtime/{worker-startup.ts → heartbeat/worker-startup.ts} +0 -0
- /package/src/runtime/{intercom-bridge.ts → live-session/intercom-bridge.ts} +0 -0
- /package/src/runtime/{live-control-realtime.ts → live-session/live-control-realtime.ts} +0 -0
- /package/src/runtime/{live-extension-bridge.ts → live-session/live-extension-bridge.ts} +0 -0
- /package/src/runtime/{live-irc.ts → live-session/live-irc.ts} +0 -0
- /package/src/runtime/{live-session-health.ts → live-session/live-session-health.ts} +0 -0
- /package/src/runtime/{model-resolver.ts → model/model-resolver.ts} +0 -0
- /package/src/runtime/{model-scope.ts → model/model-scope.ts} +0 -0
- /package/src/runtime/{output-validator.ts → output/output-validator.ts} +0 -0
- /package/src/runtime/{pi-json-output.ts → output/pi-json-output.ts} +0 -0
- /package/src/runtime/{progress-event-coalescer.ts → output/progress-event-coalescer.ts} +0 -0
- /package/src/runtime/{cancellation-token.ts → process/cancellation-token.ts} +0 -0
- /package/src/runtime/{zombie-scanner.ts → process/zombie-scanner.ts} +0 -0
- /package/src/runtime/{crash-classification.ts → recovery/crash-classification.ts} +0 -0
- /package/src/runtime/{batch-barrier.ts → scheduling/batch-barrier.ts} +0 -0
- /package/src/runtime/{global-worker-cap.ts → scheduling/global-worker-cap.ts} +0 -0
- /package/src/runtime/{parallel-utils.ts → scheduling/parallel-utils.ts} +0 -0
- /package/src/runtime/{scheduler.ts → scheduling/scheduler.ts} +0 -0
- /package/src/runtime/{semaphore.ts → scheduling/semaphore.ts} +0 -0
- /package/src/runtime/{task-graph.ts → scheduling/task-graph.ts} +0 -0
- /package/src/runtime/{verification-worktree.ts → verification/verification-worktree.ts} +0 -0
- /package/src/state/{event-reconstructor.ts → event-log/event-reconstructor.ts} +0 -0
- /package/src/state/{worker-atomic-writer.ts → event-log/worker-atomic-writer.ts} +0 -0
|
@@ -8,47 +8,54 @@ import { childCorrelation, withCorrelation } from "../observability/correlation.
|
|
|
8
8
|
import type { MetricRegistry } from "../observability/metric-registry.ts";
|
|
9
9
|
import { PluginRegistry } from "../plugins/plugin-registry.ts";
|
|
10
10
|
import { NextJsPlugin, VitePlugin, VitestPlugin } from "../plugins/plugins/index.ts";
|
|
11
|
-
import { hashArtifactContent as hashContent, writeArtifact } from "../state/artifact-store.ts";
|
|
12
11
|
import { atomicWriteFile, flushPendingAtomicWrites } from "../state/atomic-write.ts";
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
|
|
12
|
+
import { canTransitionRunStatus, TEAM_TASK_STATUSES, TEAM_TERMINAL_TASK_STATUSES, type TeamTaskStatus } from "../state/contracts.ts";
|
|
13
|
+
import { withRunLock } from "../state/coordination/locks.ts";
|
|
14
|
+
import {
|
|
15
|
+
appendEvent,
|
|
16
|
+
appendEventAsync,
|
|
17
|
+
appendEventBuffered,
|
|
18
|
+
appendEventFireAndForget,
|
|
19
|
+
flushEventLogBuffer,
|
|
20
|
+
} from "../state/event-log/event-log.ts";
|
|
21
|
+
import { hashArtifactContent as hashContent, writeArtifact } from "../state/stores/artifact-store.ts";
|
|
22
|
+
import { HealthStore } from "../state/stores/health-store.ts";
|
|
23
|
+
import { loadRunManifestById, saveRunManifestAsync, saveRunTasksAsync, updateRunStatus } from "../state/stores/state-store.ts";
|
|
17
24
|
import type { ArtifactDescriptor, PolicyDecision, TaskAttemptState, TeamRunManifest, TeamTaskState } from "../state/types.ts";
|
|
18
25
|
import { aggregateUsage, formatTokens, formatUsage } from "../state/usage.ts";
|
|
19
26
|
import type { TeamConfig } from "../teams/team-config.ts";
|
|
20
27
|
import { logInternalError } from "../utils/internal-error.ts";
|
|
21
28
|
import type { WorkflowConfig, WorkflowStep } from "../workflows/workflow-config.ts";
|
|
22
29
|
import { checkBranchFreshness } from "../worktree/branch-freshness.ts";
|
|
23
|
-
import { buildSyntheticTerminalEvidence, CrewCancellationError, cancellationReasonFromSignal } from "./cancellation.ts";
|
|
24
|
-
import { buildDispatchUnits, type DispatchUnit, planCoalescedGroups } from "./coalesce-tasks.ts";
|
|
25
|
-
import { type BatchConcurrencyDecision, resolveBatchConcurrency } from "./concurrency.ts";
|
|
26
30
|
import { readCrewAgents, saveCrewAgents, saveCrewAgentsCoalesced } from "./crew-agent-records.ts";
|
|
27
31
|
import type { CrewRuntimeKind } from "./crew-agent-runtime.ts";
|
|
28
32
|
import { crewHooks } from "./crew-hooks.ts";
|
|
29
33
|
import { appendDeadletter } from "./deadletter.ts";
|
|
30
34
|
import { effectivenessPolicyDecision, evaluateRunEffectiveness, formatRunEffectivenessLines } from "./effectiveness.ts";
|
|
31
|
-
import { applyGoalAchievement, assessGoalAchievement } from "./goal-achievement.ts";
|
|
35
|
+
import { applyGoalAchievement, assessGoalAchievement } from "./goal-workflow/goal-achievement.ts";
|
|
32
36
|
import { deliverGroupJoin, resolveGroupJoinMode } from "./group-join.ts";
|
|
33
|
-
import { terminateLiveAgentsForRun } from "./live-agent-manager.ts";
|
|
37
|
+
import { terminateLiveAgentsForRun } from "./live-session/live-agent-manager.ts";
|
|
38
|
+
import { resolveTaskRuntimeKind } from "./model/runtime-policy.ts";
|
|
39
|
+
import type { CrewRuntimeCapabilities } from "./model/runtime-resolver.ts";
|
|
34
40
|
import { filterReadyByWriteOverlap } from "./path-overlap.ts";
|
|
35
41
|
import { evaluateCrewPolicy, summarizePolicyDecisions } from "./policy-engine.ts";
|
|
36
|
-
import {
|
|
37
|
-
import {
|
|
42
|
+
import { buildSyntheticTerminalEvidence, CrewCancellationError, cancellationReasonFromSignal } from "./process/cancellation.ts";
|
|
43
|
+
import { buildRecoveryLedger, shouldRerunFailedTask } from "./recovery/recovery-recipes.ts";
|
|
44
|
+
import { DEFAULT_RETRY_POLICY, executeWithRetry, type RetryPolicy } from "./recovery/retry-executor.ts";
|
|
38
45
|
import { permissionForRole } from "./role-permission.ts";
|
|
39
|
-
import { runCoalescedTaskGroup } from "./run-coalesced-task-group.ts";
|
|
40
46
|
import { registerRunPromise, rejectRunPromise, resolveRunPromise } from "./run-tracker.ts";
|
|
41
|
-
import {
|
|
42
|
-
import type
|
|
43
|
-
import {
|
|
44
|
-
import { buildExecutionPlan as buildDagExecutionPlan, getReadyTasks as getDagReadyTasks, type TaskNode } from "./task-graph.ts";
|
|
47
|
+
import { buildDispatchUnits, type DispatchUnit, planCoalescedGroups } from "./scheduling/coalesce-tasks.ts";
|
|
48
|
+
import { type BatchConcurrencyDecision, resolveBatchConcurrency } from "./scheduling/concurrency.ts";
|
|
49
|
+
import { runCoalescedTaskGroup } from "./scheduling/run-coalesced-task-group.ts";
|
|
50
|
+
import { buildExecutionPlan as buildDagExecutionPlan, getReadyTasks as getDagReadyTasks, type TaskNode } from "./scheduling/task-graph.ts";
|
|
45
51
|
import {
|
|
46
52
|
buildTaskGraphIndex,
|
|
47
53
|
refreshTaskGraphQueues,
|
|
48
54
|
type TaskGraphIndex,
|
|
49
55
|
type TaskGraphSchedulerSnapshot,
|
|
50
56
|
taskGraphSnapshot,
|
|
51
|
-
} from "./task-graph-scheduler.ts";
|
|
57
|
+
} from "./scheduling/task-graph-scheduler.ts";
|
|
58
|
+
import { recordsForMaterializedTasks } from "./task-display.ts";
|
|
52
59
|
import { aggregateTaskOutputs } from "./task-output-context.ts";
|
|
53
60
|
import { clearStablePrefixCache, computeStablePrefixComponents } from "./task-runner/prompt-builder.ts";
|
|
54
61
|
import { runTeamTask, type SpawnBudget } from "./task-runner.ts";
|
|
@@ -246,18 +253,32 @@ function isNonTerminalTaskStatus(status: TeamTaskState["status"]): boolean {
|
|
|
246
253
|
* terminalised with the given status.
|
|
247
254
|
* - With `filter`: the filter is the sole gate — the non-terminal check is
|
|
248
255
|
* NOT applied automatically, matching per-task/per-id variants.
|
|
256
|
+
* - Optional `transform(task, terminalised)`: lets a caller attach
|
|
257
|
+
* site-specific fields (graph mutation, terminalEvidence) to the
|
|
258
|
+
* terminalised task. `terminalised` already carries status/finishedAt/error;
|
|
259
|
+
* the transform returns it unchanged or a modified copy.
|
|
260
|
+
*
|
|
261
|
+
* RT-14: the two remaining inline cancel sites (cancelPlanTasks,
|
|
262
|
+
* cancelRunFromSignal) route through this helper via `transform` so EVERY
|
|
263
|
+
* cancel site uses the single shared transform. Their extra logic
|
|
264
|
+
* (graph mutation / terminalEvidence) is preserved inside the transform.
|
|
249
265
|
*
|
|
250
266
|
* `markBlocked` is intentionally NOT unified here (it sets status "skipped",
|
|
251
267
|
* not cancelled/failed, and only acts on "queued" tasks).
|
|
252
268
|
*/
|
|
253
|
-
function cancelNonTerminalTasks(
|
|
269
|
+
export function cancelNonTerminalTasks(
|
|
254
270
|
tasks: TeamTaskState[],
|
|
255
271
|
status: "cancelled" | "failed",
|
|
256
272
|
reason: string,
|
|
257
273
|
filter?: (task: TeamTaskState) => boolean,
|
|
274
|
+
transform?: (task: TeamTaskState, terminalised: TeamTaskState) => TeamTaskState,
|
|
258
275
|
): TeamTaskState[] {
|
|
259
276
|
const predicate = filter ?? ((task: TeamTaskState) => isNonTerminalTaskStatus(task.status));
|
|
260
|
-
return tasks.map((task) =>
|
|
277
|
+
return tasks.map((task) => {
|
|
278
|
+
if (!predicate(task)) return task;
|
|
279
|
+
const terminalised: TeamTaskState = { ...task, status, finishedAt: new Date().toISOString(), error: reason };
|
|
280
|
+
return transform ? transform(task, terminalised) : terminalised;
|
|
281
|
+
});
|
|
261
282
|
}
|
|
262
283
|
|
|
263
284
|
/**
|
|
@@ -280,34 +301,73 @@ function isMalformedFinishedAtReplacement(currentTime: number, updatedTime: numb
|
|
|
280
301
|
return !Number.isFinite(currentTime) && Number.isFinite(updatedTime);
|
|
281
302
|
}
|
|
282
303
|
|
|
304
|
+
/**
|
|
305
|
+
* RT-16: status-level gate for shouldMergeTaskUpdate. Returns the stable
|
|
306
|
+
* "from->to" key used by REJECTED_STATUS_MERGE_TRANSITIONS.
|
|
307
|
+
*/
|
|
308
|
+
function statusMergeKey(from: TeamTaskStatus, to: TeamTaskStatus): string {
|
|
309
|
+
return `${from}->${to}`;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* RT-16 — derived merge-gate transition table.
|
|
314
|
+
*
|
|
315
|
+
* The set of old->new status pairs that shouldMergeTaskUpdate must REJECT based
|
|
316
|
+
* solely on the status transition (before any field-level comparison). It
|
|
317
|
+
* replaces the former 13 hand-written status guards that hand-duplicated the
|
|
318
|
+
* lifecycle table. Built once from the single source-of-truth table
|
|
319
|
+
* (TEAM_TASK_STATUSES + TEAM_TERMINAL_TASK_STATUSES — the terminal half of
|
|
320
|
+
* TEAM_TASK_STATUS_TRANSITIONS) plus two merge-specific policies that are
|
|
321
|
+
* STRICTER than the lifecycle table on the parallel-merge path:
|
|
322
|
+
*
|
|
323
|
+
* P1 Terminal preservation — every terminal->non-terminal pair is rejected.
|
|
324
|
+
* The lifecycle table permits retries (e.g. completed->queued), but a stale
|
|
325
|
+
* worker snapshot must never resurrect a settled task.
|
|
326
|
+
* P2 Completed integrity — five terminal->terminal flips that touch the
|
|
327
|
+
* "completed" success terminal are rejected: completed->failed,
|
|
328
|
+
* completed->needs_attention, failed->completed, cancelled->completed,
|
|
329
|
+
* needs_attention->completed. (completed may still move to
|
|
330
|
+
* cancelled/skipped; that is intentionally allowed, so these are NOT simply
|
|
331
|
+
* "every illegal terminal->terminal flip".)
|
|
332
|
+
* P3 waiting->running regression — the single stale-snapshot case.
|
|
333
|
+
*
|
|
334
|
+
* The decision for every old->new pair is byte-for-byte identical to the former
|
|
335
|
+
* 7 status guards (verified exhaustively in
|
|
336
|
+
* test/unit/team-runner-should-merge-table.test.ts).
|
|
337
|
+
*/
|
|
338
|
+
const REJECTED_STATUS_MERGE_TRANSITIONS: ReadonlySet<string> = (() => {
|
|
339
|
+
const rejected = new Set<string>();
|
|
340
|
+
// P1 — terminal preservation: reject every terminal->non-terminal pair.
|
|
341
|
+
for (const from of TEAM_TASK_STATUSES) {
|
|
342
|
+
if (!TEAM_TERMINAL_TASK_STATUSES.has(from)) continue;
|
|
343
|
+
for (const to of TEAM_TASK_STATUSES) {
|
|
344
|
+
if (!TEAM_TERMINAL_TASK_STATUSES.has(to)) rejected.add(statusMergeKey(from, to));
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
// P3 — waiting->running stale-snapshot regression.
|
|
348
|
+
rejected.add(statusMergeKey("waiting", "running"));
|
|
349
|
+
// P2 — completed integrity flips (bespoke terminal->terminal policy).
|
|
350
|
+
const completedIntegrityFlips: ReadonlyArray<[TeamTaskStatus, TeamTaskStatus]> = [
|
|
351
|
+
["completed", "failed"],
|
|
352
|
+
["completed", "needs_attention"],
|
|
353
|
+
["failed", "completed"],
|
|
354
|
+
["cancelled", "completed"],
|
|
355
|
+
["needs_attention", "completed"],
|
|
356
|
+
];
|
|
357
|
+
for (const [from, to] of completedIntegrityFlips) rejected.add(statusMergeKey(from, to));
|
|
358
|
+
return rejected;
|
|
359
|
+
})();
|
|
360
|
+
|
|
283
361
|
function shouldMergeTaskUpdate(current: TeamTaskState, updated: TeamTaskState): boolean {
|
|
284
|
-
//
|
|
285
|
-
//
|
|
286
|
-
//
|
|
287
|
-
|
|
288
|
-
//
|
|
289
|
-
//
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
if (
|
|
293
|
-
// Explicitly block completed↔needs_attention terminal-to-terminal transitions.
|
|
294
|
-
// Both are success terminal states used interchangeably; stale worker updates must
|
|
295
|
-
// not cause a completed task to appear as needs_attention or vice versa.
|
|
296
|
-
if (current.status === "completed" && updated.status === "needs_attention") return false;
|
|
297
|
-
if (current.status === "needs_attention" && updated.status === "completed") return false;
|
|
298
|
-
// Explicitly block failed→completed resurrection. Both statuses are terminal,
|
|
299
|
-
// but completed is the success terminal state and should not be reachable from
|
|
300
|
-
// failed via a stale merge. The check above only guards non-terminal→terminal.
|
|
301
|
-
if (current.status === "failed" && updated.status === "completed") return false;
|
|
302
|
-
// Mirror that guard for the other dangerous terminal→terminal flips (reverse
|
|
303
|
-
// audit CANCEL-3 + F3): a cancelled task must not be resurrected to completed,
|
|
304
|
-
// and a completed task must not be demoted to failed, by a stale worker merge.
|
|
305
|
-
// The task transition table (contracts.ts) only permits terminal→queued
|
|
306
|
-
// (retry); these flips are always stale results. A worker that completed after
|
|
307
|
-
// the task was cancelled, or a stale failed result arriving after completion,
|
|
308
|
-
// must not flip a settled terminal status.
|
|
309
|
-
if (current.status === "cancelled" && updated.status === "completed") return false;
|
|
310
|
-
if (current.status === "completed" && updated.status === "failed") return false;
|
|
362
|
+
// RT-16: status-level gate — reject stale/dangerous transitions via the
|
|
363
|
+
// derived transition table (REJECTED_STATUS_MERGE_TRANSITIONS) instead of
|
|
364
|
+
// hand-written guards. Parallel workers receive the same input snapshot; a
|
|
365
|
+
// later result may still carry stale copies. The table encodes three
|
|
366
|
+
// merge-specific policies stricter than the lifecycle table: terminal
|
|
367
|
+
// preservation (no terminal->non-terminal resurrection), completed integrity
|
|
368
|
+
// (no flipping the "completed" success terminal to/from failed or
|
|
369
|
+
// needs_attention), and the waiting->running stale-snapshot regression.
|
|
370
|
+
if (REJECTED_STATUS_MERGE_TRANSITIONS.has(statusMergeKey(current.status, updated.status))) return false;
|
|
311
371
|
// Guard: when current is "running" but has resultArtifact (another worker already
|
|
312
372
|
// completed it), a stale updated with status="running" and no resultArtifact
|
|
313
373
|
// must not overwrite the actual completed state.
|
|
@@ -359,6 +419,8 @@ function shouldMergeTaskUpdate(current: TeamTaskState, updated: TeamTaskState):
|
|
|
359
419
|
updated.agentProgress?.lastActivityAt !== current.agentProgress?.lastActivityAt;
|
|
360
420
|
return hasMeaningfulUpdate;
|
|
361
421
|
}
|
|
422
|
+
/** Exposed for the exhaustive status-merge table test (RT-16). */
|
|
423
|
+
export const __test__shouldMergeTaskUpdate = shouldMergeTaskUpdate;
|
|
362
424
|
|
|
363
425
|
// H4 fix: rename to descriptive name. Kept __test__ as alias for backward
|
|
364
426
|
// compat test imports.
|
|
@@ -406,14 +468,14 @@ export function mergeTaskUpdatesPreservingTerminal(base: TeamTaskState[], result
|
|
|
406
468
|
/** @deprecated Use mergeTaskUpdatesPreservingTerminal. Kept for backward test import compat. */
|
|
407
469
|
export const __test__mergeTaskUpdates = mergeTaskUpdatesPreservingTerminal;
|
|
408
470
|
|
|
409
|
-
// 2.8: adaptive-plan parsing/repair/injection moved to src/runtime/adaptive-plan.ts.
|
|
471
|
+
// 2.8: adaptive-plan parsing/repair/injection moved to src/runtime/goal-workflow/adaptive-plan.ts.
|
|
410
472
|
// Re-export the test-only helpers so existing test imports still resolve.
|
|
411
473
|
export {
|
|
412
474
|
__test__parseAdaptivePlan,
|
|
413
475
|
__test__repairAdaptivePlan,
|
|
414
|
-
} from "./adaptive-plan.ts";
|
|
476
|
+
} from "./goal-workflow/adaptive-plan.ts";
|
|
415
477
|
|
|
416
|
-
import { injectAdaptivePlanIfReady } from "./adaptive-plan.ts";
|
|
478
|
+
import { injectAdaptivePlanIfReady } from "./goal-workflow/adaptive-plan.ts";
|
|
417
479
|
|
|
418
480
|
function formatTaskProgress(task: TeamTaskState): string {
|
|
419
481
|
return `- ${task.id}: ${task.status} (${task.role} -> ${task.agent})${task.taskPacket ? ` scope=${task.taskPacket.scope}` : ""}${task.verification ? ` green=${task.verification.observedGreenLevel}/${task.verification.requiredGreenLevel}` : ""}${task.error ? ` - ${task.error}` : ""}`;
|
|
@@ -442,7 +504,11 @@ function runEffectivenessLines(
|
|
|
442
504
|
// (the previous implementation ran 2 redundant passes on every batch) to
|
|
443
505
|
// a single-pass Map-based replacement: remove the existing entry by path, then
|
|
444
506
|
// append the new one. Net complexity: O(N) build + O(1) replace per write.
|
|
445
|
-
|
|
507
|
+
// RT-7: key on manifest.runId (stable string) instead of object identity
|
|
508
|
+
// (WeakMap). Every writeProgress mutator returns a NEW manifest object via
|
|
509
|
+
// spread, so object-identity keying meant the cache NEVER hit. Using runId
|
|
510
|
+
// makes back-to-back calls (same millisecond) actually dedup.
|
|
511
|
+
const lastProgressContentHash = new Map<string, string>();
|
|
446
512
|
|
|
447
513
|
function writeProgress(
|
|
448
514
|
manifest: TeamRunManifest,
|
|
@@ -481,19 +547,33 @@ function writeProgress(
|
|
|
481
547
|
// both calls happen within the same millisecond. It's a minor win but
|
|
482
548
|
// matches the audit recommendation (skip artifact write when nothing
|
|
483
549
|
// material changed).
|
|
484
|
-
|
|
550
|
+
// RT-7: compute the content hash ONCE (was hashed twice per call: once
|
|
551
|
+
// for the canSkip comparison and again for the cache .set). Key the cache
|
|
552
|
+
// on manifest.runId (stable) instead of object identity (never hit).
|
|
553
|
+
const contentHash = hashContent(content);
|
|
554
|
+
const prevHash = lastProgressContentHash.get(manifest.runId);
|
|
485
555
|
// Cheap pre-check: avoid the redaction + atomicWrite + readback roundtrip
|
|
486
556
|
// when both the timestamp and the input args are identical to last time.
|
|
487
|
-
const canSkip = prevHash ===
|
|
557
|
+
const canSkip = prevHash === contentHash;
|
|
488
558
|
|
|
489
559
|
const progress = canSkip
|
|
490
560
|
? (() => {
|
|
491
|
-
// Reuse the previous artifact
|
|
492
|
-
//
|
|
561
|
+
// Reuse the previous artifact rather than rebuilding one via
|
|
562
|
+
// writeArtifact. This skips mkdirSync, resolveRealContainedPath,
|
|
493
563
|
// redactSecrets, atomicWriteFile, and the post-write readFileSync +
|
|
494
564
|
// statSync.
|
|
495
565
|
const existing = manifest.artifacts.find((a) => a.kind === "progress");
|
|
496
|
-
if (existing)
|
|
566
|
+
if (existing) {
|
|
567
|
+
// RT-7a: return a FRESH descriptor with a refreshed createdAt
|
|
568
|
+
// instead of reusing the stale existing reference. The existing
|
|
569
|
+
// descriptor's createdAt reflects the FIRST write time, not this
|
|
570
|
+
// skip-write; refreshing it matches the non-skip path (writeArtifact
|
|
571
|
+
// stamps createdAt with the actual write time) so the manifest
|
|
572
|
+
// always carries a descriptor whose createdAt reflects the current
|
|
573
|
+
// write. Content is identical (that's why we skipped), so path /
|
|
574
|
+
// sizeBytes / contentHash / retention are unchanged.
|
|
575
|
+
return { ...existing, createdAt: new Date().toISOString() };
|
|
576
|
+
}
|
|
497
577
|
// No prior progress artifact (rare; first call from a stale manifest
|
|
498
578
|
// view). Fall through to the normal write.
|
|
499
579
|
return writeArtifact(manifest.artifactsRoot, {
|
|
@@ -509,7 +589,7 @@ function writeProgress(
|
|
|
509
589
|
producer,
|
|
510
590
|
content,
|
|
511
591
|
});
|
|
512
|
-
lastProgressContentHash.set(manifest,
|
|
592
|
+
lastProgressContentHash.set(manifest.runId, contentHash);
|
|
513
593
|
|
|
514
594
|
// P6 dedup: replace by path in a single Map pass instead of
|
|
515
595
|
// .filter(...) // O(N) to remove the old entry
|
|
@@ -531,6 +611,13 @@ function writeProgress(
|
|
|
531
611
|
};
|
|
532
612
|
}
|
|
533
613
|
|
|
614
|
+
/** @internal RT-7 test export — verify cache is keyed on runId (stable string). */
|
|
615
|
+
export const __test__lastProgressContentHash = lastProgressContentHash;
|
|
616
|
+
/** @internal RT-7 test export — exercise writeProgress directly. */
|
|
617
|
+
export const __test__writeProgress = writeProgress;
|
|
618
|
+
/** @internal RT-14 test export — verify cancelPlanTasks preserves graph mutation after consolidation. */
|
|
619
|
+
export const __test__cancelPlanTasks = cancelPlanTasks;
|
|
620
|
+
|
|
534
621
|
function applyPolicy(manifest: TeamRunManifest, tasks: TeamTaskState[], limits?: CrewLimitsConfig): TeamRunManifest {
|
|
535
622
|
const branchFreshness = checkBranchFreshness(manifest.cwd);
|
|
536
623
|
const branchArtifact = writeArtifact(manifest.artifactsRoot, {
|
|
@@ -678,17 +765,15 @@ async function ensurePlanApprovalRequested(manifest: TeamRunManifest, tasks: Tea
|
|
|
678
765
|
}
|
|
679
766
|
|
|
680
767
|
function cancelPlanTasks(tasks: TeamTaskState[], reason: string): TeamTaskState[] {
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
: task,
|
|
691
|
-
);
|
|
768
|
+
// RT-14: delegate to the shared cancelNonTerminalTasks helper. The
|
|
769
|
+
// non-terminal gate (queued/running/waiting) is identical to the helper's
|
|
770
|
+
// default filter (isNonTerminalTaskStatus). The only site-specific logic is
|
|
771
|
+
// the graph mutation (move the task graph to the "done" queue), passed via
|
|
772
|
+
// the `transform` hook so every cancel site uses the single shared path.
|
|
773
|
+
return cancelNonTerminalTasks(tasks, "cancelled", reason, undefined, (task, terminalised) => ({
|
|
774
|
+
...terminalised,
|
|
775
|
+
graph: task.graph ? { ...task.graph, queue: "done" } : undefined,
|
|
776
|
+
}));
|
|
692
777
|
}
|
|
693
778
|
|
|
694
779
|
function hasPendingMutatingAdaptiveTask(tasks: TeamTaskState[]): boolean {
|
|
@@ -734,6 +819,25 @@ function dagReadyTaskIds(tasks: TeamTaskState[], completedIds: Set<string>): str
|
|
|
734
819
|
return getDagReadyTasks(plan, completedIds);
|
|
735
820
|
}
|
|
736
821
|
|
|
822
|
+
/** RT-12: result shape from a settled dispatch unit (pre-created wrapper). */
|
|
823
|
+
type SettledUnit = {
|
|
824
|
+
unitKey: string;
|
|
825
|
+
result: { manifest: TeamRunManifest; tasks: TeamTaskState[] } | undefined;
|
|
826
|
+
error: Error | undefined;
|
|
827
|
+
};
|
|
828
|
+
|
|
829
|
+
/**
|
|
830
|
+
* RT-12: in-flight dispatch unit. `wrapped` is a pre-created wrapper promise
|
|
831
|
+
* (try/catch → SettledUnit) so mergeUnitResult can Promise.race without
|
|
832
|
+
* allocating new async closures every loop iteration (O(C) total wrappers
|
|
833
|
+
* instead of O(C×T) churn).
|
|
834
|
+
*/
|
|
835
|
+
type PendingUnit = {
|
|
836
|
+
taskIds: string[];
|
|
837
|
+
promise: Promise<{ manifest: TeamRunManifest; tasks: TeamTaskState[] }>;
|
|
838
|
+
wrapped: Promise<SettledUnit>;
|
|
839
|
+
};
|
|
840
|
+
|
|
737
841
|
/**
|
|
738
842
|
* Drain in-flight dispatch units (pendingUnits) by aborting the run-scoped
|
|
739
843
|
* controller and awaiting all settled promises before clearing the map.
|
|
@@ -744,14 +848,17 @@ function dagReadyTaskIds(tasks: TeamTaskState[], completedIds: Set<string>): str
|
|
|
744
848
|
*
|
|
745
849
|
* Exported so unit tests can exercise it directly.
|
|
746
850
|
*/
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
851
|
+
/** Settled outcome of a single in-flight dispatch unit promise (returned by drainPendingUnits). */
|
|
852
|
+
export type DrainOutcome = PromiseSettledResult<{ manifest: TeamRunManifest; tasks: TeamTaskState[] }>;
|
|
853
|
+
|
|
854
|
+
export async function drainPendingUnits<
|
|
855
|
+
T extends { taskIds: string[]; promise: Promise<{ manifest: TeamRunManifest; tasks: TeamTaskState[] }> },
|
|
856
|
+
>(pendingUnits: Map<string, T>, controller?: AbortController): Promise<DrainOutcome[]> {
|
|
857
|
+
if (pendingUnits.size === 0) return [];
|
|
752
858
|
controller?.abort();
|
|
753
|
-
await Promise.allSettled([...pendingUnits.values()].map((p) => p.promise));
|
|
859
|
+
const outcomes = await Promise.allSettled([...pendingUnits.values()].map((p) => p.promise));
|
|
754
860
|
pendingUnits.clear();
|
|
861
|
+
return outcomes;
|
|
755
862
|
}
|
|
756
863
|
|
|
757
864
|
export async function executeTeamRun(input: ExecuteTeamRunInput): Promise<{ manifest: TeamRunManifest; tasks: TeamTaskState[] }> {
|
|
@@ -965,6 +1072,12 @@ export async function executeTeamRun(input: ExecuteTeamRunInput): Promise<{ mani
|
|
|
965
1072
|
// NEW-M1: clear the stable-prefix cache (keyed by runId) so it does not
|
|
966
1073
|
// grow unbounded across runs in a long-lived session.
|
|
967
1074
|
clearStablePrefixCache();
|
|
1075
|
+
// RT-7b: clear the progress-content cache entry for this run so the
|
|
1076
|
+
// module-level lastProgressContentHash Map (keyed by runId) does not grow
|
|
1077
|
+
// unbounded across runs in a long-lived session. The finally block is the
|
|
1078
|
+
// single guaranteed exit point (success, failure, and re-throw), so this
|
|
1079
|
+
// runs on every run completion. Mirrors clearStablePrefixCache() above.
|
|
1080
|
+
lastProgressContentHash.delete(manifest.runId);
|
|
968
1081
|
}
|
|
969
1082
|
}
|
|
970
1083
|
|
|
@@ -988,7 +1101,7 @@ interface SchedulerContext {
|
|
|
988
1101
|
tasks: TeamTaskState[];
|
|
989
1102
|
queueIndex: TaskGraphIndex;
|
|
990
1103
|
wfMachine: WorkflowStateMachine;
|
|
991
|
-
pendingUnits: Map<string,
|
|
1104
|
+
pendingUnits: Map<string, PendingUnit>;
|
|
992
1105
|
runController: AbortController;
|
|
993
1106
|
runtimeKind: CrewRuntimeKind;
|
|
994
1107
|
adaptivePlanInjected: boolean;
|
|
@@ -1020,6 +1133,43 @@ type SchedulerDecision =
|
|
|
1020
1133
|
coalesceEnabled: boolean;
|
|
1021
1134
|
};
|
|
1022
1135
|
|
|
1136
|
+
/**
|
|
1137
|
+
* RT-13: Safely normalize the manifest status to "running" so a subsequent
|
|
1138
|
+
* terminal transition (e.g. → cancelled) goes through updateRunStatus legally.
|
|
1139
|
+
*
|
|
1140
|
+
* Replaces the former inline status-rewrite hack
|
|
1141
|
+
* `manifest = { ...manifest, status: "running" }` which bypassed
|
|
1142
|
+
* updateRunStatus entirely. Unlike the raw spread, this validates the
|
|
1143
|
+
* transition via canTransitionRunStatus. Does NOT call updateRunStatus —
|
|
1144
|
+
* no event is emitted, no persistence — preserving the exact observable
|
|
1145
|
+
* behavior of the former hack. If the transition is somehow illegal, the
|
|
1146
|
+
* manifest is returned unchanged (safe fallback, no throw introduced).
|
|
1147
|
+
*
|
|
1148
|
+
* All non-terminal statuses and the terminal statuses failed/cancelled/
|
|
1149
|
+
* completed can legally reach "running" per TEAM_RUN_STATUS_TRANSITIONS, so
|
|
1150
|
+
* in practice the validation always passes.
|
|
1151
|
+
*/
|
|
1152
|
+
export function setRunStatusRunning(manifest: TeamRunManifest): TeamRunManifest {
|
|
1153
|
+
if (canTransitionRunStatus(manifest.status, "running")) {
|
|
1154
|
+
return { ...manifest, status: "running" };
|
|
1155
|
+
}
|
|
1156
|
+
return manifest;
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
/**
|
|
1160
|
+
* RT-17: compute a bounded batch-summary filename slug from coalesced task IDs.
|
|
1161
|
+
*
|
|
1162
|
+
* The unbounded join of coalesced task IDs (e.g. "01+02+…+20") can exceed
|
|
1163
|
+
* NAME_MAX (255) on ext4 with ~20 members. For short joins (common case: ≤180
|
|
1164
|
+
* chars) use the raw IDs to preserve readability + uniqueness; for long joins,
|
|
1165
|
+
* hash to a fixed-length slug (SHA-256 hex = 64 chars) with a member-count
|
|
1166
|
+
* prefix for human scannability.
|
|
1167
|
+
*/
|
|
1168
|
+
export function batchSummarySlug(taskIds: string[]): string {
|
|
1169
|
+
const joined = taskIds.join("+");
|
|
1170
|
+
return joined.length <= 180 ? joined : `coalesced-${taskIds.length}-${hashContent(joined)}`;
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1023
1173
|
/**
|
|
1024
1174
|
* CORE-4 extraction 1: handle a pre-aborted signal at the top of the
|
|
1025
1175
|
* scheduler loop.
|
|
@@ -1039,25 +1189,25 @@ async function cancelRunFromSignal(ctx: SchedulerContext): Promise<SchedulerDeci
|
|
|
1039
1189
|
const cancelReason = cancellationReasonFromSignal(ctx.input.signal);
|
|
1040
1190
|
const message = `${cancelReason.message} (${cancelReason.code})`;
|
|
1041
1191
|
const cancelledTaskIds: string[] = [];
|
|
1042
|
-
|
|
1043
|
-
|
|
1192
|
+
// RT-14: delegate to the shared cancelNonTerminalTasks helper. The
|
|
1193
|
+
// non-terminal gate (queued/running/waiting) is identical to the helper's
|
|
1194
|
+
// default filter. The site-specific logic lives in the `transform` hook:
|
|
1195
|
+
// (1) collect cancelledTaskIds for the run-status event payload, and
|
|
1196
|
+
// (2) synthesise terminalEvidence for in-flight ("running") workers so the
|
|
1197
|
+
// signal-abort is recorded. Both are preserved exactly — only the
|
|
1198
|
+
// transform frame changed, not the per-task output.
|
|
1199
|
+
ctx.tasks = cancelNonTerminalTasks(ctx.tasks, "cancelled", message, undefined, (task, terminalised) => {
|
|
1044
1200
|
cancelledTaskIds.push(task.id);
|
|
1045
|
-
const base = {
|
|
1046
|
-
...task,
|
|
1047
|
-
status: "cancelled" as const,
|
|
1048
|
-
finishedAt: new Date().toISOString(),
|
|
1049
|
-
error: message,
|
|
1050
|
-
};
|
|
1051
1201
|
if (task.status === "running") {
|
|
1052
1202
|
return {
|
|
1053
|
-
...
|
|
1203
|
+
...terminalised,
|
|
1054
1204
|
terminalEvidence: [
|
|
1055
1205
|
...(task.terminalEvidence ?? []),
|
|
1056
1206
|
buildSyntheticTerminalEvidence("worker", cancelReason, task.startedAt),
|
|
1057
1207
|
],
|
|
1058
1208
|
};
|
|
1059
1209
|
}
|
|
1060
|
-
return
|
|
1210
|
+
return terminalised;
|
|
1061
1211
|
});
|
|
1062
1212
|
await saveRunTasksAsync(ctx.manifest, ctx.tasks);
|
|
1063
1213
|
await Promise.all(
|
|
@@ -1134,6 +1284,67 @@ async function handleFailedTask(ctx: SchedulerContext): Promise<SchedulerDecisio
|
|
|
1134
1284
|
});
|
|
1135
1285
|
return { kind: "continue" }; // loop re-processes the re-queued task
|
|
1136
1286
|
}
|
|
1287
|
+
// RT-1: drain in-flight units and merge settled results BEFORE terminalising
|
|
1288
|
+
// tasks. The streaming-dispatch path only adds to ctx.pendingUnits — it
|
|
1289
|
+
// never updates ctx.tasks to "running" — so ctx.tasks is stale ("queued")
|
|
1290
|
+
// for in-flight tasks. A direct saveRunTasksAsync would overwrite disk
|
|
1291
|
+
// "running" with the stale "queued" status, then markBlocked maps that to
|
|
1292
|
+
// "skipped". team resume never re-queues skipped tasks → permanent work
|
|
1293
|
+
// loss. Draining first preserves settled results and gives non-settled
|
|
1294
|
+
// in-flight tasks a re-queueable terminal status (cancelled, not skipped).
|
|
1295
|
+
//
|
|
1296
|
+
// #3 refactor: drainPendingUnits now RETURNS the settled outcomes directly,
|
|
1297
|
+
// so we collect inflightTaskIds before draining (it clears the map), then
|
|
1298
|
+
// consume the returned outcomes — no redundant re-await of the same promises.
|
|
1299
|
+
// After drain, ctx.pendingUnits is empty, so the finally-block
|
|
1300
|
+
// drainPendingUnits call (~:2419) is a no-op (idempotent — no double-drain).
|
|
1301
|
+
const inflightTaskIds = new Set<string>();
|
|
1302
|
+
for (const unit of ctx.pendingUnits.values()) {
|
|
1303
|
+
for (const id of unit.taskIds) inflightTaskIds.add(id);
|
|
1304
|
+
}
|
|
1305
|
+
const outcomes = await drainPendingUnits(ctx.pendingUnits, ctx.runController);
|
|
1306
|
+
const validResults: { manifest: TeamRunManifest; tasks: TeamTaskState[] }[] = [];
|
|
1307
|
+
for (const outcome of outcomes) {
|
|
1308
|
+
if (outcome.status === "fulfilled") validResults.push(outcome.value);
|
|
1309
|
+
}
|
|
1310
|
+
if (validResults.length > 0) {
|
|
1311
|
+
// Merge under the run lock — same pattern as mergeUnitResult:
|
|
1312
|
+
// flush pending writes, load disk state, merge artifacts + tasks,
|
|
1313
|
+
// save atomically.
|
|
1314
|
+
const mergeResult = await withRunLock(ctx.manifest, async () => {
|
|
1315
|
+
flushPendingAtomicWrites();
|
|
1316
|
+
const disk = loadRunManifestById(ctx.manifest.cwd, ctx.manifest.runId);
|
|
1317
|
+
const diskManifest = disk?.manifest ?? ctx.manifest;
|
|
1318
|
+
const reconciledArtifacts = mergeArtifacts([
|
|
1319
|
+
...diskManifest.artifacts,
|
|
1320
|
+
...validResults.flatMap((item) => item.manifest.artifacts),
|
|
1321
|
+
]);
|
|
1322
|
+
const resultManifest = updateRunStatus(
|
|
1323
|
+
{ ...diskManifest, artifacts: reconciledArtifacts },
|
|
1324
|
+
"running",
|
|
1325
|
+
"Merged in-flight results during failed-task abort.",
|
|
1326
|
+
);
|
|
1327
|
+
const resultTasks = mergeTaskUpdatesPreservingTerminal(disk?.tasks ?? ctx.tasks, validResults);
|
|
1328
|
+
await saveRunManifestAsync(resultManifest);
|
|
1329
|
+
await saveRunTasksAsync(resultManifest, resultTasks);
|
|
1330
|
+
return { resultManifest, resultTasks };
|
|
1331
|
+
});
|
|
1332
|
+
ctx.manifest = mergeResult.resultManifest;
|
|
1333
|
+
ctx.tasks = mergeResult.resultTasks;
|
|
1334
|
+
}
|
|
1335
|
+
// RT-1: cancel in-flight tasks that did NOT settle (e.g. rejected promises)
|
|
1336
|
+
// so team resume CAN re-queue them. markBlocked maps queued→skipped, which
|
|
1337
|
+
// resume never re-queues — work would be lost permanently. Only cancel
|
|
1338
|
+
// tasks that are both in-flight AND still non-terminal (settled tasks with
|
|
1339
|
+
// a terminal status are preserved).
|
|
1340
|
+
ctx.tasks = cancelNonTerminalTasks(
|
|
1341
|
+
ctx.tasks,
|
|
1342
|
+
"cancelled",
|
|
1343
|
+
`Cancelled by failed task '${failed.id}'.`,
|
|
1344
|
+
(task) => inflightTaskIds.has(task.id) && isNonTerminalTaskStatus(task.status),
|
|
1345
|
+
);
|
|
1346
|
+
// RT-1: remaining queued tasks (never dispatched) → skipped (original
|
|
1347
|
+
// behavior preserved for downstream tasks not yet in-flight).
|
|
1137
1348
|
ctx.tasks = markBlocked(ctx.tasks, `Blocked by failed task '${failed.id}'.`);
|
|
1138
1349
|
await saveRunTasksAsync(ctx.manifest, ctx.tasks);
|
|
1139
1350
|
saveCrewAgents(ctx.manifest, recordsForMaterializedTasks(ctx.manifest, ctx.tasks, ctx.runtimeKind));
|
|
@@ -1466,6 +1677,8 @@ async function dispatchBatch(ctx: SchedulerContext, decision: DispatchBatchDecis
|
|
|
1466
1677
|
runtimeKind,
|
|
1467
1678
|
workspaceId: input.workspaceId,
|
|
1468
1679
|
onJsonEvent: input.onJsonEvent,
|
|
1680
|
+
runtimeConfig: input.runtimeConfig,
|
|
1681
|
+
reliability: input.reliability,
|
|
1469
1682
|
teamRole,
|
|
1470
1683
|
perTaskRuntime,
|
|
1471
1684
|
});
|
|
@@ -1674,9 +1887,26 @@ async function dispatchBatch(ctx: SchedulerContext, decision: DispatchBatchDecis
|
|
|
1674
1887
|
for (const unit of dispatchUnits) {
|
|
1675
1888
|
const unitKey = unit.kind === "singleton" ? unit.taskId : unit.group.id;
|
|
1676
1889
|
const unitTaskIds = unit.kind === "singleton" ? [unit.taskId] : unit.group.tasks.map((t) => t.id);
|
|
1890
|
+
// RT-12: create the wrapper promise ONCE at dispatch time so
|
|
1891
|
+
// mergeUnitResult can Promise.race on pre-existing wrappers instead
|
|
1892
|
+
// of allocating new async closures every loop iteration.
|
|
1893
|
+
const rawPromise = dispatchUnit(unit);
|
|
1894
|
+
const wrapped: Promise<SettledUnit> = (async () => {
|
|
1895
|
+
try {
|
|
1896
|
+
const result = await rawPromise;
|
|
1897
|
+
return {
|
|
1898
|
+
unitKey,
|
|
1899
|
+
result: result as { manifest: TeamRunManifest; tasks: TeamTaskState[] } | undefined,
|
|
1900
|
+
error: undefined as Error | undefined,
|
|
1901
|
+
};
|
|
1902
|
+
} catch (error) {
|
|
1903
|
+
return { unitKey, result: undefined, error: error instanceof Error ? error : new Error(String(error)) };
|
|
1904
|
+
}
|
|
1905
|
+
})();
|
|
1677
1906
|
ctx.pendingUnits.set(unitKey, {
|
|
1678
1907
|
taskIds: unitTaskIds,
|
|
1679
|
-
promise:
|
|
1908
|
+
promise: rawPromise,
|
|
1909
|
+
wrapped,
|
|
1680
1910
|
});
|
|
1681
1911
|
}
|
|
1682
1912
|
}
|
|
@@ -1701,23 +1931,11 @@ async function dispatchBatch(ctx: SchedulerContext, decision: DispatchBatchDecis
|
|
|
1701
1931
|
* @param ctx The scheduler context.
|
|
1702
1932
|
*/
|
|
1703
1933
|
async function mergeUnitResult(ctx: SchedulerContext): Promise<SchedulerDecision | null> {
|
|
1704
|
-
//
|
|
1705
|
-
//
|
|
1706
|
-
//
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
try {
|
|
1710
|
-
const result = await pending.promise;
|
|
1711
|
-
return {
|
|
1712
|
-
unitKey: key,
|
|
1713
|
-
result: result as { manifest: TeamRunManifest; tasks: TeamTaskState[] } | undefined,
|
|
1714
|
-
error: undefined as Error | undefined,
|
|
1715
|
-
};
|
|
1716
|
-
} catch (error) {
|
|
1717
|
-
return { unitKey: key, result: undefined, error: error instanceof Error ? error : new Error(String(error)) };
|
|
1718
|
-
}
|
|
1719
|
-
}),
|
|
1720
|
-
);
|
|
1934
|
+
// RT-12: race on pre-created wrapper promises (created once at dispatch
|
|
1935
|
+
// time) instead of rebuilding a wrapper-promise array with new async
|
|
1936
|
+
// closures every iteration. This reduces allocation from O(C×T) wrapper
|
|
1937
|
+
// promises to O(C) total (one per unit, created once at dispatch).
|
|
1938
|
+
const settled = await Promise.race([...ctx.pendingUnits.values()].map((u) => u.wrapped));
|
|
1721
1939
|
const completedUnit = ctx.pendingUnits.get(settled.unitKey)!;
|
|
1722
1940
|
ctx.pendingUnits.delete(settled.unitKey);
|
|
1723
1941
|
|
|
@@ -1946,7 +2164,7 @@ async function enforceRunBudget(ctx: SchedulerContext): Promise<SchedulerDecisio
|
|
|
1946
2164
|
taskUsage: taskTotal,
|
|
1947
2165
|
},
|
|
1948
2166
|
}).then(
|
|
1949
|
-
() =>
|
|
2167
|
+
() => undefined,
|
|
1950
2168
|
(error) => logInternalError("team-runner.fair-share-event", error, `taskId=${violatorId}`),
|
|
1951
2169
|
),
|
|
1952
2170
|
);
|
|
@@ -2042,6 +2260,14 @@ async function finalizeRun(ctx: SchedulerContext): Promise<{ manifest: TeamRunMa
|
|
|
2042
2260
|
// runnable). This is NOT a completed run — mark it blocked rather than
|
|
2043
2261
|
// false-green "completed".
|
|
2044
2262
|
manifest = updateRunStatus(manifest, "blocked", "Run exited with queued tasks still pending.");
|
|
2263
|
+
} else if (manifest.status === "failed" || manifest.status === "cancelled") {
|
|
2264
|
+
// The run was already marked failed/cancelled mid-run (e.g. handleFailedTask
|
|
2265
|
+
// on a coalesced-group race where the failing task's status was later
|
|
2266
|
+
// mutated by the group-drain, or a cancel). Preserve that terminal status —
|
|
2267
|
+
// do NOT force "completed" here: failed -> completed is not in
|
|
2268
|
+
// TEAM_RUN_STATUS_TRANSITIONS and would throw an invalid-transition error.
|
|
2269
|
+
// (No updateRunStatus call: from===to is a no-op, but the intent here is
|
|
2270
|
+
// explicitly "leave the earlier decision intact".)
|
|
2045
2271
|
} else {
|
|
2046
2272
|
manifest = updateRunStatus(
|
|
2047
2273
|
manifest,
|
|
@@ -2192,7 +2418,7 @@ async function executeTeamRunCore(
|
|
|
2192
2418
|
// task can be dispatched as soon as a slot frees, without waiting for
|
|
2193
2419
|
// the entire batch to complete. Each entry maps a unit key (singleton
|
|
2194
2420
|
// task ID or coalesced-group ID) to the in-flight promise + member IDs. ──
|
|
2195
|
-
const pendingUnits = new Map<string,
|
|
2421
|
+
const pendingUnits = new Map<string, PendingUnit>();
|
|
2196
2422
|
|
|
2197
2423
|
// CORE-1: run-scoped AbortController linked to input.signal. Aborted by
|
|
2198
2424
|
// drainPendingUnits() so in-flight dispatch promises are settled (and
|
|
@@ -2224,11 +2450,17 @@ async function executeTeamRunCore(
|
|
|
2224
2450
|
// CORE-1: single drain point — all early returns + normal exit settle pendingUnits via finally block.
|
|
2225
2451
|
try {
|
|
2226
2452
|
while (tasks.some((task) => task.status === "queued") || pendingUnits.size > 0) {
|
|
2227
|
-
// CORE-4: full sync of mutable closure locals → ctx at the top
|
|
2228
|
-
// iteration.
|
|
2229
|
-
//
|
|
2230
|
-
//
|
|
2231
|
-
// mergeUnitResult
|
|
2453
|
+
// CORE-4 / RT-15: full sync of mutable closure locals → ctx at the top
|
|
2454
|
+
// of every iteration. This is the SINGLE forward-sync point — all
|
|
2455
|
+
// extracted scheduler functions (cancelRunFromSignal /
|
|
2456
|
+
// handleFailedTask / selectDispatchBatch / dispatchBatch /
|
|
2457
|
+
// mergeUnitResult / advanceWorkflowPhases / enforceRunBudget) read
|
|
2458
|
+
// ctx directly. The per-function redundant forward-syncs were removed
|
|
2459
|
+
// (RT-15) because back-syncs after each call keep locals aligned with
|
|
2460
|
+
// ctx, making intermediate re-syncs no-ops. The inline post-merge
|
|
2461
|
+
// block (adaptive plan re-injection, phase advance, queueIndex
|
|
2462
|
+
// rebuild) mutates these locals; without this top-of-loop sync the
|
|
2463
|
+
// extracted functions would read stale ctx fields — e.g. ctx.workflow
|
|
2232
2464
|
// missing newly-injected adaptive steps → E006 step-not-found.
|
|
2233
2465
|
ctx.tasks = tasks;
|
|
2234
2466
|
ctx.manifest = manifest;
|
|
@@ -2242,24 +2474,35 @@ async function executeTeamRunCore(
|
|
|
2242
2474
|
const signalDecision = await cancelRunFromSignal(ctx);
|
|
2243
2475
|
if (signalDecision?.kind === "return") return signalDecision.result;
|
|
2244
2476
|
|
|
2245
|
-
// CORE-4 extraction 2: failed-task handling.
|
|
2246
|
-
//
|
|
2247
|
-
// returns a SchedulerDecision.
|
|
2248
|
-
ctx.tasks = tasks;
|
|
2249
|
-
ctx.manifest = manifest;
|
|
2477
|
+
// CORE-4 extraction 2: failed-task handling. ctx is already synced
|
|
2478
|
+
// from the top-of-loop sync (RT-15); handleFailedTask mutates ctx
|
|
2479
|
+
// in-place and returns a SchedulerDecision.
|
|
2250
2480
|
const failedDecision = await handleFailedTask(ctx);
|
|
2251
2481
|
tasks = ctx.tasks;
|
|
2252
2482
|
manifest = ctx.manifest;
|
|
2253
|
-
if (failedDecision?.kind === "return")
|
|
2483
|
+
if (failedDecision?.kind === "return") {
|
|
2484
|
+
// #4 (completeness): route the failed-run short-circuit through
|
|
2485
|
+
// finalizeRun so the summary artifact + health snapshot + policy
|
|
2486
|
+
// decisions are written (previously bypassed — failed runs had no
|
|
2487
|
+
// closeout artifacts). handleFailedTask already set manifest status
|
|
2488
|
+
// to "failed" and saved tasks; finalizeRun re-derives the status
|
|
2489
|
+
// (still "failed" since a failed task exists — the `if (failed)`
|
|
2490
|
+
// branch in finalizeRun takes priority) and performs the closeout
|
|
2491
|
+
// writes. Policy-decision semantics are UNCHANGED — finalizeRun
|
|
2492
|
+
// uses identical logic for both the normal-completion and
|
|
2493
|
+
// failed-short-circuit paths.
|
|
2494
|
+
const failedResult = await finalizeRun(ctx);
|
|
2495
|
+
manifest = ctx.manifest;
|
|
2496
|
+
tasks = ctx.tasks;
|
|
2497
|
+
return failedResult;
|
|
2498
|
+
}
|
|
2254
2499
|
if (failedDecision?.kind === "continue") continue;
|
|
2255
2500
|
|
|
2256
|
-
// CORE-4 extraction 3: batch selection.
|
|
2257
|
-
//
|
|
2258
|
-
// decision (or return if the run
|
|
2259
|
-
// ctx.wfMachine back because
|
|
2260
|
-
|
|
2261
|
-
ctx.manifest = manifest;
|
|
2262
|
-
ctx.wfMachine = wfMachine;
|
|
2501
|
+
// CORE-4 extraction 3: batch selection. ctx is already synced from
|
|
2502
|
+
// the top-of-loop sync (RT-15); selectDispatchBatch computes the
|
|
2503
|
+
// ready batch and returns a dispatch decision (or return if the run
|
|
2504
|
+
// must block/abort). The caller syncs ctx.wfMachine back because
|
|
2505
|
+
// the function may advance phases.
|
|
2263
2506
|
const dispatchDecision = await selectDispatchBatch(ctx);
|
|
2264
2507
|
tasks = ctx.tasks;
|
|
2265
2508
|
manifest = ctx.manifest;
|
|
@@ -2271,9 +2514,7 @@ async function executeTeamRunCore(
|
|
|
2271
2514
|
// dispatch units, pre-warms the stable-prefix cache, and adds each
|
|
2272
2515
|
// unit's promise to ctx.pendingUnits (wrapped in executeWithRetry).
|
|
2273
2516
|
// The function mutates ctx.tasks (hook skips) and ctx.manifest (hook
|
|
2274
|
-
// status) in-place.
|
|
2275
|
-
ctx.tasks = tasks;
|
|
2276
|
-
ctx.manifest = manifest;
|
|
2517
|
+
// status) in-place. ctx is already synced from the top-of-loop sync.
|
|
2277
2518
|
await dispatchBatch(ctx, dispatchDecision);
|
|
2278
2519
|
tasks = ctx.tasks;
|
|
2279
2520
|
manifest = ctx.manifest;
|
|
@@ -2289,8 +2530,6 @@ async function executeTeamRunCore(
|
|
|
2289
2530
|
// ctx.pendingUnits, and records the merge outcome on ctx. Returns null
|
|
2290
2531
|
// (continue to phase/budget check) — a return decision is reserved for
|
|
2291
2532
|
// future run-complete/failure detection.
|
|
2292
|
-
ctx.tasks = tasks;
|
|
2293
|
-
ctx.manifest = manifest;
|
|
2294
2533
|
const mergeDecision = await mergeUnitResult(ctx);
|
|
2295
2534
|
tasks = ctx.tasks;
|
|
2296
2535
|
manifest = ctx.manifest;
|
|
@@ -2299,21 +2538,20 @@ async function executeTeamRunCore(
|
|
|
2299
2538
|
// (cancel-during-exec check + batch summary artifact).
|
|
2300
2539
|
const { taskIds: settledTaskIds, result: resultToMerge } = ctx.settledMerge!;
|
|
2301
2540
|
|
|
2302
|
-
// CORE-4 extraction 6: workflow phase advance.
|
|
2541
|
+
// CORE-4 extraction 6: workflow phase advance. ctx.wfMachine is
|
|
2542
|
+
// already synced from the top-of-loop sync (RT-15);
|
|
2303
2543
|
// advanceWorkflowPhases advances phases whose tasks are all terminal,
|
|
2304
2544
|
// emits phase_* events, and advances currentPhaseIndex. Mutates
|
|
2305
2545
|
// ctx.wfMachine in-place.
|
|
2306
|
-
ctx.wfMachine = wfMachine;
|
|
2307
2546
|
await advanceWorkflowPhases(ctx);
|
|
2308
2547
|
wfMachine = ctx.wfMachine;
|
|
2309
2548
|
|
|
2310
|
-
// CORE-4 extraction 7: budget enforcement.
|
|
2311
|
-
//
|
|
2312
|
-
//
|
|
2313
|
-
//
|
|
2314
|
-
//
|
|
2315
|
-
|
|
2316
|
-
ctx.manifest = manifest;
|
|
2549
|
+
// CORE-4 extraction 7: budget enforcement. ctx is already synced
|
|
2550
|
+
// from the top-of-loop sync (RT-15); enforceRunBudget checks
|
|
2551
|
+
// cumulative usage against warn/abort thresholds and a fair-share
|
|
2552
|
+
// heuristic. On abort it marks the run failed and returns
|
|
2553
|
+
// { kind: "return" }; otherwise emits warning events and returns
|
|
2554
|
+
// null (continue).
|
|
2317
2555
|
const budgetDecision = await enforceRunBudget(ctx);
|
|
2318
2556
|
tasks = ctx.tasks;
|
|
2319
2557
|
manifest = ctx.manifest;
|
|
@@ -2323,7 +2561,7 @@ async function executeTeamRunCore(
|
|
|
2323
2561
|
if (cancelledResult || input.signal?.aborted) {
|
|
2324
2562
|
const reason = input.signal?.aborted ? cancellationReasonFromSignal(input.signal) : undefined;
|
|
2325
2563
|
const message = reason?.message ?? cancelledResult?.manifest.summary ?? "Run cancelled during task execution.";
|
|
2326
|
-
manifest =
|
|
2564
|
+
manifest = setRunStatusRunning(manifest);
|
|
2327
2565
|
manifest = updateRunStatus(manifest, "cancelled", message);
|
|
2328
2566
|
// CANCEL-2: re-cancel non-terminal tasks here, mirroring the batch-loop
|
|
2329
2567
|
// cancel check at team-runner.ts:~925. A manifest cancelled mid-merge
|
|
@@ -2384,7 +2622,7 @@ async function executeTeamRunCore(
|
|
|
2384
2622
|
const completedBatch = tasks.filter((t) => settledTaskIds.includes(t.id));
|
|
2385
2623
|
const batchArtifact = writeArtifact(manifest.artifactsRoot, {
|
|
2386
2624
|
kind: "summary",
|
|
2387
|
-
relativePath: `batches/${settledTaskIds
|
|
2625
|
+
relativePath: `batches/${batchSummarySlug(settledTaskIds)}.md`,
|
|
2388
2626
|
producer: "team-runner",
|
|
2389
2627
|
content: aggregateTaskOutputs(completedBatch, manifest),
|
|
2390
2628
|
});
|
|
@@ -2416,6 +2654,9 @@ async function executeTeamRunCore(
|
|
|
2416
2654
|
tasks = ctx.tasks;
|
|
2417
2655
|
return finalResult;
|
|
2418
2656
|
} finally {
|
|
2657
|
+
// #3: drainPendingUnits returns settled outcomes, but the finally block
|
|
2658
|
+
// only needs the drain side-effect (abort + await + clear); the return
|
|
2659
|
+
// value is intentionally unused here.
|
|
2419
2660
|
await drainPendingUnits(pendingUnits, runController);
|
|
2420
2661
|
}
|
|
2421
2662
|
}
|