@tea-agent/loop-agent 0.1.0-fe-test.0
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 +114 -0
- package/CHANGELOG.md +2283 -0
- package/README.md +177 -0
- package/bin/agent-worker.js +22 -0
- package/bin/loop-agent.js +57 -0
- package/dist/adapters/aimax.js +91 -0
- package/dist/adapters/context.js +32 -0
- package/dist/adapters/index.js +29 -0
- package/dist/adapters/loop-agent.js +192 -0
- package/dist/adapters/types.js +1 -0
- package/dist/application/context-usage/skill-resolution-stats.js +263 -0
- package/dist/application/dag/args.js +486 -0
- package/dist/application/dag/generate-task-dag.js +408 -0
- package/dist/application/dag/report-dag.js +14 -0
- package/dist/application/dag/run-dag.js +109 -0
- package/dist/application/dag/validate-dag.js +166 -0
- package/dist/application/evaluation/alias.js +184 -0
- package/dist/application/evaluation/budget.js +192 -0
- package/dist/application/evaluation/campaign-hash.js +47 -0
- package/dist/application/evaluation/campaign-matrix.js +372 -0
- package/dist/application/evaluation/campaign-scorecard.js +135 -0
- package/dist/application/evaluation/campaign.js +370 -0
- package/dist/application/evaluation/candidate-hash.js +75 -0
- package/dist/application/evaluation/candidate.js +69 -0
- package/dist/application/evaluation/corpus-hash.js +38 -0
- package/dist/application/evaluation/corpus.js +56 -0
- package/dist/application/evaluation/experiment.js +294 -0
- package/dist/application/evaluation/ignition.js +198 -0
- package/dist/application/evaluation/integrity-audit.js +162 -0
- package/dist/application/evaluation/outer-loop.js +132 -0
- package/dist/application/evaluation/pi-cell-executor.js +39 -0
- package/dist/application/evaluation/private-verifier.js +46 -0
- package/dist/application/evaluation/promotion-policy.js +151 -0
- package/dist/application/evaluation/proposer.js +98 -0
- package/dist/application/evaluation/replay.js +289 -0
- package/dist/application/evaluation/types.js +652 -0
- package/dist/application/loop/run-action.js +19 -0
- package/dist/application/task-lifecycle/advance.js +1204 -0
- package/dist/application/task-lifecycle/gates.js +130 -0
- package/dist/application/task-lifecycle/index.js +7 -0
- package/dist/application/task-lifecycle/observe.js +424 -0
- package/dist/application/task-lifecycle/plan-transitions.js +251 -0
- package/dist/application/task-lifecycle/recommendations.js +180 -0
- package/dist/application/task-lifecycle/record.js +73 -0
- package/dist/application/task-lifecycle/types.js +1 -0
- package/dist/build-stamp.json +6 -0
- package/dist/cli/catalog.js +24 -0
- package/dist/cli/command-definitions.js +659 -0
- package/dist/cli/help.js +56 -0
- package/dist/cli/index.js +5 -0
- package/dist/cli/program.js +647 -0
- package/dist/cli/router.js +13 -0
- package/dist/cli/update/init-surface-notifier.js +167 -0
- package/dist/cli/update/notifier.js +117 -0
- package/dist/cli/update/npm-client.js +151 -0
- package/dist/cli/update/policy.js +92 -0
- package/dist/cli/update/runtime-activity.js +29 -0
- package/dist/cli/update/state.js +68 -0
- package/dist/cli-governance/active-residue-check.js +38 -0
- package/dist/cli.js +57 -0
- package/dist/commands/client-recovery.js +1373 -0
- package/dist/commands/closeout.js +13 -0
- package/dist/commands/coverage-audit.js +14 -0
- package/dist/commands/coverage-report.js +50 -0
- package/dist/commands/cursor-prompt.js +182 -0
- package/dist/commands/dag-approve.js +142 -0
- package/dist/commands/dag-final-verification.js +76 -0
- package/dist/commands/dag-init-hybrid.js +57 -0
- package/dist/commands/dag-reconcile-run.js +5 -0
- package/dist/commands/dag-reconcile-tasks.js +51 -0
- package/dist/commands/dag-reject.js +91 -0
- package/dist/commands/dag-report.js +76 -0
- package/dist/commands/dag-request-interrupt.js +20 -0
- package/dist/commands/dag-rerun-task.js +19 -0
- package/dist/commands/dag-rerun.js +111 -0
- package/dist/commands/dag-resume.js +35 -0
- package/dist/commands/dag-run-task.js +12 -0
- package/dist/commands/dag-validate.js +20 -0
- package/dist/commands/dag-workflow-compile.js +91 -0
- package/dist/commands/dag-workflow-plan.js +130 -0
- package/dist/commands/dag-workflow-validate.js +66 -0
- package/dist/commands/delegate.js +130 -0
- package/dist/commands/docs-archive.js +5 -0
- package/dist/commands/docs-audit.js +6 -0
- package/dist/commands/doctor.js +283 -0
- package/dist/commands/eval.js +1398 -0
- package/dist/commands/examples.js +90 -0
- package/dist/commands/goal.js +92 -0
- package/dist/commands/handoff-check.js +5 -0
- package/dist/commands/harvest.js +44 -0
- package/dist/commands/import-prd.js +81 -0
- package/dist/commands/init-upgrade.js +3085 -0
- package/dist/commands/init.js +3630 -0
- package/dist/commands/inspect.js +11 -0
- package/dist/commands/instructions.js +223 -0
- package/dist/commands/knowledge.js +162 -0
- package/dist/commands/loop-benchmark.js +72 -0
- package/dist/commands/loop.js +251 -0
- package/dist/commands/new-task.js +5 -0
- package/dist/commands/operator.js +44 -0
- package/dist/commands/pi-prompt.js +174 -0
- package/dist/commands/pi-reuse-benchmark.js +153 -0
- package/dist/commands/plan-list.js +5 -0
- package/dist/commands/plan.js +50 -0
- package/dist/commands/promote-run.js +29 -0
- package/dist/commands/reference-index.js +16 -0
- package/dist/commands/run-dag-progress.js +123 -0
- package/dist/commands/run-dag.js +22 -0
- package/dist/commands/spine.js +38 -0
- package/dist/commands/stats.js +113 -0
- package/dist/commands/status.js +57 -0
- package/dist/commands/study-init.js +192 -0
- package/dist/commands/task-advance.js +366 -0
- package/dist/commands/task-contract.js +269 -0
- package/dist/commands/task-source-prepare.js +479 -0
- package/dist/commands/task-status.js +133 -0
- package/dist/commands/workflow.js +259 -0
- package/dist/commands/worktree-create.js +31 -0
- package/dist/commands/worktree-list.js +5 -0
- package/dist/commands/worktree-remove.js +26 -0
- package/dist/executors/config-core.js +5 -0
- package/dist/executors/config.js +2 -0
- package/dist/executors/dag-pi-executor.js +1277 -0
- package/dist/executors/dag-static-executor.js +42 -0
- package/dist/executors/dag.js +3 -0
- package/dist/executors/index.js +6 -0
- package/dist/executors/model-routing.js +71 -0
- package/dist/executors/pi-defaults.js +9 -0
- package/dist/executors/pi-event-serializer.js +65 -0
- package/dist/executors/pi-executor.js +981 -0
- package/dist/executors/pi-playwright-cli-tool.js +1006 -0
- package/dist/executors/pi-prompt-transport.js +198 -0
- package/dist/executors/pi-reuse-benchmark.js +316 -0
- package/dist/executors/pi-runtime-reuse.js +29 -0
- package/dist/executors/pi-sdk-executor.js +722 -0
- package/dist/executors/pi-sdk.js +1 -0
- package/dist/executors/pi-writer-tool-policy.js +266 -0
- package/dist/executors/pi.js +3 -0
- package/dist/executors/playwright-cli-launcher.js +63 -0
- package/dist/executors/process-tree.js +33 -0
- package/dist/executors/shell-executor.js +3055 -0
- package/dist/executors/shell-presets.js +99 -0
- package/dist/executors/shell-verification.js +262 -0
- package/dist/executors/shell-write-guard.js +543 -0
- package/dist/executors/shell.js +3 -0
- package/dist/executors/static.js +1 -0
- package/dist/governance/checks.js +437 -0
- package/dist/governance/document-index-closure.js +164 -0
- package/dist/governance/exec-plans.js +549 -0
- package/dist/governance/harness.js +9 -0
- package/dist/governance/index.js +3 -0
- package/dist/governance/manifest-types.js +254 -0
- package/dist/governance/manifest.js +2 -0
- package/dist/governance/path-guard.js +69 -0
- package/dist/governance/path-guards.js +2 -0
- package/dist/governance/profiles.js +3 -0
- package/dist/governance/requirement-coverage.js +425 -0
- package/dist/governance/skill-safety.js +135 -0
- package/dist/governance/spine-audit.js +155 -0
- package/dist/infrastructure/evaluation/alias-store.js +199 -0
- package/dist/infrastructure/evaluation/campaign-store.js +154 -0
- package/dist/infrastructure/evaluation/candidate-store.js +439 -0
- package/dist/infrastructure/evaluation/corpus-store.js +181 -0
- package/dist/infrastructure/evaluation/experiment-store.js +124 -0
- package/dist/infrastructure/evaluation/ignition-store.js +82 -0
- package/dist/infrastructure/evaluation/private-verifier-store.js +145 -0
- package/dist/infrastructure/evaluation/proposer-store.js +78 -0
- package/dist/infrastructure/evaluation/store.js +40 -0
- package/dist/infrastructure/harness/active-residue-policy.js +73 -0
- package/dist/infrastructure/harness/artifact-store.js +72 -0
- package/dist/infrastructure/harness/atomic-write.js +50 -0
- package/dist/infrastructure/harness/completed-facts-guard.js +40 -0
- package/dist/infrastructure/harness/loop-action-store.js +20 -0
- package/dist/infrastructure/harness/loop-store.js +41 -0
- package/dist/infrastructure/harness/one-shot-run-store.js +94 -0
- package/dist/infrastructure/harness/task-store.js +108 -0
- package/dist/records/closeout.js +2 -0
- package/dist/records/harvest.js +215 -0
- package/dist/records/index.js +3 -0
- package/dist/records/one-shot-runs.js +386 -0
- package/dist/records/promotion.js +200 -0
- package/dist/shared/artifacts-core.js +107 -0
- package/dist/shared/artifacts.js +2 -0
- package/dist/shared/context-files.js +32 -0
- package/dist/shared/context.js +2 -0
- package/dist/shared/copy-dir.js +17 -0
- package/dist/shared/git-progress.js +172 -0
- package/dist/shared/index.js +5 -0
- package/dist/shared/logger.js +17 -0
- package/dist/shared/one-shot-prompt-args.js +98 -0
- package/dist/shared/openspec-spec.js +125 -0
- package/dist/shared/operator/capabilities.js +2659 -0
- package/dist/shared/operator/command-lifecycle.js +94 -0
- package/dist/shared/operator/envelope.js +59 -0
- package/dist/shared/operator/index.js +5 -0
- package/dist/shared/operator/registry.js +38 -0
- package/dist/shared/operator/types.js +5 -0
- package/dist/shared/output-truncation.js +37 -0
- package/dist/shared/package-metadata.js +530 -0
- package/dist/shared/path-refs.js +31 -0
- package/dist/shared/pi-retry-settings.js +23 -0
- package/dist/shared/playwright-cli-command-policy.js +41 -0
- package/dist/shared/preview.js +39 -0
- package/dist/shared/prompts.js +26 -0
- package/dist/shared/reference-context.js +264 -0
- package/dist/shared/resilient-git.js +133 -0
- package/dist/shared/runtime-activity.js +6 -0
- package/dist/shared/timeout-policy.js +24 -0
- package/dist/shared/timeout.js +1 -0
- package/dist/shared/types.js +5 -0
- package/dist/sidecars/cursor-prompt/executor.js +428 -0
- package/dist/sidecars/cursor-prompt/index.js +3 -0
- package/dist/sidecars/cursor-prompt/stream.js +121 -0
- package/dist/task/config-types.js +235 -0
- package/dist/task/config.js +2 -0
- package/dist/task/contract/adopt.js +166 -0
- package/dist/task/contract/apply.js +326 -0
- package/dist/task/contract/canonicalize.js +60 -0
- package/dist/task/contract/constants.js +30 -0
- package/dist/task/contract/diff.js +177 -0
- package/dist/task/contract/hash.js +42 -0
- package/dist/task/contract/import-revision.js +96 -0
- package/dist/task/contract/index.js +17 -0
- package/dist/task/contract/journal.js +155 -0
- package/dist/task/contract/lock.js +153 -0
- package/dist/task/contract/observe.js +296 -0
- package/dist/task/contract/paths.js +19 -0
- package/dist/task/contract/project.js +185 -0
- package/dist/task/contract/recover.js +312 -0
- package/dist/task/contract/request-ledger.js +37 -0
- package/dist/task/contract/schema.js +153 -0
- package/dist/task/contract/transaction.js +160 -0
- package/dist/task/contract/types.js +1 -0
- package/dist/task/contract/validate-draft.js +106 -0
- package/dist/task/dag-source-paths.js +50 -0
- package/dist/task/delegate.js +208 -0
- package/dist/task/frontend-preflight.js +131 -0
- package/dist/task/frontend-project-capability.js +570 -0
- package/dist/task/goal-audit.js +51 -0
- package/dist/task/goal-policy.js +8 -0
- package/dist/task/goal.js +3 -0
- package/dist/task/ids.js +1 -0
- package/dist/task/index.js +12 -0
- package/dist/task/lifecycle.js +1 -0
- package/dist/task/operator/capabilities.js +6 -0
- package/dist/task/operator/envelope.js +2 -0
- package/dist/task/operator/index.js +5 -0
- package/dist/task/operator/registry.js +2 -0
- package/dist/task/operator/types.js +1 -0
- package/dist/task/paths.js +1 -0
- package/dist/task/read-model.js +140 -0
- package/dist/task/runtime.js +708 -0
- package/dist/task/source-prepare/artifact-meta.js +137 -0
- package/dist/task/source-prepare/build-draft.js +232 -0
- package/dist/task/source-prepare/completeness.js +248 -0
- package/dist/task/source-prepare/index.js +10 -0
- package/dist/task/source-prepare/parse-intent.js +496 -0
- package/dist/task/source-prepare/path-policy.js +197 -0
- package/dist/task/source-prepare/placeholder-paths.js +77 -0
- package/dist/task/source-prepare/prepare.js +920 -0
- package/dist/task/source-prepare/reference-integrity.js +290 -0
- package/dist/task/source-prepare/semantic-intake.js +717 -0
- package/dist/task/source-prepare/types.js +7 -0
- package/dist/task/source-references.js +228 -0
- package/dist/task/source-state.js +1 -0
- package/dist/task/state.js +41 -0
- package/dist/task/subagent-guidance.js +1 -0
- package/dist/task/task-demand-routing.js +443 -0
- package/dist/task/workflow-state-types.js +92 -0
- package/dist/task/worktree-cleanup.js +140 -0
- package/dist/task/worktree.js +388 -0
- package/dist/verification/maven/cache.js +142 -0
- package/dist/verification/maven/index.js +120 -0
- package/dist/verification/maven/plan-commands.js +421 -0
- package/dist/verification/maven/pom-static.js +136 -0
- package/dist/verification/maven/scope-resolve.js +153 -0
- package/dist/verification/maven/stale.js +130 -0
- package/dist/verification/maven/types.js +23 -0
- package/dist/verification/maven/workspace-graph.js +322 -0
- package/dist/worker/cli.js +957 -0
- package/dist/worker/closeout/apply.js +73 -0
- package/dist/worker/closeout/preview.js +30 -0
- package/dist/worker/console/app-data.js +316 -0
- package/dist/worker/console/chat/artifact-card.js +30 -0
- package/dist/worker/console/chat/assistant-content.js +121 -0
- package/dist/worker/console/chat/chat-event-store.js +981 -0
- package/dist/worker/console/chat/chat-ui-policy.js +25 -0
- package/dist/worker/console/chat/compaction-errors.js +64 -0
- package/dist/worker/console/chat/composer-draft-store.js +45 -0
- package/dist/worker/console/chat/context-panel.js +54 -0
- package/dist/worker/console/chat/contract-apply-receipt-store.js +174 -0
- package/dist/worker/console/chat/explore-tools.js +299 -0
- package/dist/worker/console/chat/human-gate-card.js +45 -0
- package/dist/worker/console/chat/interview-adapter.js +149 -0
- package/dist/worker/console/chat/mcp-inventory.js +221 -0
- package/dist/worker/console/chat/model-resolver.js +215 -0
- package/dist/worker/console/chat/operation-card.js +92 -0
- package/dist/worker/console/chat/pi-console-config.js +376 -0
- package/dist/worker/console/chat/pi-runtime.js +2446 -0
- package/dist/worker/console/chat/repo-browser.js +140 -0
- package/dist/worker/console/chat/repo-walk.js +125 -0
- package/dist/worker/console/chat/resource-loader.js +59 -0
- package/dist/worker/console/chat/resource-preferences-store.js +152 -0
- package/dist/worker/console/chat/routes.js +3137 -0
- package/dist/worker/console/chat/runtime-context.js +102 -0
- package/dist/worker/console/chat/runtime-selection.js +96 -0
- package/dist/worker/console/chat/semantic-activity.js +489 -0
- package/dist/worker/console/chat/session-store.js +484 -0
- package/dist/worker/console/chat/shortcuts.js +217 -0
- package/dist/worker/console/chat/tool-adapter.js +131 -0
- package/dist/worker/console/chat/tool-preview.js +247 -0
- package/dist/worker/console/chat/tools.js +212 -0
- package/dist/worker/console/chat/turn-execution-registry.js +82 -0
- package/dist/worker/console/chat/turn-process.js +346 -0
- package/dist/worker/console/chat/usage.js +165 -0
- package/dist/worker/console/chat/workspace-landing.js +103 -0
- package/dist/worker/console/dag-confirmation.js +347 -0
- package/dist/worker/console/dag-execution-receipt.js +393 -0
- package/dist/worker/console/doctor.js +230 -0
- package/dist/worker/console/draft-store.js +158 -0
- package/dist/worker/console/harness-pi-model-matrix.js +137 -0
- package/dist/worker/console/human-gate-token.js +130 -0
- package/dist/worker/console/index.js +17 -0
- package/dist/worker/console/inspect-split.js +82 -0
- package/dist/worker/console/interview/assessment.js +67 -0
- package/dist/worker/console/interview/grill-me.js +277 -0
- package/dist/worker/console/interview/session.js +100 -0
- package/dist/worker/console/interview/tools.js +109 -0
- package/dist/worker/console/loopback.js +16 -0
- package/dist/worker/console/mutation-gate-receipt-store.js +184 -0
- package/dist/worker/console/night-aux-ticker.js +146 -0
- package/dist/worker/console/observe-health-match.js +179 -0
- package/dist/worker/console/observe-link.js +42 -0
- package/dist/worker/console/open-browser.js +134 -0
- package/dist/worker/console/operation-run-facts.js +190 -0
- package/dist/worker/console/operation-runner.js +353 -0
- package/dist/worker/console/operation-sse.js +184 -0
- package/dist/worker/console/operation-store.js +169 -0
- package/dist/worker/console/operation-wait.js +355 -0
- package/dist/worker/console/operator-actions.js +3768 -0
- package/dist/worker/console/operator-selection.js +105 -0
- package/dist/worker/console/operator-surface-health.js +24 -0
- package/dist/worker/console/operator-user-error.js +45 -0
- package/dist/worker/console/pi-readiness.js +288 -0
- package/dist/worker/console/prd-identity.js +102 -0
- package/dist/worker/console/prd-intake-bridge.js +441 -0
- package/dist/worker/console/prd-reference-discovery.js +124 -0
- package/dist/worker/console/recovery-cta.js +320 -0
- package/dist/worker/console/recovery-error-copy.js +198 -0
- package/dist/worker/console/recovery-selection.js +107 -0
- package/dist/worker/console/repo-fingerprint.js +35 -0
- package/dist/worker/console/resolve-dag-run-for-task.js +115 -0
- package/dist/worker/console/resource-loader.js +95 -0
- package/dist/worker/console/routes.js +475 -0
- package/dist/worker/console/security.js +170 -0
- package/dist/worker/console/server.js +357 -0
- package/dist/worker/console/sibling-controller.js +33 -0
- package/dist/worker/console/static/assets/abnfDiagram-N423BO3Z-BmOEx0gA.js +1 -0
- package/dist/worker/console/static/assets/arc-BGiVVe8b.js +1 -0
- package/dist/worker/console/static/assets/architectureDiagram-T3A2C74G-nDdu4rgJ.js +36 -0
- package/dist/worker/console/static/assets/blockDiagram-VBNYF7ZC-Bb5u0BWf.js +132 -0
- package/dist/worker/console/static/assets/c4Diagram-5PPSVZJV-BlhC0j94.js +10 -0
- package/dist/worker/console/static/assets/channel-Tl-FkUqr.js +1 -0
- package/dist/worker/console/static/assets/chunk-2GRJ4B5K-Ai8_qQXI.js +1 -0
- package/dist/worker/console/static/assets/chunk-2Q5K7J3B-B9gyCjqX.js +1 -0
- package/dist/worker/console/static/assets/chunk-5RXB4S5H-Bzbj3zGH.js +231 -0
- package/dist/worker/console/static/assets/chunk-5VM5RSS4-BT415PvT.js +15 -0
- package/dist/worker/console/static/assets/chunk-6Q2QTUOP-D_A5_5QL.js +88 -0
- package/dist/worker/console/static/assets/chunk-GF5L2VYU-srJkaP3f.js +206 -0
- package/dist/worker/console/static/assets/chunk-JWPE2WC7-UYGvEP4l.js +1 -0
- package/dist/worker/console/static/assets/chunk-KBJHAD2P-B8luBrrz.js +1 -0
- package/dist/worker/console/static/assets/chunk-RYQCIY6F-DN7wXZSq.js +1 -0
- package/dist/worker/console/static/assets/chunk-XXDRQBXY-DXKyMuKS.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-BaehtRk4.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-BaehtRk4.js +1 -0
- package/dist/worker/console/static/assets/cose-bilkent-JH36ORCC-dqvoynXj.js +1 -0
- package/dist/worker/console/static/assets/cynefin-VYW2F7L2--PcyL34A.js +166 -0
- package/dist/worker/console/static/assets/cynefinDiagram-MW4NZA55-DvWs_hTY.js +62 -0
- package/dist/worker/console/static/assets/cytoscape.esm-yzknjiTM.js +321 -0
- package/dist/worker/console/static/assets/dagre-VZM6K2ZE-DxeIxbyO.js +4 -0
- package/dist/worker/console/static/assets/defaultLocale-DX6XiGOO.js +1 -0
- package/dist/worker/console/static/assets/diagram-7IWD3JNH-DPCG61Of.js +30 -0
- package/dist/worker/console/static/assets/diagram-B4RE2ZJO-CmuU50-U.js +3 -0
- package/dist/worker/console/static/assets/diagram-LBJQPF4R-B61XLD_v.js +24 -0
- package/dist/worker/console/static/assets/diagram-Q27KOJAE-BB7HTRRY.js +24 -0
- package/dist/worker/console/static/assets/diagram-UB23O5K3-DvkboAIp.js +41 -0
- package/dist/worker/console/static/assets/ebnfDiagram-BXEA7PRR-BdJ_y1fL.js +1 -0
- package/dist/worker/console/static/assets/erDiagram-JOGREHBK-9bUBZTRi.js +85 -0
- package/dist/worker/console/static/assets/flowDiagram-UKHOOZJN-Dw4L4RBa.js +156 -0
- package/dist/worker/console/static/assets/ganttDiagram-PKOTCBZU-Jr82N_QP.js +292 -0
- package/dist/worker/console/static/assets/gitGraphDiagram-DS77QQ5N-DAz9wFLr.js +106 -0
- package/dist/worker/console/static/assets/graph-DOmOIIwC.js +1 -0
- package/dist/worker/console/static/assets/index-B7Y-0g3J.css +1 -0
- package/dist/worker/console/static/assets/index-DkCrqaYg.js +325 -0
- package/dist/worker/console/static/assets/infoDiagram-6WML65LV-CBB5Np7y.js +2 -0
- package/dist/worker/console/static/assets/init-Gi6I4Gst.js +1 -0
- package/dist/worker/console/static/assets/ishikawaDiagram-WSZJBQD7-D8IOp94y.js +70 -0
- package/dist/worker/console/static/assets/journeyDiagram-NVQOT4AX-Dgd3AHGG.js +139 -0
- package/dist/worker/console/static/assets/kanban-definition-27J2QSJJ-BmYOxLE9.js +89 -0
- package/dist/worker/console/static/assets/layout-D-LzfAck.js +1 -0
- package/dist/worker/console/static/assets/linear-DfYTEF6J.js +1 -0
- package/dist/worker/console/static/assets/map-DxJ2ADlA.js +1 -0
- package/dist/worker/console/static/assets/mermaid.core-BjuFPRqa.js +308 -0
- package/dist/worker/console/static/assets/mindmap-definition-FAOFIHXS-CLyUViGd.js +96 -0
- package/dist/worker/console/static/assets/ordinal-Cboi1Yqb.js +1 -0
- package/dist/worker/console/static/assets/pegDiagram-VL7TDLO6-BvX2Di4-.js +1 -0
- package/dist/worker/console/static/assets/pieDiagram-7S7Q4E2Y-BFj0_OLv.js +39 -0
- package/dist/worker/console/static/assets/quadrantDiagram-CIZ2JOQS-BIUrD_hx.js +7 -0
- package/dist/worker/console/static/assets/railroadDiagram-AXF67PYL-CHBB48fj.js +1 -0
- package/dist/worker/console/static/assets/requirementDiagram-LRYGKXZP-sJrYw2Sf.js +84 -0
- package/dist/worker/console/static/assets/sankeyDiagram-W5VNT64P-BzHH6fPR.js +40 -0
- package/dist/worker/console/static/assets/sequenceDiagram-SI44F4Z6-7QCnelCt.js +162 -0
- package/dist/worker/console/static/assets/sizeCapture-X5ZJPWSS-sDYSxn72.js +1 -0
- package/dist/worker/console/static/assets/stateDiagram-OKZ733FA-XpiUdwzm.js +1 -0
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-eEhq2A8Y.js +1 -0
- package/dist/worker/console/static/assets/swimlanes-SLNWSIFB-DvqgNPzD.js +2 -0
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-la8AC7fo.js +8 -0
- package/dist/worker/console/static/assets/timeline-definition-Z64GVDOM-2TyOYNx0.js +120 -0
- package/dist/worker/console/static/assets/vennDiagram-T6HMQDX7-B-nM6wM2.js +34 -0
- package/dist/worker/console/static/assets/wardleyDiagram-T6FBY63Y-B86wPdbr.js +78 -0
- package/dist/worker/console/static/assets/xychartDiagram-ELKLHX3M-Dr1C4AQz.js +7 -0
- package/dist/worker/console/static/favicon.svg +37 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_AMS-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Caligraphic-Bold.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Caligraphic-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Fraktur-Bold.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Fraktur-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Main-Bold.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Main-BoldItalic.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Main-Italic.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Main-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Math-BoldItalic.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Math-Italic.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_SansSerif-Bold.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_SansSerif-Italic.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_SansSerif-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Script-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Size1-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Size2-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Size3-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Size4-Regular.woff2 +0 -0
- package/dist/worker/console/static/fonts/katex/KaTeX_Typewriter-Regular.woff2 +0 -0
- package/dist/worker/console/static/index.html +15 -0
- package/dist/worker/console/static-src/app/console-types.js +184 -0
- package/dist/worker/console/static-src/app/useConsoleShell.js +101 -0
- package/dist/worker/console/static-src/app/useOperatorActions.js +324 -0
- package/dist/worker/console/static-src/app/usePrdImport.js +172 -0
- package/dist/worker/console/static-src/app/useRecoveryActions.js +289 -0
- package/dist/worker/console/static-src/app/useRecoveryConsole.js +425 -0
- package/dist/worker/console/static-src/app/useTaskWizard.js +296 -0
- package/dist/worker/console/static-src/chat-markdown-security.js +38 -0
- package/dist/worker/console/static-src/chat-view-types.js +2 -0
- package/dist/worker/console/static-src/night/night-types.js +24 -0
- package/dist/worker/console/static-src/night/useNightBoard.js +94 -0
- package/dist/worker/console/static-src/night/useNightWizard.js +171 -0
- package/dist/worker/console/static-src/night-prepare-result.js +118 -0
- package/dist/worker/console/static-src/operator-chat/activity-journey.js +125 -0
- package/dist/worker/console/static-src/operator-chat/activity-rail-presentation.js +73 -0
- package/dist/worker/console/static-src/operator-chat/activity-references.js +20 -0
- package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +420 -0
- package/dist/worker/console/static-src/operator-chat/format.js +71 -0
- package/dist/worker/console/static-src/operator-chat/landing-density.js +23 -0
- package/dist/worker/console/static-src/operator-chat/mutation-gate.js +45 -0
- package/dist/worker/console/static-src/operator-chat/refs.js +86 -0
- package/dist/worker/console/static-src/operator-chat/resource-auto-invocation.js +91 -0
- package/dist/worker/console/static-src/operator-chat/resource-diagnostics.js +204 -0
- package/dist/worker/console/static-src/operator-chat/runtime-snapshot-store.js +257 -0
- package/dist/worker/console/static-src/operator-chat/session-title-watcher.js +128 -0
- package/dist/worker/console/static-src/operator-chat/sidebar-split.js +90 -0
- package/dist/worker/console/static-src/operator-chat/slash-palette-layout.js +24 -0
- package/dist/worker/console/static-src/operator-chat/slash-palette-nav.js +141 -0
- package/dist/worker/console/static-src/operator-chat/spatial-overlay.js +38 -0
- package/dist/worker/console/static-src/operator-chat/tools-catalog.js +77 -0
- package/dist/worker/console/static-src/operator-chat/turn-stream-controller.js +690 -0
- package/dist/worker/console/static-src/operator-chat/turn-submission.js +158 -0
- package/dist/worker/console/static-src/operator-chat/types.js +1 -0
- package/dist/worker/console/static-src/operator-chat/useActivityRailTransition.js +59 -0
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +495 -0
- package/dist/worker/console/static-src/operator-chat/useChatStream.js +780 -0
- package/dist/worker/console/static-src/operator-chat/useChatThread.js +280 -0
- package/dist/worker/console/static-src/operator-chat/useComposer.js +257 -0
- package/dist/worker/console/static-src/operator-chat/useInterview.js +108 -0
- package/dist/worker/console/static-src/operator-chat/useOverlayFocus.js +84 -0
- package/dist/worker/console/static-src/operator-chat/useRepoBrowser.js +148 -0
- package/dist/worker/console/static-src/operator-chat/useRuntimeControls.js +358 -0
- package/dist/worker/console/static-src/operator-chat/useRuntimeSnapshot.js +196 -0
- package/dist/worker/console/static-src/operator-chat/useWorkspaceLayout.js +58 -0
- package/dist/worker/console/static-src/operator-chat/workspace-layout-mode.js +35 -0
- package/dist/worker/console/static-src/prd-file-import.js +218 -0
- package/dist/worker/console/vite.config.js +27 -0
- package/dist/worker/console/workflow-kinds.js +46 -0
- package/dist/worker/delivery/final-verification.js +302 -0
- package/dist/worker/delivery/git-transaction.js +497 -0
- package/dist/worker/delivery/package.js +537 -0
- package/dist/worker/delivery/verification-bundle.js +544 -0
- package/dist/worker/feature/acceptance-policy.js +227 -0
- package/dist/worker/feature/advance.js +301 -0
- package/dist/worker/feature/decision-loader.js +99 -0
- package/dist/worker/feature/discover.js +14 -0
- package/dist/worker/feature/doctor.js +223 -0
- package/dist/worker/feature/fullstack-validate.js +337 -0
- package/dist/worker/feature/next-action.js +140 -0
- package/dist/worker/feature/profile-schema.js +47 -0
- package/dist/worker/feature/ready-plan-projection.js +82 -0
- package/dist/worker/feature/reducer.js +137 -0
- package/dist/worker/feature/review.js +678 -0
- package/dist/worker/feature/run.js +390 -0
- package/dist/worker/feature/scaffold.js +812 -0
- package/dist/worker/feature/types.js +1 -0
- package/dist/worker/follow-up/approve.js +273 -0
- package/dist/worker/follow-up/factory.js +234 -0
- package/dist/worker/follow-up/paths.js +25 -0
- package/dist/worker/follow-up/policy.js +26 -0
- package/dist/worker/follow-up/schema.js +93 -0
- package/dist/worker/follow-up/store.js +96 -0
- package/dist/worker/loop-agent/command-result.js +1 -0
- package/dist/worker/loop-agent/controller-protocol.js +143 -0
- package/dist/worker/loop-agent/loop-agent-client.js +489 -0
- package/dist/worker/loop-agent/parse-json.js +14 -0
- package/dist/worker/materialize/harness-task-lifecycle-probe.js +126 -0
- package/dist/worker/materialize/harness-task-lineage.js +220 -0
- package/dist/worker/materialize/harness-task-materializer.js +648 -0
- package/dist/worker/metrics/projector.js +139 -0
- package/dist/worker/observability/dag-execution-trajectory.js +591 -0
- package/dist/worker/observability/event-history.js +216 -0
- package/dist/worker/observability/event-store.js +83 -0
- package/dist/worker/observability/events.js +79 -0
- package/dist/worker/observability/interrupt-eligibility.js +264 -0
- package/dist/worker/observability/progress-composite.js +34 -0
- package/dist/worker/observability/read-model.js +2499 -0
- package/dist/worker/observability/snapshot-store.js +43 -0
- package/dist/worker/observability/types.js +1 -0
- package/dist/worker/observe/dag-node-execution-output.js +180 -0
- package/dist/worker/observe/dag-run-artifacts.js +90 -0
- package/dist/worker/observe/health.js +58 -0
- package/dist/worker/observe/night-jobs.js +104 -0
- package/dist/worker/observe/node-input.js +441 -0
- package/dist/worker/observe/paths.js +174 -0
- package/dist/worker/observe/routes.js +1077 -0
- package/dist/worker/observe/server.js +117 -0
- package/dist/worker/observe/spec-evidence.js +398 -0
- package/dist/worker/observe/static/api.js +117 -0
- package/dist/worker/observe/static/app.js +169 -0
- package/dist/worker/observe/static/constants.js +182 -0
- package/dist/worker/observe/static/copy.js +67 -0
- package/dist/worker/observe/static/custom-select.js +567 -0
- package/dist/worker/observe/static/dag-edge-routing.js +368 -0
- package/dist/worker/observe/static/dag-helpers.js +161 -0
- package/dist/worker/observe/static/dag-history-labels.js +95 -0
- package/dist/worker/observe/static/dag-layout.d.ts +36 -0
- package/dist/worker/observe/static/dag-layout.js +163 -0
- package/dist/worker/observe/static/dag-model.js +145 -0
- package/dist/worker/observe/static/dom.js +220 -0
- package/dist/worker/observe/static/favicon.svg +37 -0
- package/dist/worker/observe/static/format-pool.d.ts +71 -0
- package/dist/worker/observe/static/format-pool.js +153 -0
- package/dist/worker/observe/static/format.js +347 -0
- package/dist/worker/observe/static/index.html +409 -0
- package/dist/worker/observe/static/kpi.js +78 -0
- package/dist/worker/observe/static/markdown-render.js +124 -0
- package/dist/worker/observe/static/operator-chrome.css +517 -0
- package/dist/worker/observe/static/operator-chrome.d.ts +83 -0
- package/dist/worker/observe/static/operator-chrome.js +623 -0
- package/dist/worker/observe/static/relations.js +133 -0
- package/dist/worker/observe/static/router.js +156 -0
- package/dist/worker/observe/static/run-processing.js +148 -0
- package/dist/worker/observe/static/shell-chrome.js +65 -0
- package/dist/worker/observe/static/state.js +496 -0
- package/dist/worker/observe/static/styles.css +3567 -0
- package/dist/worker/observe/static/views/batch.js +227 -0
- package/dist/worker/observe/static/views/dag-graph.js +508 -0
- package/dist/worker/observe/static/views/dag-inspector.js +1722 -0
- package/dist/worker/observe/static/views/dag-trajectory.js +313 -0
- package/dist/worker/observe/static/views/dag.js +483 -0
- package/dist/worker/observe/static/views/dags.js +877 -0
- package/dist/worker/observe/static/views/dashboard.js +747 -0
- package/dist/worker/observe/static/views/failures.js +143 -0
- package/dist/worker/observe/static/views/feature.js +492 -0
- package/dist/worker/observe/static/views/night.js +201 -0
- package/dist/worker/observe/static/views/pool.js +708 -0
- package/dist/worker/observe/static/views/run.js +453 -0
- package/dist/worker/observe/static/views/session-timeline.js +771 -0
- package/dist/worker/observe/static/views/shell.js +7 -0
- package/dist/worker/observe/static/views/task.js +315 -0
- package/dist/worker/observe/static/views/timeline.js +163 -0
- package/dist/worker/outcomes/adapters.js +153 -0
- package/dist/worker/outcomes/declared-artifacts.js +103 -0
- package/dist/worker/outcomes/evidence-tokens.js +29 -0
- package/dist/worker/outcomes/gate.js +40 -0
- package/dist/worker/outcomes/projector.js +227 -0
- package/dist/worker/outcomes/registry.js +1 -0
- package/dist/worker/outcomes/store.js +131 -0
- package/dist/worker/outcomes/types.js +79 -0
- package/dist/worker/pool/attempt-identity.js +41 -0
- package/dist/worker/pool/attempt-lease.js +184 -0
- package/dist/worker/pool/attempt-transition.js +210 -0
- package/dist/worker/pool/begin-attempt-with-lease.js +26 -0
- package/dist/worker/pool/begin-attempt.js +35 -0
- package/dist/worker/pool/doctor.js +165 -0
- package/dist/worker/pool/failure-routing.js +182 -0
- package/dist/worker/pool/migrate-state.js +303 -0
- package/dist/worker/pool/reconcile.js +285 -0
- package/dist/worker/pool/recovery-decision.js +163 -0
- package/dist/worker/pool/run-owner-store.js +126 -0
- package/dist/worker/pool/run-store.js +360 -0
- package/dist/worker/pool/runtime-reconcile-inventory.js +127 -0
- package/dist/worker/pool/state-projection.js +57 -0
- package/dist/worker/pool/types.js +17 -0
- package/dist/worker/pool/validation.js +144 -0
- package/dist/worker/preflight.js +157 -0
- package/dist/worker/profile-mapping.js +76 -0
- package/dist/worker/progress-reporter.js +63 -0
- package/dist/worker/report/morning-report.js +155 -0
- package/dist/worker/repos/repo-resolver.js +23 -0
- package/dist/worker/run-task/execute-prepared-task.js +178 -0
- package/dist/worker/run-task/run-task.js +912 -0
- package/dist/worker/runner/run-ready.js +561 -0
- package/dist/worker/runner/single-task-attempt.js +176 -0
- package/dist/worker/scheduler/admission.js +547 -0
- package/dist/worker/scheduler/auto-followup.js +99 -0
- package/dist/worker/scheduler/cli.js +644 -0
- package/dist/worker/scheduler/clock-install/darwin-launchd.js +238 -0
- package/dist/worker/scheduler/clock-install/linux-systemd-user.js +224 -0
- package/dist/worker/scheduler/clock-install/types.js +1 -0
- package/dist/worker/scheduler/clock-install/win32-schtasks.js +227 -0
- package/dist/worker/scheduler/clock-install.js +284 -0
- package/dist/worker/scheduler/clock.js +420 -0
- package/dist/worker/scheduler/dispatcher.js +503 -0
- package/dist/worker/scheduler/doctor.js +431 -0
- package/dist/worker/scheduler/evidence.js +170 -0
- package/dist/worker/scheduler/git-base.js +90 -0
- package/dist/worker/scheduler/index.js +25 -0
- package/dist/worker/scheduler/lease.js +114 -0
- package/dist/worker/scheduler/lifecycle.js +348 -0
- package/dist/worker/scheduler/lock.js +80 -0
- package/dist/worker/scheduler/morning-window.js +191 -0
- package/dist/worker/scheduler/night-git-finalizer.js +114 -0
- package/dist/worker/scheduler/night-harvest.js +412 -0
- package/dist/worker/scheduler/paths.js +92 -0
- package/dist/worker/scheduler/prepared-attempt-recovery.js +471 -0
- package/dist/worker/scheduler/recovery.js +277 -0
- package/dist/worker/scheduler/reservation.js +146 -0
- package/dist/worker/scheduler/retry.js +199 -0
- package/dist/worker/scheduler/scheduler-loop.js +272 -0
- package/dist/worker/scheduler/store.js +275 -0
- package/dist/worker/scheduler/traceability.js +54 -0
- package/dist/worker/scheduler/trigger.js +258 -0
- package/dist/worker/scheduler/types.js +369 -0
- package/dist/worker/scheduler/workspace-adapter.js +128 -0
- package/dist/worker/task-graph/acceptance-schema.js +40 -0
- package/dist/worker/task-graph/ready-planner.js +267 -0
- package/dist/worker/task-graph/ready-queue.js +23 -0
- package/dist/worker/task-graph/task-graph-schema.js +59 -0
- package/dist/worker/task-graph/types.js +1 -0
- package/dist/worker/task-graph/validate.js +293 -0
- package/dist/worker/task-spec/complexity-mapping.js +8 -0
- package/dist/worker/task-spec/schema.js +125 -0
- package/dist/worker/task-spec/types.js +1 -0
- package/dist/worker/task-spec/validate.js +391 -0
- package/dist/worker/task-spec/workflow-routing.js +149 -0
- package/dist/workflows/dag/authoring.js +8 -0
- package/dist/workflows/dag/authority-surface.js +138 -0
- package/dist/workflows/dag/backend-test-analysis-contract.js +507 -0
- package/dist/workflows/dag/backend-test-case-coverage-analysis.js +1985 -0
- package/dist/workflows/dag/backend-test-case-manifest.js +638 -0
- package/dist/workflows/dag/backend-test-classification-contract.js +38 -0
- package/dist/workflows/dag/backend-test-contract-envelope.js +205 -0
- package/dist/workflows/dag/backend-test-coverage-contract.js +202 -0
- package/dist/workflows/dag/backend-test-execution-contract.js +715 -0
- package/dist/workflows/dag/backend-test-gap-fill.js +205 -0
- package/dist/workflows/dag/backend-test-intake-context.js +178 -0
- package/dist/workflows/dag/backend-test-layout.js +133 -0
- package/dist/workflows/dag/backend-test-markdown-workflow.js +1984 -0
- package/dist/workflows/dag/backend-test-module-stem.js +26 -0
- package/dist/workflows/dag/backend-test-pytest-collection.js +859 -0
- package/dist/workflows/dag/backend-test-result-contract.js +1030 -0
- package/dist/workflows/dag/backend-test-scenario-param.js +1670 -0
- package/dist/workflows/dag/backend-test-scenario-partitions.js +249 -0
- package/dist/workflows/dag/backend-test-semantic-review-contract.js +36 -0
- package/dist/workflows/dag/backend-test-stability-contract.js +57 -0
- package/dist/workflows/dag/backend-test-writer-completeness.js +1058 -0
- package/dist/workflows/dag/budget-enforcement.js +67 -0
- package/dist/workflows/dag/canvas-observer.js +474 -0
- package/dist/workflows/dag/context-policy.js +137 -0
- package/dist/workflows/dag/contract-output-registry.js +14 -0
- package/dist/workflows/dag/contract-validator-registrations.js +10 -0
- package/dist/workflows/dag/controller-identity.js +104 -0
- package/dist/workflows/dag/convergence/controller.js +596 -0
- package/dist/workflows/dag/decision-envelope.js +557 -0
- package/dist/workflows/dag/decision-evidence.js +153 -0
- package/dist/workflows/dag/decision-gates.js +1 -0
- package/dist/workflows/dag/dynamic-runtime/condition.js +48 -0
- package/dist/workflows/dag/dynamic-runtime/loop-until.js +161 -0
- package/dist/workflows/dag/dynamic-runtime/map.js +435 -0
- package/dist/workflows/dag/dynamic-runtime/reduction.js +72 -0
- package/dist/workflows/dag/dynamic-runtime/shared.js +203 -0
- package/dist/workflows/dag/event-observer.js +132 -0
- package/dist/workflows/dag/executor-registry.js +23 -0
- package/dist/workflows/dag/facts.js +4 -0
- package/dist/workflows/dag/failure-category.js +118 -0
- package/dist/workflows/dag/failure-routing.js +105 -0
- package/dist/workflows/dag/final-verification.js +180 -0
- package/dist/workflows/dag/frontend-implementation-contract.js +1911 -0
- package/dist/workflows/dag/frontend-lint-baseline.js +474 -0
- package/dist/workflows/dag/frontend-plan-render.js +90 -0
- package/dist/workflows/dag/frontend-prewrite-gate.js +1011 -0
- package/dist/workflows/dag/frontend-project-capability.js +1 -0
- package/dist/workflows/dag/frontend-recovery-plan.js +73 -0
- package/dist/workflows/dag/frontend-recovery-root-manifest.js +123 -0
- package/dist/workflows/dag/frontend-recovery-run.js +539 -0
- package/dist/workflows/dag/frontend-repair.js +541 -0
- package/dist/workflows/dag/frontend-review-context.js +116 -0
- package/dist/workflows/dag/frontend-risk.js +161 -0
- package/dist/workflows/dag/frontend-test-case-checklist.js +343 -0
- package/dist/workflows/dag/frontend-test-case-manifest.js +104 -0
- package/dist/workflows/dag/frontend-test-case-quality.js +105 -0
- package/dist/workflows/dag/frontend-test-html-report.js +174 -0
- package/dist/workflows/dag/frontend-test-l5-report.js +177 -0
- package/dist/workflows/dag/frontend-test-result-contract.js +487 -0
- package/dist/workflows/dag/frontend-verification-trace.js +275 -0
- package/dist/workflows/dag/frontend-worktree-diff.js +201 -0
- package/dist/workflows/dag/frontend-writer-recovery.js +106 -0
- package/dist/workflows/dag/frontend-writer-rollback.js +821 -0
- package/dist/workflows/dag/governance-constants.js +5 -0
- package/dist/workflows/dag/governance-profile.js +413 -0
- package/dist/workflows/dag/index.js +6 -0
- package/dist/workflows/dag/init-hybrid.js +7345 -0
- package/dist/workflows/dag/interrupt-request.js +559 -0
- package/dist/workflows/dag/knowledge-curator.js +165 -0
- package/dist/workflows/dag/l5-report-metrics.js +33 -0
- package/dist/workflows/dag/lifecycle.js +855 -0
- package/dist/workflows/dag/liveness-policy.js +251 -0
- package/dist/workflows/dag/node-execution.js +1081 -0
- package/dist/workflows/dag/observer-compose.js +52 -0
- package/dist/workflows/dag/output-protocol.js +356 -0
- package/dist/workflows/dag/project-governance-context.js +514 -0
- package/dist/workflows/dag/prompt-source.js +88 -0
- package/dist/workflows/dag/prompt.js +348 -0
- package/dist/workflows/dag/reconcile-run.js +145 -0
- package/dist/workflows/dag/reconcile-tasks.js +404 -0
- package/dist/workflows/dag/recovery-recommendation.js +329 -0
- package/dist/workflows/dag/repair-artifact.js +363 -0
- package/dist/workflows/dag/report.js +1229 -0
- package/dist/workflows/dag/rerun-plan.js +614 -0
- package/dist/workflows/dag/rerun-run.js +556 -0
- package/dist/workflows/dag/rerun-task.js +370 -0
- package/dist/workflows/dag/retry-policy.js +263 -0
- package/dist/workflows/dag/run-store.js +41 -0
- package/dist/workflows/dag/runner.js +1069 -0
- package/dist/workflows/dag/runtime-contract.js +87 -0
- package/dist/workflows/dag/runtime.js +5 -0
- package/dist/workflows/dag/scheduler.js +497 -0
- package/dist/workflows/dag/sdd-embedded.js +128 -0
- package/dist/workflows/dag/skill-instructions.js +478 -0
- package/dist/workflows/dag/skill-snapshot.js +553 -0
- package/dist/workflows/dag/skills.js +41 -0
- package/dist/workflows/dag/spec.js +3 -0
- package/dist/workflows/dag/task-contract-binding.js +138 -0
- package/dist/workflows/dag/task-demand-routing.js +1 -0
- package/dist/workflows/dag/topo.js +30 -0
- package/dist/workflows/dag/types.js +1145 -0
- package/dist/workflows/dag/upstream-artifacts.js +98 -0
- package/dist/workflows/dag/validate.js +998 -0
- package/dist/workflows/dag/workspace-checkpoint.js +186 -0
- package/dist/workflows/dynamic/artifacts.js +65 -0
- package/dist/workflows/dynamic/compile.js +371 -0
- package/dist/workflows/dynamic/compileTypes.js +1 -0
- package/dist/workflows/dynamic/errors.js +5 -0
- package/dist/workflows/dynamic/index.js +7 -0
- package/dist/workflows/dynamic/profiles.js +156 -0
- package/dist/workflows/dynamic/spec.js +115 -0
- package/dist/workflows/dynamic/validate.js +275 -0
- package/dist/workflows/loop/actions/dag-action.js +129 -0
- package/dist/workflows/loop/actions/pi-review.js +267 -0
- package/dist/workflows/loop/actions/shared.js +157 -0
- package/dist/workflows/loop/actions/shell-verify.js +82 -0
- package/dist/workflows/loop/actions/types.js +1 -0
- package/dist/workflows/loop/actions/workflow-action.js +255 -0
- package/dist/workflows/loop/actions.js +160 -0
- package/dist/workflows/loop/benchmark.js +510 -0
- package/dist/workflows/loop/closeout.js +135 -0
- package/dist/workflows/loop/context.js +47 -0
- package/dist/workflows/loop/events.js +26 -0
- package/dist/workflows/loop/hash.js +32 -0
- package/dist/workflows/loop/index.js +8 -0
- package/dist/workflows/loop/paths.js +17 -0
- package/dist/workflows/loop/policy/auto-policy.js +112 -0
- package/dist/workflows/loop/policy/path-patterns.js +13 -0
- package/dist/workflows/loop/rounds.js +81 -0
- package/dist/workflows/loop/signals.js +52 -0
- package/dist/workflows/loop/state.js +116 -0
- package/dist/workflows/loop/templates.js +54 -0
- package/dist/workflows/loop/types.js +28 -0
- package/docs/README.md +82 -0
- package/docs/architecture/README.md +35 -0
- package/docs/architecture/dag-execution.md +165 -0
- package/docs/architecture/evolution.md +108 -0
- package/docs/architecture/facts-and-state.md +74 -0
- package/docs/architecture/runtime-boundaries.md +226 -0
- package/docs/architecture/system-overview.md +100 -0
- package/docs/architecture/worker-and-feature.md +122 -0
- package/docs/governance/README.md +18 -0
- package/docs/governance/harness-methodology-debugging.md +177 -0
- package/docs/governance/harness-methodology-tdd.md +130 -0
- package/docs/governance/harness-methodology-verification.md +27 -0
- package/docs/init-surface.manifest.json +358 -0
- package/docs/operations/README.md +15 -0
- package/docs/operations/local-development-environment.md +77 -0
- package/docs/skills/README.md +7 -0
- package/docs/skills/vetted-skill-registry.md +49 -0
- package/docs/templates/README.md +67 -0
- package/docs/templates/adr.md +60 -0
- package/docs/templates/agent-dag-authority-surface-audit.prompt.md +94 -0
- package/docs/templates/agent-dag-decision-envelope.schema.json +213 -0
- package/docs/templates/agent-dag-decision-gate-dogfood-report.md +117 -0
- package/docs/templates/agent-dag-decision-gate.prompt.md +246 -0
- package/docs/templates/agent-dag-process-supervisor.prompt.md +98 -0
- package/docs/templates/agent-dag-report.schema.json +473 -0
- package/docs/templates/agent-dag-review-verdict.prompt.md +68 -0
- package/docs/templates/agent-dag.base.json +190 -0
- package/docs/templates/agent-dag.final-verification.json +185 -0
- package/docs/templates/agent-dag.schema.json +590 -0
- package/docs/templates/agent-dag.supervised-implementation.json +640 -0
- package/docs/templates/agent-worker-production-readiness-checklist.md +47 -0
- package/docs/templates/backend-test-analysis.schema.json +37 -0
- package/docs/templates/backend-test-case-manifest.schema.json +223 -0
- package/docs/templates/backend-test-dag.classify.prompt.md +75 -0
- package/docs/templates/backend-test-dag.generate-pytest.prompt.md +41 -0
- package/docs/templates/backend-test-dag.json +671 -0
- package/docs/templates/backend-test-dag.retrospect.prompt.md +165 -0
- package/docs/templates/backend-test-dag.review-cases.prompt.md +36 -0
- package/docs/templates/backend-test-execution.schema.json +138 -0
- package/docs/templates/backend-test-result.schema.json +99 -0
- package/docs/templates/branch-merge-report.md +115 -0
- package/docs/templates/evaluation/agents-map-slim-v1.candidate.json +9 -0
- package/docs/templates/evaluation/agents-map-slim-v1.md +87 -0
- package/docs/templates/evaluation/agents-map-verbose-v0.candidate.json +9 -0
- package/docs/templates/evaluation/agents-map-verbose-v0.md +153 -0
- package/docs/templates/evaluation/campaign-budget-v1.json +12 -0
- package/docs/templates/evaluation/campaign-dogfood-v0.json +24 -0
- package/docs/templates/evaluation/campaign-evidence-v1.json +44 -0
- package/docs/templates/evaluation/context-policy-baseline-v1.json +17 -0
- package/docs/templates/evaluation/context-policy-role-specialized-v1.json +28 -0
- package/docs/templates/evaluation/corpus-dogfood-v0.manifest.json +118 -0
- package/docs/templates/evaluation/matrix-dag-dry-run-v1.json +21 -0
- package/docs/templates/evaluation/matrix-fixture-v1.json +10 -0
- package/docs/templates/evaluation/private-verifier-dogfood-v0.json +16 -0
- package/docs/templates/exec-plan.md +66 -0
- package/docs/templates/feature-spec.md +53 -0
- package/docs/templates/frontend-design-contract.md +68 -0
- package/docs/templates/frontend-eval/fixtures/failures/01-type-build-error.md +17 -0
- package/docs/templates/frontend-eval/fixtures/failures/02-unit-component-test-fail.md +16 -0
- package/docs/templates/frontend-eval/fixtures/failures/03-fixture-schema-drift.md +16 -0
- package/docs/templates/frontend-eval/fixtures/failures/04-missing-loading-empty-error-state.md +16 -0
- package/docs/templates/frontend-eval/fixtures/failures/05-forbidden-write-writeset-expansion.md +16 -0
- package/docs/templates/frontend-eval/fixtures/failures/06-unapproved-dependency-add.md +16 -0
- package/docs/templates/frontend-eval/fixtures/failures/07-mock-production-on.md +21 -0
- package/docs/templates/frontend-eval/fixtures/functional/01-simple-component-style.md +29 -0
- package/docs/templates/frontend-eval/fixtures/functional/02-form-validation.md +28 -0
- package/docs/templates/frontend-eval/fixtures/functional/03-list-detail-page.md +28 -0
- package/docs/templates/frontend-eval/fixtures/functional/04-api-mock.md +29 -0
- package/docs/templates/frontend-eval/fixtures/functional/05-permission-auth-gated-ui.md +27 -0
- package/docs/templates/frontend-eval/fixtures/functional/06-ssr-server-client-boundary.md +28 -0
- package/docs/templates/frontend-eval/fixtures/functional/07-shared-public-component-api.md +28 -0
- package/docs/templates/frontend-eval/fixtures/functional/08-pure-local-no-remote.md +27 -0
- package/docs/templates/frontend-eval/metrics.md +138 -0
- package/docs/templates/frontend-eval/smoke-targets.md +53 -0
- package/docs/templates/frontend-implementation-contract.schema.json +35 -0
- package/docs/templates/frontend-task-constraints.md +62 -0
- package/docs/templates/frontend-task-requirement.md +104 -0
- package/docs/templates/frontend-test-case-checklist.md +44 -0
- package/docs/templates/frontend-test-case.example.md +143 -0
- package/docs/templates/frontend-test-dag.generate-cases.prompt.md +63 -0
- package/docs/templates/frontend-test-dag.json +409 -0
- package/docs/templates/frontend-test-dag.retrieve-context.prompt.md +21 -0
- package/docs/templates/frontend-test-dag.retrospect.prompt.md +5 -0
- package/docs/templates/frontend-test-dag.review-cases.prompt.md +5 -0
- package/docs/templates/frontend-test-dag.review-execution.prompt.md +3 -0
- package/docs/templates/frontend-test-standard-scenarios.v1.json +159 -0
- package/docs/templates/harness.schema.json +351 -0
- package/docs/templates/hybrid-dag.json +188 -0
- package/docs/templates/init-evolution-review.md +35 -0
- package/docs/templates/init-managed-agents.md +157 -0
- package/docs/templates/interactive-ui-round2-experiment.md +66 -0
- package/docs/templates/knowledge-graph-bootstrap-dag.json +118 -0
- package/docs/templates/knowledge-sync-dag.json +178 -0
- package/docs/templates/knowledge-sync-draft.schema.json +71 -0
- package/docs/templates/product-line/AGENTS.md +9 -0
- package/docs/templates/product-line/README.md +42 -0
- package/docs/templates/product-line/acceptance.yaml +23 -0
- package/docs/templates/product-line/closeout.yaml +9 -0
- package/docs/templates/product-line/design.md +13 -0
- package/docs/templates/product-line/feature-scaffold-batch.example.yaml +31 -0
- package/docs/templates/product-line/feature.yaml +11 -0
- package/docs/templates/product-line/links.md +10 -0
- package/docs/templates/product-line/requirement.md +17 -0
- package/docs/templates/product-line/scaffold-samples/README.md +36 -0
- package/docs/templates/product-line/scaffold-samples/backend-only/acceptance.yaml +16 -0
- package/docs/templates/product-line/scaffold-samples/backend-only/design.md +14 -0
- package/docs/templates/product-line/scaffold-samples/backend-only/feature.yaml +14 -0
- package/docs/templates/product-line/scaffold-samples/backend-only/requirement.md +6 -0
- package/docs/templates/product-line/scaffold-samples/backend-only/tasks/BE-IMPL-001.yaml +83 -0
- package/docs/templates/product-line/scaffold-samples/backend-only/tasks/task-graph.yaml +14 -0
- package/docs/templates/product-line/scaffold-samples/fe-with-api/acceptance.yaml +18 -0
- package/docs/templates/product-line/scaffold-samples/fe-with-api/design.md +18 -0
- package/docs/templates/product-line/scaffold-samples/fe-with-api/feature.yaml +14 -0
- package/docs/templates/product-line/scaffold-samples/fe-with-api/requirement.md +6 -0
- package/docs/templates/product-line/scaffold-samples/fe-with-api/tasks/BE-IMPL-001.yaml +84 -0
- package/docs/templates/product-line/scaffold-samples/fe-with-api/tasks/CONTRACT-001.yaml +84 -0
- package/docs/templates/product-line/scaffold-samples/fe-with-api/tasks/FE-IMPL-001.yaml +86 -0
- package/docs/templates/product-line/scaffold-samples/fe-with-api/tasks/task-graph.yaml +40 -0
- package/docs/templates/product-line/scaffold-samples/frontend-only/acceptance.yaml +16 -0
- package/docs/templates/product-line/scaffold-samples/frontend-only/design.md +14 -0
- package/docs/templates/product-line/scaffold-samples/frontend-only/feature.yaml +14 -0
- package/docs/templates/product-line/scaffold-samples/frontend-only/requirement.md +6 -0
- package/docs/templates/product-line/scaffold-samples/frontend-only/tasks/FE-IMPL-001.yaml +85 -0
- package/docs/templates/product-line/scaffold-samples/frontend-only/tasks/task-graph.yaml +14 -0
- package/docs/templates/product-line/task-graph.yaml +23 -0
- package/docs/templates/product-line/task.yaml +68 -0
- package/docs/templates/product-line/test-plan.md +7 -0
- package/docs/templates/production-readiness-checklist.md +57 -0
- package/docs/templates/progress-log.md +27 -0
- package/docs/templates/project-start-checklist.md +9 -0
- package/docs/templates/qa-report.md +48 -0
- package/docs/templates/sprint-contract.md +29 -0
- package/docs/templates/worker-dogfood-evidence.md +80 -0
- package/docs/templates/worker-dogfood-setup.md +68 -0
- package/examples/decision-gate-agent-dag.json +177 -0
- package/examples/example-dag.json +46 -0
- package/examples/hybrid-loop-agent-dag.json +189 -0
- package/examples/l5-report-coms-process-definition.html +322 -0
- package/examples/l5-report-demo.html +79 -0
- package/harness.json +90 -0
- package/package.json +98 -0
- package/scripts/kb-bootstrap-init-skeleton.sh +241 -0
- package/scripts/kb-graph-incremental-prepare.mjs +386 -0
- package/scripts/kb-graph-materialize.mjs +105 -0
- package/scripts/kb-graph-promote.mjs +164 -0
- package/scripts/kb-query.mjs +554 -0
- package/skills/agent-worker/SKILL.md +48 -0
- package/skills/agent-worker/references/agent-worker-operator.md +159 -0
- package/skills/ai-engineering-context/SKILL.md +48 -0
- package/skills/analyze-product-dependencies/SKILL.md +108 -0
- package/skills/analyze-product-dependencies/references/api-documentation-schema.md +35 -0
- package/skills/analyze-product-dependencies/references/dependency-analysis-schema.md +38 -0
- package/skills/analyze-product-dependencies/references/example.md +76 -0
- package/skills/analyze-product-dependencies/references/forward-test-cases.md +110 -0
- package/skills/analyze-product-dependencies/references/input-contract.md +34 -0
- package/skills/analyze-product-dependencies/references/kb-integration.md +64 -0
- package/skills/analyze-product-dependencies/references/scouting-rules.md +76 -0
- package/skills/analyze-product-dependencies/scripts/test-validators.mjs +460 -0
- package/skills/analyze-product-dependencies/scripts/validate-api-documentation.mjs +194 -0
- package/skills/analyze-product-dependencies/scripts/validate-dependency-analysis.mjs +200 -0
- package/skills/analyze-product-dependencies/scripts/validate-product-requirement-input.mjs +90 -0
- package/skills/analyze-product-dependencies/scripts/validation-helpers.mjs +138 -0
- package/skills/analyze-product-requirements/SKILL.md +145 -0
- package/skills/analyze-product-requirements/references/acceptance-criteria.md +87 -0
- package/skills/analyze-product-requirements/references/clarification-and-knowledge.md +70 -0
- package/skills/analyze-product-requirements/references/example.md +104 -0
- package/skills/analyze-product-requirements/references/forward-test-cases.md +144 -0
- package/skills/analyze-product-requirements/references/kb-integration.md +56 -0
- package/skills/analyze-product-requirements/references/product-analysis-schema.md +41 -0
- package/skills/analyze-product-requirements/references/product-requirement-schema.md +42 -0
- package/skills/analyze-product-requirements/references/requirement-clarification-schema.md +66 -0
- package/skills/analyze-product-requirements/scripts/compute-source-identity.mjs +35 -0
- package/skills/analyze-product-requirements/scripts/test-validators.mjs +501 -0
- package/skills/analyze-product-requirements/scripts/validate-product-analysis.mjs +100 -0
- package/skills/analyze-product-requirements/scripts/validate-product-requirement.mjs +109 -0
- package/skills/analyze-product-requirements/scripts/validate-requirement-clarification.mjs +109 -0
- package/skills/analyze-product-requirements/scripts/validation-helpers.mjs +175 -0
- package/skills/browser-tools/SKILL.md +196 -0
- package/skills/browser-tools/browser-content.js +103 -0
- package/skills/browser-tools/browser-cookies.js +35 -0
- package/skills/browser-tools/browser-eval.js +53 -0
- package/skills/browser-tools/browser-hn-scraper.js +108 -0
- package/skills/browser-tools/browser-nav.js +44 -0
- package/skills/browser-tools/browser-pick.js +162 -0
- package/skills/browser-tools/browser-screenshot.js +34 -0
- package/skills/browser-tools/browser-start.js +86 -0
- package/skills/browser-tools/package-lock.json +2556 -0
- package/skills/browser-tools/package.json +19 -0
- package/skills/code-review-core/SKILL.md +20 -0
- package/skills/codebase-scout/SKILL.md +19 -0
- package/skills/frontend-bounded-implement/SKILL.md +53 -0
- package/skills/frontend-design-review/SKILL.md +79 -0
- package/skills/frontend-design-review/references/review-checklist.md +43 -0
- package/skills/frontend-implementation/SKILL.md +52 -0
- package/skills/frontend-implementation/references/code-standards.md +33 -0
- package/skills/frontend-implementation/references/design-spec.md +56 -0
- package/skills/frontend-implementation/references/node-contracts.md +31 -0
- package/skills/frontend-review/SKILL.md +55 -0
- package/skills/frontend-review/references/review-findings.md +50 -0
- package/skills/frontend-verification/SKILL.md +61 -0
- package/skills/frontend-verification/references/verification-checklist.md +48 -0
- package/skills/grill-me/SKILL.md +10 -0
- package/skills/grill-with-docs/SKILL.md +80 -0
- package/skills/grill-with-docs/adr-format.md +58 -0
- package/skills/grill-with-docs/context-format.md +52 -0
- package/skills/init-capability-evolution/SKILL.md +70 -0
- package/skills/local-jacoco-coverage/SKILL.md +281 -0
- package/skills/local-jacoco-coverage/references/requirement-to-source-mapping.md +85 -0
- package/skills/local-jacoco-coverage/references/runtime-alignment.md +106 -0
- package/skills/local-jacoco-coverage/scripts/run-coverage-analysis.sh +148 -0
- package/skills/local-jacoco-coverage/scripts/start-jacoco-agent.sh +110 -0
- package/skills/loop-agent/SKILL.md +68 -0
- package/skills/loop-agent/references/README.md +67 -0
- package/skills/loop-agent/references/command-reference.md +645 -0
- package/skills/loop-agent/references/docs-converge.md +126 -0
- package/skills/loop-agent/references/harness-policy.md +289 -0
- package/skills/loop-agent/references/hybrid-dag.md +245 -0
- package/skills/loop-agent/references/learned/README.md +21 -0
- package/skills/loop-agent/references/long-running-loop.md +57 -0
- package/skills/loop-agent/references/model-routing.md +45 -0
- package/skills/loop-agent/references/multi-worktree.md +54 -0
- package/skills/loop-agent/references/one-shot-runs.md +84 -0
- package/skills/loop-agent/references/orchestrator-and-interventions.md +175 -0
- package/skills/loop-agent/references/pi-prompt.md +23 -0
- package/skills/loop-agent/references/pi-subagent-assisted-mode.md +84 -0
- package/skills/loop-agent/references/post-implementation-and-patterns.md +54 -0
- package/skills/loop-agent/references/source-and-plan-practice.md +169 -0
- package/skills/loop-agent/references/task-workflow.md +104 -0
- package/skills/loop-agent/references/verification-and-failure-handling.md +148 -0
- package/skills/playwright-cli/SKILL.md +93 -0
- package/skills/playwright-cli/references/element-attributes.md +23 -0
- package/skills/playwright-cli/references/playwright-tests.md +39 -0
- package/skills/playwright-cli/references/request-mocking.md +87 -0
- package/skills/playwright-cli/references/running-code.md +241 -0
- package/skills/playwright-cli/references/session-management.md +225 -0
- package/skills/playwright-cli/references/storage-state.md +275 -0
- package/skills/playwright-cli/references/test-generation.md +433 -0
- package/skills/playwright-cli/references/tracing.md +5 -0
- package/skills/playwright-cli/references/video-recording.md +5 -0
- package/skills/playwright-cli-case-generator/SKILL.md +108 -0
- package/skills/requesting-code-review/SKILL.md +101 -0
- package/skills/requesting-code-review/code-reviewer.md +168 -0
- package/skills/systematic-debugging/CREATION-LOG.md +119 -0
- package/skills/systematic-debugging/SKILL.md +312 -0
- package/skills/systematic-debugging/condition-based-waiting-example.ts +158 -0
- package/skills/systematic-debugging/condition-based-waiting.md +115 -0
- package/skills/systematic-debugging/defense-in-depth.md +122 -0
- package/skills/systematic-debugging/find-polluter.sh +63 -0
- package/skills/systematic-debugging/root-cause-tracing.md +169 -0
- package/skills/systematic-debugging/test-academic.md +14 -0
- package/skills/systematic-debugging/test-pressure-1.md +58 -0
- package/skills/systematic-debugging/test-pressure-2.md +68 -0
- package/skills/systematic-debugging/test-pressure-3.md +69 -0
- package/skills/test-driven-development/SKILL.md +27 -0
- package/skills/using-git-worktrees/SKILL.md +215 -0
- package/skills/verification-before-completion/SKILL.md +154 -0
- package/skills/webapp-testing/SKILL.md +19 -0
|
@@ -0,0 +1,2499 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { readdir, readFile } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { truncateUtf8Preview } from "../../shared/preview.js";
|
|
5
|
+
import { parseWorkerEventLine } from "./events.js";
|
|
6
|
+
import { readBoundedJsonlTail } from "./event-history.js";
|
|
7
|
+
import { getTaskPoolRoot, listLegacyStateFiles, listTaskPoolStates, } from "../pool/run-store.js";
|
|
8
|
+
import { listRunOwners } from "../pool/run-owner-store.js";
|
|
9
|
+
import { assessDagRunLiveness, assessDagRunRecoveryEligibility, deriveDagRunEffectiveStatus, } from "../../workflows/dag/lifecycle.js";
|
|
10
|
+
import { parseDagSpec, resolveModelForTask, } from "../../workflows/dag/types.js";
|
|
11
|
+
import { loadFeatureDecisionModels } from "../feature/decision-loader.js";
|
|
12
|
+
import { frontendLintAssessmentArtifactSchema } from "../../workflows/dag/frontend-lint-baseline.js";
|
|
13
|
+
/** Internal map key: featureId + NUL + taskId (stable, non-printable delimiter). */
|
|
14
|
+
export function taskIdentityKey(featureId, taskId) {
|
|
15
|
+
return `${featureId}\0${taskId}`;
|
|
16
|
+
}
|
|
17
|
+
export function compareTaskIdentity(a, b) {
|
|
18
|
+
const fa = a.featureId ?? "";
|
|
19
|
+
const fb = b.featureId ?? "";
|
|
20
|
+
const byFeature = fa.localeCompare(fb);
|
|
21
|
+
if (byFeature !== 0)
|
|
22
|
+
return byFeature;
|
|
23
|
+
return a.taskId.localeCompare(b.taskId);
|
|
24
|
+
}
|
|
25
|
+
export function findTasksByTaskId(tasks, taskId) {
|
|
26
|
+
return tasks.filter((task) => task.taskId === taskId);
|
|
27
|
+
}
|
|
28
|
+
export function resolveLegacyTask(tasks, taskId) {
|
|
29
|
+
const matches = findTasksByTaskId(tasks, taskId);
|
|
30
|
+
const candidates = matches.map((task) => ({
|
|
31
|
+
featureId: task.featureId,
|
|
32
|
+
taskId: task.taskId,
|
|
33
|
+
status: task.status,
|
|
34
|
+
workerRunId: task.workerRunId,
|
|
35
|
+
}));
|
|
36
|
+
if (matches.length === 0) {
|
|
37
|
+
return { kind: "missing", candidates: [] };
|
|
38
|
+
}
|
|
39
|
+
if (matches.length === 1) {
|
|
40
|
+
return { kind: "unique", task: matches[0], candidates };
|
|
41
|
+
}
|
|
42
|
+
return { kind: "ambiguous", candidates };
|
|
43
|
+
}
|
|
44
|
+
export const TASK_RUN_HISTORY_DEFAULT_LIMIT = 20;
|
|
45
|
+
export const TASK_RUN_HISTORY_MAX_LIMIT = 100;
|
|
46
|
+
/** Default / allowed page sizes for GET /api/dag-runs history list. */
|
|
47
|
+
export const DAG_RUN_HISTORY_DEFAULT_PAGE = 1;
|
|
48
|
+
export const DAG_RUN_HISTORY_DEFAULT_PAGE_SIZE = 20;
|
|
49
|
+
export const DAG_RUN_HISTORY_MAX_PAGE_SIZE = 100;
|
|
50
|
+
export const DAG_RUN_HISTORY_ALLOWED_PAGE_SIZES = [20, 50, 100];
|
|
51
|
+
const ACTIVE_TASK_STATUSES = new Set(["running", "pending"]);
|
|
52
|
+
const ACTIVE_BATCH_STATUSES = new Set(["running", "pending"]);
|
|
53
|
+
export async function buildGlobalSnapshot(options) {
|
|
54
|
+
try {
|
|
55
|
+
const repoRoot = path.resolve(options.repoRoot);
|
|
56
|
+
const now = options.now ?? (() => new Date());
|
|
57
|
+
const staleThresholdMs = options.staleThresholdMs ?? 90_000;
|
|
58
|
+
const quietThresholdMs = options.quietThresholdMs ?? 60_000;
|
|
59
|
+
const taskPoolPresent = existsSync(getTaskPoolRoot(repoRoot));
|
|
60
|
+
const [eventsLoad, ledgerLoad, ledgerBatches, statesLoad, handoffs, dagRuns, owners,] = await Promise.all([
|
|
61
|
+
loadObservabilityEvents(repoRoot),
|
|
62
|
+
loadLedgerRuns(repoRoot),
|
|
63
|
+
loadLedgerBatches(repoRoot),
|
|
64
|
+
loadStateRecords(repoRoot),
|
|
65
|
+
loadFailureHandoffs(repoRoot),
|
|
66
|
+
loadDagRuns(repoRoot, now()),
|
|
67
|
+
listRunOwners(repoRoot).catch(() => []),
|
|
68
|
+
]);
|
|
69
|
+
const events = eventsLoad.events;
|
|
70
|
+
const ledgerRuns = ledgerLoad.runs;
|
|
71
|
+
const identityWarnings = [];
|
|
72
|
+
const taskMap = new Map();
|
|
73
|
+
// Ledger first so featureId-bearing rows seed composite identity before states/events.
|
|
74
|
+
for (const run of ledgerRuns) {
|
|
75
|
+
mergeLedgerRun(taskMap, run, identityWarnings);
|
|
76
|
+
}
|
|
77
|
+
for (const state of statesLoad.states) {
|
|
78
|
+
mergeStateRecord(taskMap, state, identityWarnings);
|
|
79
|
+
}
|
|
80
|
+
// Attach uniquely-resolvable legacy flat states after owned rows exist.
|
|
81
|
+
for (const state of statesLoad.legacyCandidates) {
|
|
82
|
+
mergeLegacyStateRecord(taskMap, state, identityWarnings);
|
|
83
|
+
}
|
|
84
|
+
const workerRunFeatureIndex = buildWorkerRunFeatureIndex(ledgerRuns, taskMap, owners);
|
|
85
|
+
mergeEvents(taskMap, events, workerRunFeatureIndex, identityWarnings);
|
|
86
|
+
mergeHandoffs(taskMap, handoffs);
|
|
87
|
+
applyStaleDetection(taskMap, now(), staleThresholdMs, quietThresholdMs);
|
|
88
|
+
const historicalRunMap = buildHistoricalRunMap(ledgerRuns, events, workerRunFeatureIndex, identityWarnings);
|
|
89
|
+
mergeHandoffs(historicalRunMap, handoffs);
|
|
90
|
+
applyStaleDetection(historicalRunMap, now(), staleThresholdMs, quietThresholdMs);
|
|
91
|
+
const tasks = [...taskMap.values()]
|
|
92
|
+
.map(toPublicTaskSummary)
|
|
93
|
+
.map((task) => normalizeTaskArtifactRefs(task, repoRoot))
|
|
94
|
+
.sort(compareTaskIdentity);
|
|
95
|
+
const batches = buildBatches(ledgerBatches, events, historicalRunMap).map((batch) => ({
|
|
96
|
+
...batch,
|
|
97
|
+
tasks: batch.tasks.map((task) => normalizeTaskArtifactRefs(task, repoRoot)),
|
|
98
|
+
}));
|
|
99
|
+
const health = computeHealth(batches, tasks, dagRuns);
|
|
100
|
+
const featureLoad = await loadFeatureDecisionModels(repoRoot);
|
|
101
|
+
const projectionWarnings = [
|
|
102
|
+
...(eventsLoad.warnings ?? []),
|
|
103
|
+
...(ledgerLoad.warnings ?? []),
|
|
104
|
+
...(statesLoad.warnings ?? []),
|
|
105
|
+
...identityWarnings,
|
|
106
|
+
...(featureLoad.projectionWarnings ?? []),
|
|
107
|
+
];
|
|
108
|
+
return {
|
|
109
|
+
schemaVersion: 1,
|
|
110
|
+
generatedAt: now().toISOString(),
|
|
111
|
+
repoRoot,
|
|
112
|
+
batches,
|
|
113
|
+
tasks,
|
|
114
|
+
dagRuns,
|
|
115
|
+
features: featureLoad.features,
|
|
116
|
+
taskPool: { present: taskPoolPresent },
|
|
117
|
+
projectionWarnings,
|
|
118
|
+
health,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
catch (error) {
|
|
122
|
+
const now = options.now ?? (() => new Date());
|
|
123
|
+
const safeNow = () => {
|
|
124
|
+
try {
|
|
125
|
+
return now();
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
return new Date();
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
let repoRoot;
|
|
132
|
+
try {
|
|
133
|
+
repoRoot = path.resolve(options.repoRoot);
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
repoRoot = String(options.repoRoot ?? "");
|
|
137
|
+
}
|
|
138
|
+
const snapshot = emptySnapshot(repoRoot, safeNow);
|
|
139
|
+
snapshot.projectionError = sanitizeProjectionError(error);
|
|
140
|
+
return snapshot;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Bounded Task run history from Task Pool ledger facts (newest first).
|
|
145
|
+
* Filtered by composite `{featureId, taskId}`.
|
|
146
|
+
* `before` is an exclusive upper bound on the sort key (`recordedAt` or empty).
|
|
147
|
+
*/
|
|
148
|
+
export async function listTaskRunHistory(repoRoot, featureId, taskId, options = {}) {
|
|
149
|
+
const limit = clampTaskRunHistoryLimit(options.limit);
|
|
150
|
+
const before = decodeTaskRunHistoryCursor(options.before);
|
|
151
|
+
const [ledgerLoad, eventsLoad] = await Promise.all([
|
|
152
|
+
loadLedgerRuns(path.resolve(repoRoot)),
|
|
153
|
+
loadObservabilityEvents(path.resolve(repoRoot)),
|
|
154
|
+
]);
|
|
155
|
+
const ledgerRuns = ledgerLoad.runs;
|
|
156
|
+
const events = eventsLoad.events;
|
|
157
|
+
const workerRunFeatureIndex = buildWorkerRunFeatureIndex(ledgerRuns);
|
|
158
|
+
const historical = buildHistoricalRunMap(ledgerRuns, events, workerRunFeatureIndex);
|
|
159
|
+
const items = [];
|
|
160
|
+
for (const run of ledgerRuns) {
|
|
161
|
+
if (run.taskId !== taskId)
|
|
162
|
+
continue;
|
|
163
|
+
if ((run.featureId ?? "") !== featureId)
|
|
164
|
+
continue;
|
|
165
|
+
const enriched = historical.get(run.workerRunId);
|
|
166
|
+
items.push({
|
|
167
|
+
workerRunId: run.workerRunId,
|
|
168
|
+
taskId: run.taskId,
|
|
169
|
+
featureId: run.featureId ?? featureId,
|
|
170
|
+
workflow: enriched?.workflow ?? run.workflow,
|
|
171
|
+
status: enriched?.status ?? mapRunRecordStatus(run.status),
|
|
172
|
+
startedAt: enriched?.startedAt,
|
|
173
|
+
finishedAt: enriched?.finishedAt ?? run.recordedAt,
|
|
174
|
+
recordedAt: run.recordedAt,
|
|
175
|
+
batchRunId: run.batchRunId,
|
|
176
|
+
dagRunId: enriched?.dagRunId,
|
|
177
|
+
...(run.retryOfWorkerRunId
|
|
178
|
+
? { retryOfWorkerRunId: run.retryOfWorkerRunId }
|
|
179
|
+
: {}),
|
|
180
|
+
failureCategory: run.failureCategory ?? enriched?.failureCategory,
|
|
181
|
+
recommendedFollowUp: run.recommendedFollowUp ?? enriched?.recommendedFollowUp,
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
items.sort((a, b) => {
|
|
185
|
+
const ta = a.recordedAt ?? a.finishedAt ?? "";
|
|
186
|
+
const tb = b.recordedAt ?? b.finishedAt ?? "";
|
|
187
|
+
if (ta !== tb)
|
|
188
|
+
return tb.localeCompare(ta);
|
|
189
|
+
return b.workerRunId.localeCompare(a.workerRunId);
|
|
190
|
+
});
|
|
191
|
+
const eligible = before
|
|
192
|
+
? items.filter((item) => taskRunSortKey(item).localeCompare(before) < 0)
|
|
193
|
+
: items;
|
|
194
|
+
const page = eligible.slice(0, limit);
|
|
195
|
+
const nextBefore = eligible.length > limit
|
|
196
|
+
? encodeTaskRunHistoryCursor(page[page.length - 1])
|
|
197
|
+
: null;
|
|
198
|
+
return {
|
|
199
|
+
featureId,
|
|
200
|
+
taskId,
|
|
201
|
+
runs: page,
|
|
202
|
+
limit,
|
|
203
|
+
nextBefore,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
function taskRunSortKey(item) {
|
|
207
|
+
return `${item.recordedAt ?? item.finishedAt ?? ""}\u0000${item.workerRunId}`;
|
|
208
|
+
}
|
|
209
|
+
function encodeTaskRunHistoryCursor(item) {
|
|
210
|
+
if (!item)
|
|
211
|
+
return null;
|
|
212
|
+
return `v1.${Buffer.from(taskRunSortKey(item), "utf-8").toString("base64url")}`;
|
|
213
|
+
}
|
|
214
|
+
function decodeTaskRunHistoryCursor(cursor) {
|
|
215
|
+
const value = cursor?.trim();
|
|
216
|
+
if (!value)
|
|
217
|
+
return null;
|
|
218
|
+
if (!value.startsWith("v1.")) {
|
|
219
|
+
// Compatibility with the initial timestamp-only cursor shape.
|
|
220
|
+
return `${value}\u0000`;
|
|
221
|
+
}
|
|
222
|
+
try {
|
|
223
|
+
return Buffer.from(value.slice(3), "base64url").toString("utf-8");
|
|
224
|
+
}
|
|
225
|
+
catch {
|
|
226
|
+
return `${value}\u0000`;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
export function clampTaskRunHistoryLimit(limit) {
|
|
230
|
+
if (limit === undefined || !Number.isFinite(limit) || limit <= 0) {
|
|
231
|
+
return TASK_RUN_HISTORY_DEFAULT_LIMIT;
|
|
232
|
+
}
|
|
233
|
+
return Math.min(Math.floor(limit), TASK_RUN_HISTORY_MAX_LIMIT);
|
|
234
|
+
}
|
|
235
|
+
export function clampDagRunHistoryPageSize(pageSize) {
|
|
236
|
+
if (pageSize === undefined || !Number.isFinite(pageSize) || pageSize <= 0) {
|
|
237
|
+
return DAG_RUN_HISTORY_DEFAULT_PAGE_SIZE;
|
|
238
|
+
}
|
|
239
|
+
const floored = Math.floor(pageSize);
|
|
240
|
+
if (DAG_RUN_HISTORY_ALLOWED_PAGE_SIZES.includes(floored)) {
|
|
241
|
+
return floored;
|
|
242
|
+
}
|
|
243
|
+
// Nearest allowed size, prefer smaller when equidistant, then clamp to max.
|
|
244
|
+
let best = DAG_RUN_HISTORY_DEFAULT_PAGE_SIZE;
|
|
245
|
+
let bestDist = Number.POSITIVE_INFINITY;
|
|
246
|
+
for (const allowed of DAG_RUN_HISTORY_ALLOWED_PAGE_SIZES) {
|
|
247
|
+
const dist = Math.abs(allowed - floored);
|
|
248
|
+
if (dist < bestDist || (dist === bestDist && allowed < best)) {
|
|
249
|
+
best = allowed;
|
|
250
|
+
bestDist = dist;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
return Math.min(best, DAG_RUN_HISTORY_MAX_PAGE_SIZE);
|
|
254
|
+
}
|
|
255
|
+
export function clampDagRunHistoryPage(page, totalPages) {
|
|
256
|
+
const safeTotal = Math.max(1, Math.floor(totalPages) || 1);
|
|
257
|
+
if (page === undefined || !Number.isFinite(page) || page <= 0) {
|
|
258
|
+
return DAG_RUN_HISTORY_DEFAULT_PAGE;
|
|
259
|
+
}
|
|
260
|
+
return Math.min(Math.floor(page), safeTotal);
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Formal DAG history membership (AC-004):
|
|
264
|
+
* exclude executionMode or lifecycle dry-run/init-only;
|
|
265
|
+
* keep missing executionMode when lifecycle is not a non-execution mode.
|
|
266
|
+
*/
|
|
267
|
+
export function isFormalDagRunHistoryCandidate(dag) {
|
|
268
|
+
const executionMode = (dag.executionMode ?? "").toLowerCase();
|
|
269
|
+
if (NON_EXECUTION_MODES.has(executionMode))
|
|
270
|
+
return false;
|
|
271
|
+
const lifecycle = (dag.lifecycle ?? "").toLowerCase();
|
|
272
|
+
if (NON_EXECUTION_MODES.has(lifecycle))
|
|
273
|
+
return false;
|
|
274
|
+
return true;
|
|
275
|
+
}
|
|
276
|
+
function dagRunHistorySortKey(dag) {
|
|
277
|
+
return `${dag.startedAt ?? dag.finishedAt ?? ""}\0${dag.dagRunId}`;
|
|
278
|
+
}
|
|
279
|
+
function projectDagRunHistoryProgress(dag) {
|
|
280
|
+
const nodes = dag.nodes ?? [];
|
|
281
|
+
let finished = 0;
|
|
282
|
+
for (const node of nodes) {
|
|
283
|
+
const status = (node.status ?? "").toLowerCase();
|
|
284
|
+
if (status === "finished" ||
|
|
285
|
+
status === "completed" ||
|
|
286
|
+
status === "succeeded" ||
|
|
287
|
+
status === "done" ||
|
|
288
|
+
status === "error" ||
|
|
289
|
+
status === "failed" ||
|
|
290
|
+
status === "skipped" ||
|
|
291
|
+
status === "partial_failed") {
|
|
292
|
+
finished += 1;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
return { finished, total: nodes.length };
|
|
296
|
+
}
|
|
297
|
+
function toDagRunHistoryItem(dag) {
|
|
298
|
+
return {
|
|
299
|
+
dagRunId: dag.dagRunId,
|
|
300
|
+
...(dag.title ? { title: dag.title } : {}),
|
|
301
|
+
...(dag.status ? { status: dag.status } : {}),
|
|
302
|
+
...(dag.effectiveStatus ? { effectiveStatus: dag.effectiveStatus } : {}),
|
|
303
|
+
...(dag.lifecycle ? { lifecycle: dag.lifecycle } : {}),
|
|
304
|
+
...(dag.executionMode ? { executionMode: dag.executionMode } : {}),
|
|
305
|
+
...(dag.startedAt ? { startedAt: dag.startedAt } : {}),
|
|
306
|
+
...(dag.finishedAt ? { finishedAt: dag.finishedAt } : {}),
|
|
307
|
+
...(dag.durationMs !== undefined ? { durationMs: dag.durationMs } : {}),
|
|
308
|
+
progress: projectDagRunHistoryProgress(dag),
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Paginated lightweight formal DAG run history from an in-memory GlobalSnapshot.
|
|
313
|
+
* Filters dry-run/init-only; sorts newest first; never embeds nodes/edges/previews.
|
|
314
|
+
*/
|
|
315
|
+
export function listDagRunHistoryFromSnapshot(snapshot, options = {}) {
|
|
316
|
+
const pageSize = clampDagRunHistoryPageSize(options.pageSize);
|
|
317
|
+
const formal = (snapshot.dagRuns ?? [])
|
|
318
|
+
.filter(isFormalDagRunHistoryCandidate)
|
|
319
|
+
.slice()
|
|
320
|
+
.sort((a, b) => dagRunHistorySortKey(b).localeCompare(dagRunHistorySortKey(a)));
|
|
321
|
+
const total = formal.length;
|
|
322
|
+
const totalPages = Math.max(1, Math.ceil(total / pageSize) || 1);
|
|
323
|
+
const page = clampDagRunHistoryPage(options.page, totalPages);
|
|
324
|
+
const start = (page - 1) * pageSize;
|
|
325
|
+
const runs = formal.slice(start, start + pageSize).map(toDagRunHistoryItem);
|
|
326
|
+
return {
|
|
327
|
+
schemaVersion: 1,
|
|
328
|
+
generatedAt: snapshot.generatedAt,
|
|
329
|
+
page,
|
|
330
|
+
pageSize,
|
|
331
|
+
total,
|
|
332
|
+
totalPages,
|
|
333
|
+
runs,
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
function emptySnapshot(repoRoot, now) {
|
|
337
|
+
let taskPoolPresent = false;
|
|
338
|
+
try {
|
|
339
|
+
taskPoolPresent = existsSync(getTaskPoolRoot(repoRoot));
|
|
340
|
+
}
|
|
341
|
+
catch {
|
|
342
|
+
taskPoolPresent = false;
|
|
343
|
+
}
|
|
344
|
+
return {
|
|
345
|
+
schemaVersion: 1,
|
|
346
|
+
generatedAt: now().toISOString(),
|
|
347
|
+
repoRoot,
|
|
348
|
+
batches: [],
|
|
349
|
+
tasks: [],
|
|
350
|
+
dagRuns: [],
|
|
351
|
+
taskPool: { present: taskPoolPresent },
|
|
352
|
+
health: {
|
|
353
|
+
activeBatches: 0,
|
|
354
|
+
activeTasks: 0,
|
|
355
|
+
quietCount: 0,
|
|
356
|
+
staleCount: 0,
|
|
357
|
+
timeoutRiskCount: 0,
|
|
358
|
+
failuresCount: 0,
|
|
359
|
+
dag: {
|
|
360
|
+
activeRuns: 0,
|
|
361
|
+
runningNodes: 0,
|
|
362
|
+
pendingNodes: 0,
|
|
363
|
+
pausedRuns: 0,
|
|
364
|
+
failedRuns: 0,
|
|
365
|
+
staleRuns: 0,
|
|
366
|
+
interruptedRuns: 0,
|
|
367
|
+
inconsistentRuns: 0,
|
|
368
|
+
attentionRuns: 0,
|
|
369
|
+
},
|
|
370
|
+
worker: {
|
|
371
|
+
activeTasks: 0,
|
|
372
|
+
activeBatches: 0,
|
|
373
|
+
quietCount: 0,
|
|
374
|
+
staleCount: 0,
|
|
375
|
+
timeoutRiskCount: 0,
|
|
376
|
+
failuresCount: 0,
|
|
377
|
+
},
|
|
378
|
+
},
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
function toPublicTaskSummary(task) {
|
|
382
|
+
const { timeoutMs: _to, commandStartedAt: _cs, ...rest } = task;
|
|
383
|
+
return rest;
|
|
384
|
+
}
|
|
385
|
+
function isLivePoolStatus(status) {
|
|
386
|
+
return (status === "Running" ||
|
|
387
|
+
status === "Queued" ||
|
|
388
|
+
status === "AgentCompleted" ||
|
|
389
|
+
status === "VerificationRunning" ||
|
|
390
|
+
status === "HumanReview" ||
|
|
391
|
+
status === "Ready" ||
|
|
392
|
+
status === "Draft");
|
|
393
|
+
}
|
|
394
|
+
function mergeStateRecord(taskMap, state, warnings) {
|
|
395
|
+
const featureId = state.featureId?.trim();
|
|
396
|
+
if (!featureId) {
|
|
397
|
+
warnings?.push(`state for task ${state.taskId} missing featureId; excluded from composite projection`);
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
const key = taskIdentityKey(featureId, state.taskId);
|
|
401
|
+
const existing = taskMap.get(key) ?? {
|
|
402
|
+
taskId: state.taskId,
|
|
403
|
+
featureId,
|
|
404
|
+
status: "unknown",
|
|
405
|
+
};
|
|
406
|
+
const live = isLivePoolStatus(state.status);
|
|
407
|
+
const mapped = mapStateStatus(state.status);
|
|
408
|
+
taskMap.set(key, {
|
|
409
|
+
...existing,
|
|
410
|
+
featureId,
|
|
411
|
+
// Live Task Pool state is the current attempt truth; do not keep a stale
|
|
412
|
+
// terminal ledger workerRunId/status over an active Running attempt.
|
|
413
|
+
status: live
|
|
414
|
+
? mapped
|
|
415
|
+
: existing.status !== "unknown"
|
|
416
|
+
? existing.status
|
|
417
|
+
: mapped,
|
|
418
|
+
workerRunId: state.workerRunId ?? existing.workerRunId,
|
|
419
|
+
updatedAt: state.updatedAt ?? existing.updatedAt,
|
|
420
|
+
artifactRefs: mergeArtifactRefs(existing.artifactRefs, {
|
|
421
|
+
runRecordPath: state.lastRunRecordPath,
|
|
422
|
+
}),
|
|
423
|
+
failureCategory: existing.failureCategory ?? state.failureCategory,
|
|
424
|
+
recommendedFollowUp: existing.recommendedFollowUp ?? state.recommendedFollowUp,
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
/**
|
|
428
|
+
* Legacy flat states/<taskId>.json: only merge when the taskId already maps to
|
|
429
|
+
* exactly one composite row in the snapshot (unique ownership). Otherwise warn+exclude.
|
|
430
|
+
*/
|
|
431
|
+
function mergeLegacyStateRecord(taskMap, state, warnings) {
|
|
432
|
+
const owners = [...taskMap.values()].filter((task) => task.taskId === state.taskId && Boolean(task.featureId));
|
|
433
|
+
if (owners.length === 1) {
|
|
434
|
+
const owner = owners[0];
|
|
435
|
+
mergeStateRecord(taskMap, { ...state, featureId: owner.featureId }, warnings);
|
|
436
|
+
warnings.push(`legacy flat state for task ${state.taskId} attached to unique feature ${owner.featureId}`);
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
if (owners.length === 0) {
|
|
440
|
+
// No owned composite row yet: still surface as a warning and do not invent featureId.
|
|
441
|
+
warnings.push(`legacy flat state for task ${state.taskId} has no unique feature ownership; excluded from composite projection`);
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
warnings.push(`legacy flat state for task ${state.taskId} is ambiguous across features; excluded from composite projection`);
|
|
445
|
+
}
|
|
446
|
+
function mergeLedgerRun(taskMap, run, warnings) {
|
|
447
|
+
const featureId = run.featureId?.trim();
|
|
448
|
+
if (!featureId) {
|
|
449
|
+
warnings?.push(`ledger run ${run.workerRunId} for task ${run.taskId} missing featureId; excluded from composite projection`);
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
const key = taskIdentityKey(featureId, run.taskId);
|
|
453
|
+
const existing = taskMap.get(key) ?? {
|
|
454
|
+
taskId: run.taskId,
|
|
455
|
+
featureId,
|
|
456
|
+
status: "unknown",
|
|
457
|
+
};
|
|
458
|
+
const sameRun = existing.workerRunId === run.workerRunId;
|
|
459
|
+
const runArtifactRefs = {
|
|
460
|
+
runRecordPath: run.runRecordPath,
|
|
461
|
+
dagPath: run.dagPath,
|
|
462
|
+
...run.failureArtifacts,
|
|
463
|
+
...(run.outcomePath ? { outcome: run.outcomePath } : {}),
|
|
464
|
+
};
|
|
465
|
+
taskMap.set(key, {
|
|
466
|
+
...(sameRun
|
|
467
|
+
? existing
|
|
468
|
+
: {
|
|
469
|
+
taskId: run.taskId,
|
|
470
|
+
featureId,
|
|
471
|
+
status: "unknown",
|
|
472
|
+
harnessTaskId: existing.harnessTaskId,
|
|
473
|
+
}),
|
|
474
|
+
batchRunId: run.batchRunId,
|
|
475
|
+
featureId,
|
|
476
|
+
workflow: run.workflow ?? existing.workflow,
|
|
477
|
+
status: mapRunRecordStatus(run.status),
|
|
478
|
+
workerRunId: run.workerRunId,
|
|
479
|
+
harnessTaskId: run.harnessTaskId ?? existing.harnessTaskId,
|
|
480
|
+
finishedAt: run.recordedAt ?? (sameRun ? existing.finishedAt : undefined),
|
|
481
|
+
failureCategory: run.failureCategory ?? (sameRun ? existing.failureCategory : undefined),
|
|
482
|
+
recommendedFollowUp: run.recommendedFollowUp ??
|
|
483
|
+
(sameRun ? existing.recommendedFollowUp : undefined),
|
|
484
|
+
artifactRefs: mergeArtifactRefs(sameRun ? existing.artifactRefs : undefined, runArtifactRefs),
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
/**
|
|
488
|
+
* Build workerRunId → featureId unique index from ledger + already-owned task rows + run owners.
|
|
489
|
+
* Ambiguous (0 or N) mappings are omitted so callers treat them as unowned.
|
|
490
|
+
*/
|
|
491
|
+
function buildWorkerRunFeatureIndex(ledgerRuns, taskMap, owners) {
|
|
492
|
+
const candidates = new Map();
|
|
493
|
+
const add = (workerRunId, featureId) => {
|
|
494
|
+
const wr = workerRunId?.trim();
|
|
495
|
+
const fid = featureId?.trim();
|
|
496
|
+
if (!wr || !fid)
|
|
497
|
+
return;
|
|
498
|
+
let set = candidates.get(wr);
|
|
499
|
+
if (!set) {
|
|
500
|
+
set = new Set();
|
|
501
|
+
candidates.set(wr, set);
|
|
502
|
+
}
|
|
503
|
+
set.add(fid);
|
|
504
|
+
};
|
|
505
|
+
for (const run of ledgerRuns) {
|
|
506
|
+
add(run.workerRunId, run.featureId);
|
|
507
|
+
}
|
|
508
|
+
if (taskMap) {
|
|
509
|
+
for (const task of taskMap.values()) {
|
|
510
|
+
add(task.workerRunId, task.featureId);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
if (owners) {
|
|
514
|
+
for (const owner of owners) {
|
|
515
|
+
add(owner.workerRunId, owner.featureId);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
const unique = new Map();
|
|
519
|
+
for (const [workerRunId, features] of candidates) {
|
|
520
|
+
if (features.size === 1) {
|
|
521
|
+
unique.set(workerRunId, [...features][0]);
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
return unique;
|
|
525
|
+
}
|
|
526
|
+
function mergeEvents(taskMap, events, workerRunFeatureIndex, warnings) {
|
|
527
|
+
const sorted = [...events].sort((a, b) => a.at.localeCompare(b.at));
|
|
528
|
+
const unownedKeys = new Set();
|
|
529
|
+
for (const event of sorted) {
|
|
530
|
+
if (event.taskId) {
|
|
531
|
+
applyTaskEvent(taskMap, event, workerRunFeatureIndex, warnings, unownedKeys);
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
function applyTaskEvent(taskMap, event, workerRunFeatureIndex, warnings, unownedKeys) {
|
|
536
|
+
const taskId = event.taskId;
|
|
537
|
+
const featureId = resolveEventFeatureId(event, taskMap, workerRunFeatureIndex, warnings, unownedKeys);
|
|
538
|
+
if (!featureId)
|
|
539
|
+
return;
|
|
540
|
+
const key = taskIdentityKey(featureId, taskId);
|
|
541
|
+
const existing = taskMap.get(key) ?? {
|
|
542
|
+
taskId,
|
|
543
|
+
featureId,
|
|
544
|
+
status: "unknown",
|
|
545
|
+
};
|
|
546
|
+
taskMap.set(key, applyEventToSummary(existing, event));
|
|
547
|
+
}
|
|
548
|
+
function resolveEventFeatureId(event, taskMap, workerRunFeatureIndex, warnings, unownedKeys) {
|
|
549
|
+
const taskId = event.taskId;
|
|
550
|
+
if (!taskId)
|
|
551
|
+
return undefined;
|
|
552
|
+
const eventFeatureId = event.featureId?.trim();
|
|
553
|
+
if (event.workerRunId) {
|
|
554
|
+
const fromIndex = workerRunFeatureIndex.get(event.workerRunId);
|
|
555
|
+
if (eventFeatureId && fromIndex && eventFeatureId !== fromIndex) {
|
|
556
|
+
pushOwnershipWarning(warnings, unownedKeys, `ownership conflict for workerRunId ${event.workerRunId}: event featureId ${eventFeatureId} vs index ${fromIndex}`, event.workerRunId);
|
|
557
|
+
return undefined;
|
|
558
|
+
}
|
|
559
|
+
if (eventFeatureId)
|
|
560
|
+
return eventFeatureId;
|
|
561
|
+
if (fromIndex)
|
|
562
|
+
return fromIndex;
|
|
563
|
+
const byWorkerRun = [...taskMap.values()].filter((task) => task.workerRunId === event.workerRunId && Boolean(task.featureId));
|
|
564
|
+
if (byWorkerRun.length === 1)
|
|
565
|
+
return byWorkerRun[0].featureId;
|
|
566
|
+
}
|
|
567
|
+
else if (eventFeatureId) {
|
|
568
|
+
return eventFeatureId;
|
|
569
|
+
}
|
|
570
|
+
const byTaskId = [...taskMap.values()].filter((task) => task.taskId === taskId && Boolean(task.featureId));
|
|
571
|
+
if (byTaskId.length === 1)
|
|
572
|
+
return byTaskId[0].featureId;
|
|
573
|
+
pushOwnershipWarning(warnings, unownedKeys, `unowned event ${event.type} for task ${taskId}` +
|
|
574
|
+
(event.workerRunId ? ` workerRunId ${event.workerRunId}` : "") +
|
|
575
|
+
` cannot uniquely resolve featureId; excluded from task projection`, event.workerRunId ?? `${taskId}:${event.type}`);
|
|
576
|
+
return undefined;
|
|
577
|
+
}
|
|
578
|
+
function pushOwnershipWarning(warnings, unownedKeys, message, key) {
|
|
579
|
+
if (!warnings)
|
|
580
|
+
return;
|
|
581
|
+
if (unownedKeys) {
|
|
582
|
+
if (unownedKeys.has(key))
|
|
583
|
+
return;
|
|
584
|
+
unownedKeys.add(key);
|
|
585
|
+
}
|
|
586
|
+
warnings.push(message);
|
|
587
|
+
}
|
|
588
|
+
function applyEventToSummary(existing, event) {
|
|
589
|
+
const updated = {
|
|
590
|
+
...existing,
|
|
591
|
+
batchRunId: event.batchRunId ?? existing.batchRunId,
|
|
592
|
+
featureId: existing.featureId,
|
|
593
|
+
workerRunId: event.workerRunId ?? existing.workerRunId,
|
|
594
|
+
harnessTaskId: event.harnessTaskId ?? existing.harnessTaskId,
|
|
595
|
+
dagRunId: event.dagRunId ?? existing.dagRunId,
|
|
596
|
+
failureCategory: event.failureCategory ?? existing.failureCategory,
|
|
597
|
+
recommendedFollowUp: event.recommendedFollowUp ?? existing.recommendedFollowUp,
|
|
598
|
+
artifactRefs: mergeArtifactRefs(existing.artifactRefs, event.artifactRefs),
|
|
599
|
+
};
|
|
600
|
+
const derivedStatus = deriveEventStatus(event);
|
|
601
|
+
if (derivedStatus) {
|
|
602
|
+
updated.status = derivedStatus;
|
|
603
|
+
}
|
|
604
|
+
if (event.type === "task.started" || event.type === "step.started") {
|
|
605
|
+
if (!updated.startedAt || event.at < updated.startedAt) {
|
|
606
|
+
updated.startedAt = event.at;
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
if (event.type === "task.finished" || event.type === "task.reused") {
|
|
610
|
+
updated.finishedAt = event.at;
|
|
611
|
+
if (event.durationMs !== undefined) {
|
|
612
|
+
updated.durationMs = event.durationMs;
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
if (event.type === "command.heartbeat" || event.type === "step.heartbeat") {
|
|
616
|
+
updated.lastHeartbeatAt = event.at;
|
|
617
|
+
}
|
|
618
|
+
if (event.type === "command.output" ||
|
|
619
|
+
event.type === "step.output" ||
|
|
620
|
+
event.type === "dag.node.output") {
|
|
621
|
+
updated.lastOutputAt = event.at;
|
|
622
|
+
}
|
|
623
|
+
if (event.type === "command.started") {
|
|
624
|
+
if (event.timeoutMs !== undefined)
|
|
625
|
+
updated.timeoutMs = event.timeoutMs;
|
|
626
|
+
if (event.pid !== undefined)
|
|
627
|
+
updated.pid = event.pid;
|
|
628
|
+
updated.commandStartedAt = event.at;
|
|
629
|
+
}
|
|
630
|
+
if (event.type === "command.finished") {
|
|
631
|
+
delete updated.timeoutMs;
|
|
632
|
+
delete updated.pid;
|
|
633
|
+
delete updated.commandStartedAt;
|
|
634
|
+
}
|
|
635
|
+
return updated;
|
|
636
|
+
}
|
|
637
|
+
function buildHistoricalRunMap(ledgerRuns, events, workerRunFeatureIndex = new Map(), warnings) {
|
|
638
|
+
const runMap = new Map();
|
|
639
|
+
const unownedKeys = new Set();
|
|
640
|
+
for (const run of ledgerRuns) {
|
|
641
|
+
// History map is keyed by workerRunId (run-scoped); featureId still required when present.
|
|
642
|
+
runMap.set(run.workerRunId, {
|
|
643
|
+
taskId: run.taskId,
|
|
644
|
+
batchRunId: run.batchRunId,
|
|
645
|
+
featureId: run.featureId,
|
|
646
|
+
workflow: run.workflow,
|
|
647
|
+
status: mapRunRecordStatus(run.status),
|
|
648
|
+
workerRunId: run.workerRunId,
|
|
649
|
+
harnessTaskId: run.harnessTaskId,
|
|
650
|
+
finishedAt: run.recordedAt,
|
|
651
|
+
failureCategory: run.failureCategory,
|
|
652
|
+
recommendedFollowUp: run.recommendedFollowUp,
|
|
653
|
+
artifactRefs: mergeArtifactRefs(undefined, {
|
|
654
|
+
runRecordPath: run.runRecordPath,
|
|
655
|
+
dagPath: run.dagPath,
|
|
656
|
+
...run.failureArtifacts,
|
|
657
|
+
...(run.outcomePath ? { outcome: run.outcomePath } : {}),
|
|
658
|
+
}),
|
|
659
|
+
});
|
|
660
|
+
}
|
|
661
|
+
for (const event of [...events].sort((a, b) => a.at.localeCompare(b.at))) {
|
|
662
|
+
if (!event.workerRunId || !event.taskId)
|
|
663
|
+
continue;
|
|
664
|
+
const existing = runMap.get(event.workerRunId);
|
|
665
|
+
if (existing) {
|
|
666
|
+
if (event.featureId?.trim() &&
|
|
667
|
+
existing.featureId &&
|
|
668
|
+
event.featureId.trim() !== existing.featureId) {
|
|
669
|
+
pushOwnershipWarning(warnings, unownedKeys, `ownership conflict for workerRunId ${event.workerRunId}: event featureId ${event.featureId} vs historical ${existing.featureId}`, event.workerRunId);
|
|
670
|
+
continue;
|
|
671
|
+
}
|
|
672
|
+
runMap.set(event.workerRunId, applyEventToSummary(existing, event));
|
|
673
|
+
continue;
|
|
674
|
+
}
|
|
675
|
+
const featureId = event.featureId?.trim() || workerRunFeatureIndex.get(event.workerRunId);
|
|
676
|
+
if (!featureId) {
|
|
677
|
+
pushOwnershipWarning(warnings, unownedKeys, `unowned historical event ${event.type} for task ${event.taskId} workerRunId ${event.workerRunId} cannot uniquely resolve featureId; excluded from historical run map`, event.workerRunId);
|
|
678
|
+
continue;
|
|
679
|
+
}
|
|
680
|
+
runMap.set(event.workerRunId, applyEventToSummary({
|
|
681
|
+
taskId: event.taskId,
|
|
682
|
+
featureId,
|
|
683
|
+
workerRunId: event.workerRunId,
|
|
684
|
+
status: "unknown",
|
|
685
|
+
}, event));
|
|
686
|
+
}
|
|
687
|
+
return runMap;
|
|
688
|
+
}
|
|
689
|
+
function deriveEventStatus(event) {
|
|
690
|
+
switch (event.type) {
|
|
691
|
+
case "task.queued":
|
|
692
|
+
return event.status ?? "pending";
|
|
693
|
+
case "task.started":
|
|
694
|
+
return event.status ?? "running";
|
|
695
|
+
case "task.reused":
|
|
696
|
+
return event.status ?? "reused";
|
|
697
|
+
case "task.finished":
|
|
698
|
+
return event.status ?? "succeeded";
|
|
699
|
+
case "state.updated":
|
|
700
|
+
return event.status;
|
|
701
|
+
case "failure.routed":
|
|
702
|
+
return "failed";
|
|
703
|
+
default:
|
|
704
|
+
return undefined;
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
function mergeHandoffs(taskMap, handoffs) {
|
|
708
|
+
for (const handoff of handoffs) {
|
|
709
|
+
for (const task of taskMap.values()) {
|
|
710
|
+
if (task.workerRunId === handoff.workerRunId) {
|
|
711
|
+
task.artifactRefs = mergeArtifactRefs(task.artifactRefs, {
|
|
712
|
+
closeoutDraft: handoff.closeoutDraftPath,
|
|
713
|
+
});
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
function applyStaleDetection(taskMap, now, staleThresholdMs, quietThresholdMs) {
|
|
719
|
+
const nowMs = now.getTime();
|
|
720
|
+
for (const task of taskMap.values()) {
|
|
721
|
+
if (task.status !== "running")
|
|
722
|
+
continue;
|
|
723
|
+
try {
|
|
724
|
+
computeLiveness(task, nowMs, staleThresholdMs, quietThresholdMs);
|
|
725
|
+
}
|
|
726
|
+
catch {
|
|
727
|
+
// Fault tolerance: keep task as-is if liveness computation fails.
|
|
728
|
+
task.liveness = "active";
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
function computeLiveness(task, nowMs, staleThresholdMs, quietThresholdMs) {
|
|
733
|
+
const startedAt = task.startedAt;
|
|
734
|
+
let elapsedMs;
|
|
735
|
+
if (startedAt) {
|
|
736
|
+
const startedMs = Date.parse(startedAt);
|
|
737
|
+
if (!Number.isNaN(startedMs)) {
|
|
738
|
+
elapsedMs = Math.max(0, nowMs - startedMs);
|
|
739
|
+
task.elapsedMs = elapsedMs;
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
const timeoutMs = task.timeoutMs;
|
|
743
|
+
let commandElapsedMs;
|
|
744
|
+
const commandStartedMs = task.commandStartedAt
|
|
745
|
+
? Date.parse(task.commandStartedAt)
|
|
746
|
+
: NaN;
|
|
747
|
+
if (!Number.isNaN(commandStartedMs)) {
|
|
748
|
+
commandElapsedMs = Math.max(0, nowMs - commandStartedMs);
|
|
749
|
+
if (timeoutMs !== undefined) {
|
|
750
|
+
task.timeoutAt = new Date(commandStartedMs + timeoutMs).toISOString();
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
const heartbeatMs = task.lastHeartbeatAt
|
|
754
|
+
? Date.parse(task.lastHeartbeatAt)
|
|
755
|
+
: NaN;
|
|
756
|
+
const outputMs = task.lastOutputAt ? Date.parse(task.lastOutputAt) : NaN;
|
|
757
|
+
// Priority 1: timeout-risk (elapsed > 0.8 * timeoutMs), only if timeoutMs known.
|
|
758
|
+
if (timeoutMs !== undefined &&
|
|
759
|
+
commandElapsedMs !== undefined &&
|
|
760
|
+
commandElapsedMs > 0.8 * timeoutMs) {
|
|
761
|
+
task.liveness = "timeout-risk";
|
|
762
|
+
return;
|
|
763
|
+
}
|
|
764
|
+
// Priority 2: stale.
|
|
765
|
+
// (a) Heartbeat present but older than staleThresholdMs.
|
|
766
|
+
// (b) No heartbeat but running longer than staleThresholdMs (startedAt known).
|
|
767
|
+
const heartbeatStale = !Number.isNaN(heartbeatMs) && nowMs - heartbeatMs > staleThresholdMs;
|
|
768
|
+
const noHeartbeatStale = Number.isNaN(heartbeatMs) &&
|
|
769
|
+
elapsedMs !== undefined &&
|
|
770
|
+
elapsedMs > staleThresholdMs;
|
|
771
|
+
if (heartbeatStale || noHeartbeatStale) {
|
|
772
|
+
task.liveness = "stale";
|
|
773
|
+
task.status = "stale";
|
|
774
|
+
return;
|
|
775
|
+
}
|
|
776
|
+
// Priority 3: quiet — alive (recent heartbeat) but output older than quietThresholdMs.
|
|
777
|
+
const quietBaselineMs = !Number.isNaN(outputMs) ? outputMs : commandStartedMs;
|
|
778
|
+
if (!Number.isNaN(heartbeatMs) &&
|
|
779
|
+
!Number.isNaN(quietBaselineMs) &&
|
|
780
|
+
nowMs - quietBaselineMs > quietThresholdMs) {
|
|
781
|
+
task.liveness = "quiet";
|
|
782
|
+
return;
|
|
783
|
+
}
|
|
784
|
+
// Default: active.
|
|
785
|
+
task.liveness = "active";
|
|
786
|
+
}
|
|
787
|
+
function buildBatches(ledgerBatches, events, taskMap) {
|
|
788
|
+
const batchMap = new Map();
|
|
789
|
+
for (const batch of ledgerBatches) {
|
|
790
|
+
batchMap.set(batch.batchRunId, { ...batch, tasks: [] });
|
|
791
|
+
}
|
|
792
|
+
for (const event of events) {
|
|
793
|
+
if (!event.batchRunId)
|
|
794
|
+
continue;
|
|
795
|
+
if (!batchMap.has(event.batchRunId)) {
|
|
796
|
+
batchMap.set(event.batchRunId, {
|
|
797
|
+
batchRunId: event.batchRunId,
|
|
798
|
+
status: "unknown",
|
|
799
|
+
tasks: [],
|
|
800
|
+
});
|
|
801
|
+
}
|
|
802
|
+
const batch = batchMap.get(event.batchRunId);
|
|
803
|
+
if (event.type === "batch.started") {
|
|
804
|
+
batch.startedAt = event.at;
|
|
805
|
+
batch.status = "running";
|
|
806
|
+
}
|
|
807
|
+
if (event.type === "batch.finished") {
|
|
808
|
+
batch.finishedAt = event.at;
|
|
809
|
+
batch.status = event.status ?? "succeeded";
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
for (const task of taskMap.values()) {
|
|
813
|
+
if (!task.batchRunId)
|
|
814
|
+
continue;
|
|
815
|
+
if (!batchMap.has(task.batchRunId)) {
|
|
816
|
+
batchMap.set(task.batchRunId, {
|
|
817
|
+
batchRunId: task.batchRunId,
|
|
818
|
+
status: "unknown",
|
|
819
|
+
tasks: [],
|
|
820
|
+
});
|
|
821
|
+
}
|
|
822
|
+
const batch = batchMap.get(task.batchRunId);
|
|
823
|
+
batch.featureId = batch.featureId ?? task.featureId;
|
|
824
|
+
batch.tasks.push(toPublicTaskSummary(task));
|
|
825
|
+
}
|
|
826
|
+
for (const batch of batchMap.values()) {
|
|
827
|
+
batch.tasks.sort(compareTaskIdentity);
|
|
828
|
+
if (batch.summary === undefined && batch.tasks.length > 0) {
|
|
829
|
+
batch.summary = summarizeTasks(batch.tasks);
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
return [...batchMap.values()].sort((a, b) => a.batchRunId.localeCompare(b.batchRunId));
|
|
833
|
+
}
|
|
834
|
+
function summarizeTasks(tasks) {
|
|
835
|
+
let succeeded = 0;
|
|
836
|
+
let failed = 0;
|
|
837
|
+
let reused = 0;
|
|
838
|
+
for (const task of tasks) {
|
|
839
|
+
if (task.status === "succeeded")
|
|
840
|
+
succeeded++;
|
|
841
|
+
else if (task.status === "failed")
|
|
842
|
+
failed++;
|
|
843
|
+
else if (task.status === "reused")
|
|
844
|
+
reused++;
|
|
845
|
+
}
|
|
846
|
+
return { total: tasks.length, succeeded, failed, reused };
|
|
847
|
+
}
|
|
848
|
+
function computeHealth(batches, tasks, dagRuns) {
|
|
849
|
+
const worker = {
|
|
850
|
+
activeTasks: tasks.filter((t) => ACTIVE_TASK_STATUSES.has(t.status)).length,
|
|
851
|
+
activeBatches: batches.filter((b) => ACTIVE_BATCH_STATUSES.has(b.status))
|
|
852
|
+
.length,
|
|
853
|
+
quietCount: tasks.filter((t) => t.liveness === "quiet").length,
|
|
854
|
+
staleCount: tasks.filter((t) => t.status === "stale").length,
|
|
855
|
+
timeoutRiskCount: tasks.filter((t) => t.liveness === "timeout-risk").length,
|
|
856
|
+
failuresCount: tasks.filter((t) => t.status === "failed").length,
|
|
857
|
+
};
|
|
858
|
+
const dag = computeDagHealth(dagRuns);
|
|
859
|
+
return {
|
|
860
|
+
// Flat aliases retained for back-compat; mirror worker.* plus activeBatches.
|
|
861
|
+
activeBatches: worker.activeBatches,
|
|
862
|
+
activeTasks: worker.activeTasks,
|
|
863
|
+
quietCount: worker.quietCount,
|
|
864
|
+
staleCount: worker.staleCount,
|
|
865
|
+
timeoutRiskCount: worker.timeoutRiskCount,
|
|
866
|
+
failuresCount: worker.failuresCount,
|
|
867
|
+
dag,
|
|
868
|
+
worker,
|
|
869
|
+
};
|
|
870
|
+
}
|
|
871
|
+
const ACTIVE_EFFECTIVE_STATUSES = new Set([
|
|
872
|
+
"running",
|
|
873
|
+
"running-quiet",
|
|
874
|
+
"running-suspected-stall",
|
|
875
|
+
"needs-attention",
|
|
876
|
+
"remote-unknown",
|
|
877
|
+
"pending",
|
|
878
|
+
"paused",
|
|
879
|
+
]);
|
|
880
|
+
const TERMINAL_RUN_STATUSES = new Set([
|
|
881
|
+
"finished",
|
|
882
|
+
"failed",
|
|
883
|
+
"partial_failed",
|
|
884
|
+
"superseded",
|
|
885
|
+
"abandoned",
|
|
886
|
+
]);
|
|
887
|
+
const NON_EXECUTION_MODES = new Set(["dry-run", "init-only"]);
|
|
888
|
+
const SHELL_NODE_STATUSES = new Set(["pending", "queued"]);
|
|
889
|
+
function hasDagExecutionEvidence(dag) {
|
|
890
|
+
const status = (dag.status ?? "").toLowerCase();
|
|
891
|
+
if (status === "running" || status === "started" || status === "paused") {
|
|
892
|
+
return true;
|
|
893
|
+
}
|
|
894
|
+
return (dag.nodes ?? []).some((node) => {
|
|
895
|
+
const nodeStatus = (node.status ?? "").toLowerCase();
|
|
896
|
+
return nodeStatus.length > 0 && !SHELL_NODE_STATUSES.has(nodeStatus);
|
|
897
|
+
});
|
|
898
|
+
}
|
|
899
|
+
/**
|
|
900
|
+
* Unified overview-active matrix (isOverviewActive). Shared contract with
|
|
901
|
+
* Observe UI `isDagRunActive` — default total view only shows real execution.
|
|
902
|
+
*/
|
|
903
|
+
function isDagRunActiveForHealth(dag) {
|
|
904
|
+
const executionMode = (dag.executionMode ?? "").toLowerCase();
|
|
905
|
+
if (NON_EXECUTION_MODES.has(executionMode))
|
|
906
|
+
return false;
|
|
907
|
+
const lifecycle = (dag.lifecycle ?? "").toLowerCase();
|
|
908
|
+
if (NON_EXECUTION_MODES.has(lifecycle))
|
|
909
|
+
return false;
|
|
910
|
+
if (lifecycle === "completed")
|
|
911
|
+
return false;
|
|
912
|
+
if (dag.effectiveStatus && TERMINAL_RUN_STATUSES.has(dag.effectiveStatus)) {
|
|
913
|
+
return false;
|
|
914
|
+
}
|
|
915
|
+
// Real execute pause remains overview-visible (AC-003).
|
|
916
|
+
if (lifecycle === "paused" || dag.effectiveStatus === "paused") {
|
|
917
|
+
return true;
|
|
918
|
+
}
|
|
919
|
+
if (["orphaned", "stale"].includes((dag.liveness ?? "").toLowerCase())) {
|
|
920
|
+
return false;
|
|
921
|
+
}
|
|
922
|
+
if (dag.effectiveStatus &&
|
|
923
|
+
ACTIVE_EFFECTIVE_STATUSES.has(dag.effectiveStatus)) {
|
|
924
|
+
// Legacy active+pending shells without execution evidence are not active.
|
|
925
|
+
if (!executionMode &&
|
|
926
|
+
dag.effectiveStatus === "pending" &&
|
|
927
|
+
!hasDagExecutionEvidence(dag)) {
|
|
928
|
+
return false;
|
|
929
|
+
}
|
|
930
|
+
// execute-mode pending (or any non-legacy execute) stays visible.
|
|
931
|
+
return true;
|
|
932
|
+
}
|
|
933
|
+
// Only "unknown" (and absent) means liveness evidence is insufficient;
|
|
934
|
+
// it must NOT override raw lifecycle/status/node facts. Every other concrete
|
|
935
|
+
// effectiveStatus is authoritative-non-active and short-circuits to false.
|
|
936
|
+
if (dag.effectiveStatus && dag.effectiveStatus !== "unknown")
|
|
937
|
+
return false;
|
|
938
|
+
const status = (dag.status ?? "").toLowerCase();
|
|
939
|
+
if (TERMINAL_RUN_STATUSES.has(status))
|
|
940
|
+
return false;
|
|
941
|
+
if (executionMode === "execute") {
|
|
942
|
+
if (["running", "pending", "started", "paused"].includes(status)) {
|
|
943
|
+
return true;
|
|
944
|
+
}
|
|
945
|
+
return (dag.nodes ?? []).some((node) => isNodeStatusActive(node.status));
|
|
946
|
+
}
|
|
947
|
+
// Historical runs without executionMode: require real execution evidence.
|
|
948
|
+
if (status === "running" || status === "started")
|
|
949
|
+
return true;
|
|
950
|
+
if (status === "pending" || status === "") {
|
|
951
|
+
return hasDagExecutionEvidence(dag);
|
|
952
|
+
}
|
|
953
|
+
return (dag.nodes ?? []).some((node) => isNodeStatusActive(node.status));
|
|
954
|
+
}
|
|
955
|
+
function isNodeStatusActive(status) {
|
|
956
|
+
const normalized = (status ?? "").toLowerCase();
|
|
957
|
+
if ([
|
|
958
|
+
"finished",
|
|
959
|
+
"completed",
|
|
960
|
+
"succeeded",
|
|
961
|
+
"done",
|
|
962
|
+
"error",
|
|
963
|
+
"failed",
|
|
964
|
+
"skipped",
|
|
965
|
+
"partial_failed",
|
|
966
|
+
].includes(normalized)) {
|
|
967
|
+
return false;
|
|
968
|
+
}
|
|
969
|
+
// pending/queued alone are not enough for overview active without mode/evidence.
|
|
970
|
+
if (SHELL_NODE_STATUSES.has(normalized))
|
|
971
|
+
return false;
|
|
972
|
+
return true;
|
|
973
|
+
}
|
|
974
|
+
function computeDagHealth(dagRuns) {
|
|
975
|
+
let activeRuns = 0;
|
|
976
|
+
let runningNodes = 0;
|
|
977
|
+
let pendingNodes = 0;
|
|
978
|
+
let pausedRuns = 0;
|
|
979
|
+
let failedRuns = 0;
|
|
980
|
+
let staleRuns = 0;
|
|
981
|
+
let interruptedRuns = 0;
|
|
982
|
+
let inconsistentRuns = 0;
|
|
983
|
+
let attentionRuns = 0;
|
|
984
|
+
for (const dag of dagRuns) {
|
|
985
|
+
const active = isDagRunActiveForHealth(dag);
|
|
986
|
+
if (active) {
|
|
987
|
+
activeRuns++;
|
|
988
|
+
for (const node of dag.nodes ?? []) {
|
|
989
|
+
const status = (node.status ?? "").toLowerCase();
|
|
990
|
+
if (status === "running" || status === "started")
|
|
991
|
+
runningNodes++;
|
|
992
|
+
if (status === "pending" || status === "queued")
|
|
993
|
+
pendingNodes++;
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
const lifecycle = (dag.lifecycle ?? "").toLowerCase();
|
|
997
|
+
const liveness = (dag.liveness ?? "").toLowerCase();
|
|
998
|
+
if (lifecycle === "paused")
|
|
999
|
+
pausedRuns++;
|
|
1000
|
+
if (dag.effectiveStatus === "failed")
|
|
1001
|
+
failedRuns++;
|
|
1002
|
+
if (liveness === "stale" ||
|
|
1003
|
+
liveness === "orphaned" ||
|
|
1004
|
+
liveness === "suspected-stall" ||
|
|
1005
|
+
liveness === "needs-attention") {
|
|
1006
|
+
staleRuns++;
|
|
1007
|
+
}
|
|
1008
|
+
if (dag.effectiveStatus === "interrupted")
|
|
1009
|
+
interruptedRuns++;
|
|
1010
|
+
if (dag.stateConsistent === false)
|
|
1011
|
+
inconsistentRuns++;
|
|
1012
|
+
if (lifecycle !== "completed" &&
|
|
1013
|
+
(lifecycle === "paused" ||
|
|
1014
|
+
liveness === "stale" ||
|
|
1015
|
+
liveness === "orphaned" ||
|
|
1016
|
+
dag.effectiveStatus === "interrupted" ||
|
|
1017
|
+
dag.effectiveStatus === "remote-unknown" ||
|
|
1018
|
+
dag.stateConsistent === false)) {
|
|
1019
|
+
attentionRuns++;
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
return {
|
|
1023
|
+
activeRuns,
|
|
1024
|
+
runningNodes,
|
|
1025
|
+
pendingNodes,
|
|
1026
|
+
pausedRuns,
|
|
1027
|
+
failedRuns,
|
|
1028
|
+
staleRuns,
|
|
1029
|
+
interruptedRuns,
|
|
1030
|
+
inconsistentRuns,
|
|
1031
|
+
attentionRuns,
|
|
1032
|
+
};
|
|
1033
|
+
}
|
|
1034
|
+
/**
|
|
1035
|
+
* Whitelist a short safe projection-failure message. Never expose stack frames,
|
|
1036
|
+
* absolute paths, env values, or secrets. Mirrors the bare catch that previously
|
|
1037
|
+
* silently returned a zero-shape snapshot.
|
|
1038
|
+
*/
|
|
1039
|
+
function sanitizeProjectionError(error) {
|
|
1040
|
+
const fallback = "投影失败 (projection failed)";
|
|
1041
|
+
const at = new Date().toISOString();
|
|
1042
|
+
if (!error || typeof error !== "object") {
|
|
1043
|
+
return { message: fallback, at };
|
|
1044
|
+
}
|
|
1045
|
+
let name;
|
|
1046
|
+
let message;
|
|
1047
|
+
try {
|
|
1048
|
+
name =
|
|
1049
|
+
typeof error.name === "string"
|
|
1050
|
+
? error.name
|
|
1051
|
+
: undefined;
|
|
1052
|
+
message =
|
|
1053
|
+
typeof error.message === "string"
|
|
1054
|
+
? error.message
|
|
1055
|
+
: undefined;
|
|
1056
|
+
}
|
|
1057
|
+
catch {
|
|
1058
|
+
return { message: fallback, at };
|
|
1059
|
+
}
|
|
1060
|
+
const candidate = message ?? name;
|
|
1061
|
+
if (!candidate) {
|
|
1062
|
+
return { message: fallback, at };
|
|
1063
|
+
}
|
|
1064
|
+
// Reject anything that looks like a stack frame, an absolute path, or a secret.
|
|
1065
|
+
const raw = String(candidate);
|
|
1066
|
+
if (raw.includes("\n") ||
|
|
1067
|
+
/\bat \S+/i.test(raw) ||
|
|
1068
|
+
/[A-Za-z]:\\/.test(raw) ||
|
|
1069
|
+
raw.includes("/Users/") ||
|
|
1070
|
+
raw.includes("/home/") ||
|
|
1071
|
+
/SECRET|TOKEN|PASSWORD|API_KEY/i.test(raw)) {
|
|
1072
|
+
return { message: name ? `${name}: 投影失败` : fallback, at };
|
|
1073
|
+
}
|
|
1074
|
+
const trimmed = raw.trim().slice(0, 200);
|
|
1075
|
+
return { message: trimmed || fallback, at };
|
|
1076
|
+
}
|
|
1077
|
+
function mergeArtifactRefs(existing, incoming) {
|
|
1078
|
+
if (!incoming)
|
|
1079
|
+
return existing;
|
|
1080
|
+
const merged = { ...existing };
|
|
1081
|
+
for (const [key, value] of Object.entries(incoming)) {
|
|
1082
|
+
if (!value)
|
|
1083
|
+
continue;
|
|
1084
|
+
if (key === "runRecordPath" ||
|
|
1085
|
+
key === "dagPath" ||
|
|
1086
|
+
key === "reportMarkdown" ||
|
|
1087
|
+
key === "doctorMarkdown" ||
|
|
1088
|
+
key === "closeoutDraft") {
|
|
1089
|
+
merged[key] = value;
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
return Object.keys(merged).length > 0 ? merged : undefined;
|
|
1093
|
+
}
|
|
1094
|
+
function normalizeTaskArtifactRefs(task, repoRoot) {
|
|
1095
|
+
if (!task.artifactRefs)
|
|
1096
|
+
return task;
|
|
1097
|
+
const normalized = {};
|
|
1098
|
+
const keys = [
|
|
1099
|
+
"runRecordPath",
|
|
1100
|
+
"dagPath",
|
|
1101
|
+
"reportMarkdown",
|
|
1102
|
+
"doctorMarkdown",
|
|
1103
|
+
"closeoutDraft",
|
|
1104
|
+
];
|
|
1105
|
+
for (const key of keys) {
|
|
1106
|
+
const value = task.artifactRefs[key];
|
|
1107
|
+
if (!value)
|
|
1108
|
+
continue;
|
|
1109
|
+
const normalizedPath = normalizeRepoRelativePath(repoRoot, value);
|
|
1110
|
+
if (normalizedPath)
|
|
1111
|
+
normalized[key] = normalizedPath;
|
|
1112
|
+
}
|
|
1113
|
+
return {
|
|
1114
|
+
...task,
|
|
1115
|
+
artifactRefs: Object.keys(normalized).length > 0 ? normalized : undefined,
|
|
1116
|
+
};
|
|
1117
|
+
}
|
|
1118
|
+
function normalizeRepoRelativePath(repoRoot, value) {
|
|
1119
|
+
const resolvedRoot = path.resolve(repoRoot);
|
|
1120
|
+
const resolved = path.isAbsolute(value)
|
|
1121
|
+
? path.resolve(value)
|
|
1122
|
+
: path.resolve(resolvedRoot, value);
|
|
1123
|
+
const relative = path.relative(resolvedRoot, resolved);
|
|
1124
|
+
if (relative === "" ||
|
|
1125
|
+
relative === ".." ||
|
|
1126
|
+
relative.startsWith(`..${path.sep}`) ||
|
|
1127
|
+
path.isAbsolute(relative)) {
|
|
1128
|
+
return undefined;
|
|
1129
|
+
}
|
|
1130
|
+
return relative.split(path.sep).join("/");
|
|
1131
|
+
}
|
|
1132
|
+
function mapRunRecordStatus(status) {
|
|
1133
|
+
switch (status) {
|
|
1134
|
+
case "succeeded":
|
|
1135
|
+
return "succeeded";
|
|
1136
|
+
case "failed":
|
|
1137
|
+
return "failed";
|
|
1138
|
+
case "reused":
|
|
1139
|
+
return "reused";
|
|
1140
|
+
case "run-error":
|
|
1141
|
+
case "record-error":
|
|
1142
|
+
return "failed";
|
|
1143
|
+
default:
|
|
1144
|
+
return "unknown";
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
function mapStateStatus(status) {
|
|
1148
|
+
switch (status) {
|
|
1149
|
+
case "Running":
|
|
1150
|
+
return "running";
|
|
1151
|
+
case "Queued":
|
|
1152
|
+
case "Ready":
|
|
1153
|
+
return "pending";
|
|
1154
|
+
case "Done":
|
|
1155
|
+
return "succeeded";
|
|
1156
|
+
case "Failed":
|
|
1157
|
+
return "failed";
|
|
1158
|
+
case "Blocked":
|
|
1159
|
+
return "blocked";
|
|
1160
|
+
default:
|
|
1161
|
+
return "unknown";
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
function mapBatchStatus(status) {
|
|
1165
|
+
switch (status) {
|
|
1166
|
+
case "completed":
|
|
1167
|
+
return "succeeded";
|
|
1168
|
+
case "failed":
|
|
1169
|
+
return "failed";
|
|
1170
|
+
case "running":
|
|
1171
|
+
return "running";
|
|
1172
|
+
default:
|
|
1173
|
+
return "unknown";
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
/** Per-file hard cap for projection merge (snapshot must not embed unbounded ledger). */
|
|
1177
|
+
const SNAPSHOT_EVENTS_MAX_LINES_PER_FILE = 2_000;
|
|
1178
|
+
const SNAPSHOT_EVENTS_MAX_BYTES_PER_FILE = 4 * 1024 * 1024;
|
|
1179
|
+
/** Global hard cap after dedupe for snapshot projection merge. */
|
|
1180
|
+
const SNAPSHOT_EVENTS_MAX_TOTAL = 8_000;
|
|
1181
|
+
const SNAPSHOT_LEDGER_MAX_LINES = 10_000;
|
|
1182
|
+
const SNAPSHOT_LEDGER_MAX_BYTES = 8 * 1024 * 1024;
|
|
1183
|
+
async function loadObservabilityEvents(repoRoot) {
|
|
1184
|
+
const events = [];
|
|
1185
|
+
const warnings = [];
|
|
1186
|
+
const obsRoot = path.join(getTaskPoolRoot(repoRoot), "observability");
|
|
1187
|
+
await appendJsonlEventsBounded(path.join(obsRoot, "events.jsonl"), events, warnings);
|
|
1188
|
+
const batchesDir = path.join(obsRoot, "batches");
|
|
1189
|
+
await forEachSubdirJsonl(batchesDir, "events.jsonl", events, warnings);
|
|
1190
|
+
const runsDir = path.join(obsRoot, "runs");
|
|
1191
|
+
await forEachSubdirJsonl(runsDir, "events.jsonl", events, warnings);
|
|
1192
|
+
const deduped = [
|
|
1193
|
+
...new Map(events.map((event) => [event.id, event])).values(),
|
|
1194
|
+
].sort(compareWorkerEvents);
|
|
1195
|
+
if (deduped.length > SNAPSHOT_EVENTS_MAX_TOTAL) {
|
|
1196
|
+
warnings.push(`observability event projection truncated to ${SNAPSHOT_EVENTS_MAX_TOTAL} events (capacity bound)`);
|
|
1197
|
+
return {
|
|
1198
|
+
events: deduped.slice(-SNAPSHOT_EVENTS_MAX_TOTAL),
|
|
1199
|
+
warnings,
|
|
1200
|
+
};
|
|
1201
|
+
}
|
|
1202
|
+
return { events: deduped, warnings };
|
|
1203
|
+
}
|
|
1204
|
+
function compareWorkerEvents(a, b) {
|
|
1205
|
+
const byTime = a.at.localeCompare(b.at);
|
|
1206
|
+
if (byTime !== 0)
|
|
1207
|
+
return byTime;
|
|
1208
|
+
return a.id.localeCompare(b.id);
|
|
1209
|
+
}
|
|
1210
|
+
async function forEachSubdirJsonl(parentDir, filename, events, warnings) {
|
|
1211
|
+
try {
|
|
1212
|
+
const entries = await readdir(parentDir, { withFileTypes: true });
|
|
1213
|
+
for (const entry of entries) {
|
|
1214
|
+
if (!entry.isDirectory())
|
|
1215
|
+
continue;
|
|
1216
|
+
await appendJsonlEventsBounded(path.join(parentDir, entry.name, filename), events, warnings);
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
catch {
|
|
1220
|
+
// skip missing dir
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
async function appendJsonlEventsBounded(filePath, events, warnings) {
|
|
1224
|
+
const tail = await readBoundedJsonlTail(filePath, {
|
|
1225
|
+
maxBytes: SNAPSHOT_EVENTS_MAX_BYTES_PER_FILE,
|
|
1226
|
+
maxLines: SNAPSHOT_EVENTS_MAX_LINES_PER_FILE,
|
|
1227
|
+
});
|
|
1228
|
+
let accepted = 0;
|
|
1229
|
+
for (const line of tail.lines) {
|
|
1230
|
+
const trimmed = line.text.trim();
|
|
1231
|
+
if (!trimmed)
|
|
1232
|
+
continue;
|
|
1233
|
+
const event = parseWorkerEventLine(trimmed);
|
|
1234
|
+
if (event) {
|
|
1235
|
+
events.push(event);
|
|
1236
|
+
accepted += 1;
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
if (tail.truncated) {
|
|
1240
|
+
warnings.push("truncated event tail for " +
|
|
1241
|
+
path.basename(path.dirname(filePath)) +
|
|
1242
|
+
"/" +
|
|
1243
|
+
path.basename(filePath) +
|
|
1244
|
+
" to newest " +
|
|
1245
|
+
accepted +
|
|
1246
|
+
" valid events");
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
async function loadLedgerRuns(repoRoot) {
|
|
1250
|
+
const runs = [];
|
|
1251
|
+
const warnings = [];
|
|
1252
|
+
const tail = await readBoundedJsonlTail(path.join(getTaskPoolRoot(repoRoot), "runs.jsonl"), {
|
|
1253
|
+
maxBytes: SNAPSHOT_LEDGER_MAX_BYTES,
|
|
1254
|
+
maxLines: SNAPSHOT_LEDGER_MAX_LINES,
|
|
1255
|
+
});
|
|
1256
|
+
if (tail.truncated) {
|
|
1257
|
+
warnings.push("runs.jsonl projection truncated to newest " +
|
|
1258
|
+
SNAPSHOT_LEDGER_MAX_LINES +
|
|
1259
|
+
" lines / " +
|
|
1260
|
+
SNAPSHOT_LEDGER_MAX_BYTES +
|
|
1261
|
+
" bytes");
|
|
1262
|
+
}
|
|
1263
|
+
for (const line of tail.lines) {
|
|
1264
|
+
const trimmed = line.text.trim();
|
|
1265
|
+
if (!trimmed)
|
|
1266
|
+
continue;
|
|
1267
|
+
const parsed = safeParseJson(trimmed);
|
|
1268
|
+
if (!parsed)
|
|
1269
|
+
continue;
|
|
1270
|
+
const taskId = readString(parsed, "taskId");
|
|
1271
|
+
const batchRunId = readString(parsed, "batchRunId");
|
|
1272
|
+
const workerRunId = readString(parsed, "workerRunId");
|
|
1273
|
+
const status = readString(parsed, "status");
|
|
1274
|
+
if (!taskId || !batchRunId || !workerRunId || !status)
|
|
1275
|
+
continue;
|
|
1276
|
+
const failure = readObject(parsed, "failure");
|
|
1277
|
+
const failureArtifacts = readObject(parsed, "failureArtifacts");
|
|
1278
|
+
const retryOfWorkerRunId = readString(parsed, "retryOfWorkerRunId");
|
|
1279
|
+
runs.push({
|
|
1280
|
+
batchRunId,
|
|
1281
|
+
workerRunId,
|
|
1282
|
+
taskId,
|
|
1283
|
+
featureId: readString(parsed, "featureId"),
|
|
1284
|
+
workflow: readString(parsed, "workflow"),
|
|
1285
|
+
outcomePath: readString(parsed, "outcomePath"),
|
|
1286
|
+
status,
|
|
1287
|
+
harnessTaskId: readString(parsed, "harnessTaskId"),
|
|
1288
|
+
runRecordPath: readString(parsed, "runRecordPath"),
|
|
1289
|
+
dagPath: readString(parsed, "dagPath"),
|
|
1290
|
+
recordedAt: readString(parsed, "recordedAt"),
|
|
1291
|
+
...(retryOfWorkerRunId ? { retryOfWorkerRunId } : {}),
|
|
1292
|
+
failureCategory: failure ? readString(failure, "category") : undefined,
|
|
1293
|
+
recommendedFollowUp: failure
|
|
1294
|
+
? readString(failure, "recommendedFollowUpKind")
|
|
1295
|
+
: undefined,
|
|
1296
|
+
failureArtifacts: failureArtifacts
|
|
1297
|
+
? {
|
|
1298
|
+
reportMarkdown: readString(failureArtifacts, "reportMarkdownArtifactPath"),
|
|
1299
|
+
doctorMarkdown: readString(failureArtifacts, "doctorMarkdownArtifactPath"),
|
|
1300
|
+
closeoutDraft: readString(failureArtifacts, "closeoutDraftPath"),
|
|
1301
|
+
}
|
|
1302
|
+
: undefined,
|
|
1303
|
+
});
|
|
1304
|
+
}
|
|
1305
|
+
return { runs, warnings };
|
|
1306
|
+
}
|
|
1307
|
+
async function loadLedgerBatches(repoRoot) {
|
|
1308
|
+
const batches = [];
|
|
1309
|
+
const artifactsDir = path.join(getTaskPoolRoot(repoRoot), "artifacts");
|
|
1310
|
+
try {
|
|
1311
|
+
const entries = await readdir(artifactsDir, { withFileTypes: true });
|
|
1312
|
+
for (const entry of entries) {
|
|
1313
|
+
if (!entry.isDirectory())
|
|
1314
|
+
continue;
|
|
1315
|
+
const batchRunId = entry.name;
|
|
1316
|
+
const batchRunPath = path.join(artifactsDir, batchRunId, "batch-run.json");
|
|
1317
|
+
const parsed = await safeReadJson(batchRunPath);
|
|
1318
|
+
if (!parsed)
|
|
1319
|
+
continue;
|
|
1320
|
+
const summaryObj = readObject(parsed, "summary");
|
|
1321
|
+
let summary;
|
|
1322
|
+
if (summaryObj) {
|
|
1323
|
+
const recordErrors = readNumber(summaryObj, "recordErrors");
|
|
1324
|
+
const runErrors = readNumber(summaryObj, "runErrors");
|
|
1325
|
+
summary = {
|
|
1326
|
+
total: readNumber(summaryObj, "total") ?? 0,
|
|
1327
|
+
succeeded: readNumber(summaryObj, "succeeded") ?? 0,
|
|
1328
|
+
failed: readNumber(summaryObj, "failed") ?? 0,
|
|
1329
|
+
reused: readNumber(summaryObj, "reused") ?? 0,
|
|
1330
|
+
...(recordErrors !== undefined ? { recordErrors } : {}),
|
|
1331
|
+
...(runErrors !== undefined ? { runErrors } : {}),
|
|
1332
|
+
};
|
|
1333
|
+
}
|
|
1334
|
+
batches.push({
|
|
1335
|
+
batchRunId: readString(parsed, "batchRunId") ?? batchRunId,
|
|
1336
|
+
featureId: readString(parsed, "featureId"),
|
|
1337
|
+
startedAt: readString(parsed, "startedAt"),
|
|
1338
|
+
finishedAt: readString(parsed, "finishedAt"),
|
|
1339
|
+
status: mapBatchStatus(readString(parsed, "status") ?? "unknown"),
|
|
1340
|
+
summary,
|
|
1341
|
+
tasks: [],
|
|
1342
|
+
batchRunPath,
|
|
1343
|
+
});
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
catch {
|
|
1347
|
+
// skip
|
|
1348
|
+
}
|
|
1349
|
+
return batches;
|
|
1350
|
+
}
|
|
1351
|
+
async function loadStateRecords(repoRoot) {
|
|
1352
|
+
const states = [];
|
|
1353
|
+
const legacyCandidates = [];
|
|
1354
|
+
const warnings = [];
|
|
1355
|
+
// Primary path: v2 nested states via pool list API (read-only import).
|
|
1356
|
+
try {
|
|
1357
|
+
const v2 = await listTaskPoolStates(repoRoot);
|
|
1358
|
+
for (const record of v2) {
|
|
1359
|
+
if (!record.taskId || !record.status)
|
|
1360
|
+
continue;
|
|
1361
|
+
if (!record.featureId) {
|
|
1362
|
+
warnings.push(`v2 state for task ${record.taskId} missing featureId; excluded from composite projection`);
|
|
1363
|
+
continue;
|
|
1364
|
+
}
|
|
1365
|
+
states.push({
|
|
1366
|
+
taskId: record.taskId,
|
|
1367
|
+
featureId: record.featureId,
|
|
1368
|
+
status: record.status,
|
|
1369
|
+
workerRunId: record.workerRunId,
|
|
1370
|
+
lastRunRecordPath: record.lastRunRecordPath,
|
|
1371
|
+
updatedAt: record.updatedAt,
|
|
1372
|
+
failureCategory: record.failure?.category,
|
|
1373
|
+
recommendedFollowUp: record.failure?.recommendedFollowUpKind,
|
|
1374
|
+
});
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
catch (error) {
|
|
1378
|
+
warnings.push(`failed to load feature-scoped task pool states: ${error instanceof Error ? error.message : String(error)}`);
|
|
1379
|
+
}
|
|
1380
|
+
// Compatibility: legacy flat states/<taskId>.json (warning + unique-only merge later).
|
|
1381
|
+
try {
|
|
1382
|
+
const legacyFiles = await listLegacyStateFiles(repoRoot);
|
|
1383
|
+
for (const file of legacyFiles) {
|
|
1384
|
+
const parsed = await safeReadJson(file.path);
|
|
1385
|
+
if (!parsed) {
|
|
1386
|
+
warnings.push(`legacy flat state file unreadable or corrupt: ${file.path}`);
|
|
1387
|
+
continue;
|
|
1388
|
+
}
|
|
1389
|
+
const taskId = readString(parsed, "taskId") ?? file.taskId;
|
|
1390
|
+
const status = readString(parsed, "status");
|
|
1391
|
+
if (!taskId || !status) {
|
|
1392
|
+
warnings.push(`legacy flat state missing taskId/status: ${file.path}`);
|
|
1393
|
+
continue;
|
|
1394
|
+
}
|
|
1395
|
+
const failure = readObject(parsed, "failure");
|
|
1396
|
+
const embeddedFeatureId = readString(parsed, "featureId");
|
|
1397
|
+
if (embeddedFeatureId) {
|
|
1398
|
+
// Rare: flat file already carries featureId — treat as owned state.
|
|
1399
|
+
states.push({
|
|
1400
|
+
taskId,
|
|
1401
|
+
featureId: embeddedFeatureId,
|
|
1402
|
+
status,
|
|
1403
|
+
workerRunId: readString(parsed, "workerRunId"),
|
|
1404
|
+
lastRunRecordPath: readString(parsed, "lastRunRecordPath"),
|
|
1405
|
+
updatedAt: readString(parsed, "updatedAt"),
|
|
1406
|
+
failureCategory: failure
|
|
1407
|
+
? readString(failure, "category")
|
|
1408
|
+
: undefined,
|
|
1409
|
+
recommendedFollowUp: failure
|
|
1410
|
+
? readString(failure, "recommendedFollowUpKind")
|
|
1411
|
+
: undefined,
|
|
1412
|
+
legacyFlat: true,
|
|
1413
|
+
});
|
|
1414
|
+
warnings.push(`legacy flat state for task ${taskId} carries featureId ${embeddedFeatureId}`);
|
|
1415
|
+
continue;
|
|
1416
|
+
}
|
|
1417
|
+
legacyCandidates.push({
|
|
1418
|
+
taskId,
|
|
1419
|
+
status,
|
|
1420
|
+
workerRunId: readString(parsed, "workerRunId"),
|
|
1421
|
+
lastRunRecordPath: readString(parsed, "lastRunRecordPath"),
|
|
1422
|
+
updatedAt: readString(parsed, "updatedAt"),
|
|
1423
|
+
failureCategory: failure ? readString(failure, "category") : undefined,
|
|
1424
|
+
recommendedFollowUp: failure
|
|
1425
|
+
? readString(failure, "recommendedFollowUpKind")
|
|
1426
|
+
: undefined,
|
|
1427
|
+
legacyFlat: true,
|
|
1428
|
+
});
|
|
1429
|
+
}
|
|
1430
|
+
}
|
|
1431
|
+
catch (error) {
|
|
1432
|
+
warnings.push(`failed to scan legacy flat states: ${error instanceof Error ? error.message : String(error)}`);
|
|
1433
|
+
}
|
|
1434
|
+
return { states, legacyCandidates, warnings };
|
|
1435
|
+
}
|
|
1436
|
+
async function loadFailureHandoffs(repoRoot) {
|
|
1437
|
+
const handoffs = [];
|
|
1438
|
+
const handoffDir = path.join(getTaskPoolRoot(repoRoot), "failure-handoffs");
|
|
1439
|
+
try {
|
|
1440
|
+
const entries = await readdir(handoffDir, { withFileTypes: true });
|
|
1441
|
+
for (const entry of entries) {
|
|
1442
|
+
if (!entry.isFile())
|
|
1443
|
+
continue;
|
|
1444
|
+
const match = /^(.+)-failure-closeout-draft\.md$/.exec(entry.name);
|
|
1445
|
+
if (!match)
|
|
1446
|
+
continue;
|
|
1447
|
+
handoffs.push({
|
|
1448
|
+
workerRunId: match[1],
|
|
1449
|
+
closeoutDraftPath: path.join(handoffDir, entry.name),
|
|
1450
|
+
});
|
|
1451
|
+
}
|
|
1452
|
+
}
|
|
1453
|
+
catch {
|
|
1454
|
+
// skip
|
|
1455
|
+
}
|
|
1456
|
+
return handoffs;
|
|
1457
|
+
}
|
|
1458
|
+
async function loadDagRuns(repoRoot, now) {
|
|
1459
|
+
const byId = new Map();
|
|
1460
|
+
const statePaths = await findStateJsonFiles(path.join(repoRoot, ".harness", "dag-runs"));
|
|
1461
|
+
for (const statePath of statePaths) {
|
|
1462
|
+
const summary = await parseDagStateFile(statePath, now);
|
|
1463
|
+
if (summary)
|
|
1464
|
+
byId.set(summary.dagRunId, mergeDagRun(byId.get(summary.dagRunId), summary));
|
|
1465
|
+
}
|
|
1466
|
+
const dagEventRuns = await loadDagEventFiles(repoRoot);
|
|
1467
|
+
for (const eventRun of dagEventRuns) {
|
|
1468
|
+
const existing = byId.get(eventRun.dagRunId);
|
|
1469
|
+
byId.set(eventRun.dagRunId, mergeDagRun(existing, eventRun));
|
|
1470
|
+
}
|
|
1471
|
+
return [...byId.values()].sort((a, b) => a.dagRunId.localeCompare(b.dagRunId));
|
|
1472
|
+
}
|
|
1473
|
+
function mergeDagRun(existing, incoming) {
|
|
1474
|
+
if (!existing)
|
|
1475
|
+
return incoming;
|
|
1476
|
+
const startedAt = incoming.startedAt ?? existing.startedAt;
|
|
1477
|
+
const finishedAt = incoming.finishedAt ?? existing.finishedAt;
|
|
1478
|
+
const ranks = incoming.ranks && incoming.ranks.length > 0
|
|
1479
|
+
? incoming.ranks
|
|
1480
|
+
: existing.ranks;
|
|
1481
|
+
const durationMs = incoming.durationMs ??
|
|
1482
|
+
existing.durationMs ??
|
|
1483
|
+
computeDurationMs(startedAt, finishedAt);
|
|
1484
|
+
return {
|
|
1485
|
+
dagRunId: incoming.dagRunId,
|
|
1486
|
+
status: incoming.status ?? existing.status,
|
|
1487
|
+
lifecycle: incoming.lifecycle ?? existing.lifecycle,
|
|
1488
|
+
executionMode: incoming.executionMode ?? existing.executionMode,
|
|
1489
|
+
liveness: incoming.liveness ?? existing.liveness,
|
|
1490
|
+
effectiveStatus: incoming.effectiveStatus ?? existing.effectiveStatus,
|
|
1491
|
+
failureCategory: incoming.failureCategory ?? existing.failureCategory,
|
|
1492
|
+
stateConsistent: incoming.stateConsistent ?? existing.stateConsistent,
|
|
1493
|
+
recoveryEligibility: incoming.recoveryEligibility ?? existing.recoveryEligibility,
|
|
1494
|
+
frontendRecovery: incoming.frontendRecovery ?? existing.frontendRecovery,
|
|
1495
|
+
title: incoming.title ?? existing.title,
|
|
1496
|
+
startedAt,
|
|
1497
|
+
finishedAt,
|
|
1498
|
+
...(durationMs !== undefined ? { durationMs } : {}),
|
|
1499
|
+
...(ranks && ranks.length > 0 ? { ranks } : {}),
|
|
1500
|
+
nodes: mergeDagNodes(existing.nodes, incoming.nodes),
|
|
1501
|
+
edges: incoming.edges.length > 0 ? incoming.edges : existing.edges,
|
|
1502
|
+
...((incoming.controlEdges ?? existing.controlEdges)
|
|
1503
|
+
? { controlEdges: incoming.controlEdges ?? existing.controlEdges }
|
|
1504
|
+
: {}),
|
|
1505
|
+
...((incoming.convergence ?? existing.convergence)
|
|
1506
|
+
? { convergence: incoming.convergence ?? existing.convergence }
|
|
1507
|
+
: {}),
|
|
1508
|
+
...((incoming.dagPath ?? existing.dagPath)
|
|
1509
|
+
? { dagPath: incoming.dagPath ?? existing.dagPath }
|
|
1510
|
+
: {}),
|
|
1511
|
+
...((incoming.backendTest ?? existing.backendTest)
|
|
1512
|
+
? { backendTest: incoming.backendTest ?? existing.backendTest }
|
|
1513
|
+
: {}),
|
|
1514
|
+
};
|
|
1515
|
+
}
|
|
1516
|
+
function mergeDagNodes(existing, incoming) {
|
|
1517
|
+
const byId = new Map();
|
|
1518
|
+
for (const node of existing) {
|
|
1519
|
+
byId.set(node.nodeId, node);
|
|
1520
|
+
}
|
|
1521
|
+
for (const node of incoming) {
|
|
1522
|
+
const prev = byId.get(node.nodeId);
|
|
1523
|
+
byId.set(node.nodeId, mergeDagNode(prev, node));
|
|
1524
|
+
}
|
|
1525
|
+
return [...byId.values()].sort((a, b) => a.nodeId.localeCompare(b.nodeId));
|
|
1526
|
+
}
|
|
1527
|
+
function mergeDagNode(existing, incoming) {
|
|
1528
|
+
if (!existing)
|
|
1529
|
+
return incoming;
|
|
1530
|
+
return {
|
|
1531
|
+
nodeId: incoming.nodeId,
|
|
1532
|
+
rank: incoming.rank ?? existing.rank,
|
|
1533
|
+
executor: incoming.executor ?? existing.executor,
|
|
1534
|
+
model: incoming.model ?? existing.model,
|
|
1535
|
+
status: incoming.status ?? existing.status,
|
|
1536
|
+
label: incoming.label ?? existing.label,
|
|
1537
|
+
durationMs: incoming.durationMs ?? existing.durationMs,
|
|
1538
|
+
startedAt: incoming.startedAt ?? existing.startedAt,
|
|
1539
|
+
finishedAt: incoming.finishedAt ?? existing.finishedAt,
|
|
1540
|
+
outputPreview: incoming.outputPreview ?? existing.outputPreview,
|
|
1541
|
+
errorPreview: incoming.errorPreview ?? existing.errorPreview,
|
|
1542
|
+
attemptCount: incoming.attemptCount ?? existing.attemptCount,
|
|
1543
|
+
failureCategory: incoming.failureCategory ?? existing.failureCategory,
|
|
1544
|
+
originKind: incoming.originKind ?? existing.originKind,
|
|
1545
|
+
passes: incoming.passes ?? existing.passes,
|
|
1546
|
+
executionSummary: incoming.executionSummary ?? existing.executionSummary,
|
|
1547
|
+
};
|
|
1548
|
+
}
|
|
1549
|
+
function computeDurationMs(startedAt, finishedAt) {
|
|
1550
|
+
if (!startedAt || !finishedAt)
|
|
1551
|
+
return undefined;
|
|
1552
|
+
const startMs = Date.parse(startedAt);
|
|
1553
|
+
const endMs = Date.parse(finishedAt);
|
|
1554
|
+
if (Number.isNaN(startMs) || Number.isNaN(endMs))
|
|
1555
|
+
return undefined;
|
|
1556
|
+
return Math.max(0, endMs - startMs);
|
|
1557
|
+
}
|
|
1558
|
+
function isFailedNodeStatus(status) {
|
|
1559
|
+
if (!status)
|
|
1560
|
+
return false;
|
|
1561
|
+
const normalized = status.toLowerCase();
|
|
1562
|
+
return (normalized === "error" ||
|
|
1563
|
+
normalized === "failed" ||
|
|
1564
|
+
normalized === "partial_failed");
|
|
1565
|
+
}
|
|
1566
|
+
function nodeOutputPreview(node) {
|
|
1567
|
+
const stdout = readString(node, "stdout") ?? readString(node, "assistantText");
|
|
1568
|
+
if (!stdout)
|
|
1569
|
+
return undefined;
|
|
1570
|
+
return truncateUtf8Preview(stdout);
|
|
1571
|
+
}
|
|
1572
|
+
function nodeErrorPreview(node, status) {
|
|
1573
|
+
if (!isFailedNodeStatus(status))
|
|
1574
|
+
return undefined;
|
|
1575
|
+
const stderr = readString(node, "stderr");
|
|
1576
|
+
if (!stderr)
|
|
1577
|
+
return undefined;
|
|
1578
|
+
return truncateUtf8Preview(stderr);
|
|
1579
|
+
}
|
|
1580
|
+
async function loadDagEventFiles(repoRoot) {
|
|
1581
|
+
const runsDir = path.join(getTaskPoolRoot(repoRoot), "observability", "runs");
|
|
1582
|
+
let entries;
|
|
1583
|
+
try {
|
|
1584
|
+
entries = await readdir(runsDir, { withFileTypes: true });
|
|
1585
|
+
}
|
|
1586
|
+
catch {
|
|
1587
|
+
return [];
|
|
1588
|
+
}
|
|
1589
|
+
const byId = new Map();
|
|
1590
|
+
const runStatus = new Map();
|
|
1591
|
+
const runTitle = new Map();
|
|
1592
|
+
const runStartedAt = new Map();
|
|
1593
|
+
const runFinishedAt = new Map();
|
|
1594
|
+
for (const entry of entries) {
|
|
1595
|
+
if (!entry.isDirectory())
|
|
1596
|
+
continue;
|
|
1597
|
+
const eventsPath = path.join(runsDir, entry.name, "dag-events.jsonl");
|
|
1598
|
+
const raw = await safeReadText(eventsPath);
|
|
1599
|
+
if (!raw)
|
|
1600
|
+
continue;
|
|
1601
|
+
for (const line of raw.split("\n")) {
|
|
1602
|
+
const trimmed = line.trim();
|
|
1603
|
+
if (!trimmed)
|
|
1604
|
+
continue;
|
|
1605
|
+
const parsed = safeParseJson(trimmed);
|
|
1606
|
+
if (!parsed)
|
|
1607
|
+
continue;
|
|
1608
|
+
const type = readString(parsed, "type");
|
|
1609
|
+
if (!type || !type.startsWith("dag."))
|
|
1610
|
+
continue;
|
|
1611
|
+
const dagRunId = readString(parsed, "dagRunId") ?? entry.name;
|
|
1612
|
+
const nodeId = readString(parsed, "nodeId");
|
|
1613
|
+
if (type === "dag.run.started") {
|
|
1614
|
+
runStatus.set(dagRunId, "running");
|
|
1615
|
+
const label = readString(parsed, "label");
|
|
1616
|
+
if (label)
|
|
1617
|
+
runTitle.set(dagRunId, label);
|
|
1618
|
+
const at = readString(parsed, "at");
|
|
1619
|
+
if (at)
|
|
1620
|
+
runStartedAt.set(dagRunId, at);
|
|
1621
|
+
}
|
|
1622
|
+
else if (type === "dag.run.finished") {
|
|
1623
|
+
runStatus.set(dagRunId, readString(parsed, "status") ?? "completed");
|
|
1624
|
+
const at = readString(parsed, "at");
|
|
1625
|
+
if (at)
|
|
1626
|
+
runFinishedAt.set(dagRunId, at);
|
|
1627
|
+
}
|
|
1628
|
+
else if (type.startsWith("dag.node.") && nodeId) {
|
|
1629
|
+
const nodeMap = byId.get(dagRunId) ?? new Map();
|
|
1630
|
+
const prev = nodeMap.get(nodeId) ?? { nodeId };
|
|
1631
|
+
const rank = readString(parsed, "rank");
|
|
1632
|
+
const executor = readString(parsed, "executor");
|
|
1633
|
+
const model = readString(parsed, "model");
|
|
1634
|
+
const label = readString(parsed, "label");
|
|
1635
|
+
const status = readString(parsed, "status");
|
|
1636
|
+
const durationMs = readNumber(parsed, "durationMs");
|
|
1637
|
+
const outputPreview = readString(parsed, "outputPreview");
|
|
1638
|
+
nodeMap.set(nodeId, {
|
|
1639
|
+
nodeId,
|
|
1640
|
+
...((rank ?? prev.rank) ? { rank: rank ?? prev.rank } : {}),
|
|
1641
|
+
...((executor ?? prev.executor)
|
|
1642
|
+
? { executor: executor ?? prev.executor }
|
|
1643
|
+
: {}),
|
|
1644
|
+
...((model ?? prev.model) ? { model: model ?? prev.model } : {}),
|
|
1645
|
+
...((label ?? prev.label) ? { label: label ?? prev.label } : {}),
|
|
1646
|
+
...((status ?? prev.status) ? { status: status ?? prev.status } : {}),
|
|
1647
|
+
...((durationMs ?? prev.durationMs)
|
|
1648
|
+
? { durationMs: durationMs ?? prev.durationMs }
|
|
1649
|
+
: {}),
|
|
1650
|
+
...((outputPreview ?? prev.outputPreview)
|
|
1651
|
+
? { outputPreview: outputPreview ?? prev.outputPreview }
|
|
1652
|
+
: {}),
|
|
1653
|
+
});
|
|
1654
|
+
byId.set(dagRunId, nodeMap);
|
|
1655
|
+
}
|
|
1656
|
+
}
|
|
1657
|
+
}
|
|
1658
|
+
const result = [];
|
|
1659
|
+
for (const [dagRunId, nodeMap] of byId) {
|
|
1660
|
+
const startedAt = runStartedAt.get(dagRunId);
|
|
1661
|
+
const finishedAt = runFinishedAt.get(dagRunId);
|
|
1662
|
+
const durationMs = computeDurationMs(startedAt, finishedAt);
|
|
1663
|
+
result.push({
|
|
1664
|
+
dagRunId,
|
|
1665
|
+
...(runStatus.get(dagRunId) ? { status: runStatus.get(dagRunId) } : {}),
|
|
1666
|
+
...(runTitle.get(dagRunId) ? { title: runTitle.get(dagRunId) } : {}),
|
|
1667
|
+
...(startedAt ? { startedAt } : {}),
|
|
1668
|
+
...(finishedAt ? { finishedAt } : {}),
|
|
1669
|
+
...(durationMs !== undefined ? { durationMs } : {}),
|
|
1670
|
+
nodes: [...nodeMap.values()].sort((a, b) => a.nodeId.localeCompare(b.nodeId)),
|
|
1671
|
+
edges: [],
|
|
1672
|
+
});
|
|
1673
|
+
}
|
|
1674
|
+
return result;
|
|
1675
|
+
}
|
|
1676
|
+
async function findStateJsonFiles(root) {
|
|
1677
|
+
const results = [];
|
|
1678
|
+
await walkForStateJson(root, results);
|
|
1679
|
+
return results;
|
|
1680
|
+
}
|
|
1681
|
+
async function walkForStateJson(dir, results) {
|
|
1682
|
+
try {
|
|
1683
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
1684
|
+
for (const entry of entries) {
|
|
1685
|
+
const fullPath = path.join(dir, entry.name);
|
|
1686
|
+
if (entry.isDirectory()) {
|
|
1687
|
+
await walkForStateJson(fullPath, results);
|
|
1688
|
+
}
|
|
1689
|
+
else if (entry.isFile() && entry.name === "state.json") {
|
|
1690
|
+
results.push(fullPath);
|
|
1691
|
+
}
|
|
1692
|
+
}
|
|
1693
|
+
}
|
|
1694
|
+
catch {
|
|
1695
|
+
// skip
|
|
1696
|
+
}
|
|
1697
|
+
}
|
|
1698
|
+
async function loadBackendTestProjection(runDir, nodes) {
|
|
1699
|
+
const contracts = path.join(runDir, "contracts");
|
|
1700
|
+
const readContract = async (name) => {
|
|
1701
|
+
const value = await safeReadJson(path.join(contracts, name));
|
|
1702
|
+
return value ?? undefined;
|
|
1703
|
+
};
|
|
1704
|
+
const [initial, classification, eligibility, final, effective, manifest] = await Promise.all([
|
|
1705
|
+
readContract("backend-test-result-initial.json"),
|
|
1706
|
+
readContract("backend-test-classification.json"),
|
|
1707
|
+
readContract("backend-test-repair-eligibility.json"),
|
|
1708
|
+
readContract("backend-test-result-final.json"),
|
|
1709
|
+
readContract("backend-test-result.json"),
|
|
1710
|
+
readContract("backend-test-case-manifest.json"),
|
|
1711
|
+
]);
|
|
1712
|
+
if (!initial &&
|
|
1713
|
+
!classification &&
|
|
1714
|
+
!eligibility &&
|
|
1715
|
+
!final &&
|
|
1716
|
+
!effective &&
|
|
1717
|
+
!manifest)
|
|
1718
|
+
return undefined;
|
|
1719
|
+
const result = (value) => value
|
|
1720
|
+
? {
|
|
1721
|
+
outcome: readString(value, "outcome"),
|
|
1722
|
+
passed: readNumber(value, "passed"),
|
|
1723
|
+
failed: readNumber(value, "failed"),
|
|
1724
|
+
error: readNumber(value, "error"),
|
|
1725
|
+
}
|
|
1726
|
+
: undefined;
|
|
1727
|
+
const repairNode = nodes.find((node) => node.nodeId === "repair-backend-pytest-pi");
|
|
1728
|
+
const safetyNode = nodes.find((node) => node.nodeId === "validate-repair-safety-and-traceability-shell");
|
|
1729
|
+
const eligible = eligibility ? eligibility.eligible === true : undefined;
|
|
1730
|
+
const repairNodeStatus = repairNode?.status?.toLowerCase();
|
|
1731
|
+
const repairAttempted = repairNodeStatus === "running" ||
|
|
1732
|
+
repairNodeStatus === "finished" ||
|
|
1733
|
+
repairNodeStatus === "error";
|
|
1734
|
+
let repairStatus = "not-needed";
|
|
1735
|
+
if (repairNodeStatus === "running")
|
|
1736
|
+
repairStatus = "repairing";
|
|
1737
|
+
else if (safetyNode && isFailedNodeStatus(safetyNode.status))
|
|
1738
|
+
repairStatus = "rejected";
|
|
1739
|
+
else if (final)
|
|
1740
|
+
repairStatus = "completed";
|
|
1741
|
+
else if (eligible)
|
|
1742
|
+
repairStatus = "eligible";
|
|
1743
|
+
const effectiveSource = final ? "final" : "initial";
|
|
1744
|
+
const legacy = Boolean(eligibility || final || repairNode || safetyNode);
|
|
1745
|
+
return {
|
|
1746
|
+
...(initial ? { initial: result(initial) } : {}),
|
|
1747
|
+
...(classification
|
|
1748
|
+
? {
|
|
1749
|
+
classification: {
|
|
1750
|
+
category: readString(classification, "category"),
|
|
1751
|
+
confidence: readNumber(classification, "confidence"),
|
|
1752
|
+
},
|
|
1753
|
+
}
|
|
1754
|
+
: {}),
|
|
1755
|
+
...(legacy
|
|
1756
|
+
? {
|
|
1757
|
+
repair: {
|
|
1758
|
+
eligible,
|
|
1759
|
+
reason: eligibility ? readString(eligibility, "reason") : undefined,
|
|
1760
|
+
attempt: final || repairAttempted ? 1 : 0,
|
|
1761
|
+
status: repairStatus,
|
|
1762
|
+
},
|
|
1763
|
+
...(final ? { final: result(final) } : {}),
|
|
1764
|
+
}
|
|
1765
|
+
: {}),
|
|
1766
|
+
...(effective
|
|
1767
|
+
? {
|
|
1768
|
+
effective: {
|
|
1769
|
+
source: effectiveSource,
|
|
1770
|
+
outcome: readString(effective, "outcome"),
|
|
1771
|
+
},
|
|
1772
|
+
}
|
|
1773
|
+
: {}),
|
|
1774
|
+
...(manifest && manifest.coverageSummary
|
|
1775
|
+
? { coverage: manifest.coverageSummary }
|
|
1776
|
+
: {}),
|
|
1777
|
+
};
|
|
1778
|
+
}
|
|
1779
|
+
async function loadFrontendLintProjection(runDir) {
|
|
1780
|
+
const decoded = await safeReadJson(path.join(runDir, "contracts", "frontend-lint-assessment.json"));
|
|
1781
|
+
if (!decoded)
|
|
1782
|
+
return undefined;
|
|
1783
|
+
const parsed = frontendLintAssessmentArtifactSchema.safeParse(decoded);
|
|
1784
|
+
if (!parsed.success)
|
|
1785
|
+
return undefined;
|
|
1786
|
+
return {
|
|
1787
|
+
status: parsed.data.status,
|
|
1788
|
+
writerChangedFiles: parsed.data.writerChangedFiles,
|
|
1789
|
+
toleratedDiagnosticCount: parsed.data.toleratedDiagnosticCount,
|
|
1790
|
+
blockingDiagnosticCount: parsed.data.blockingDiagnostics.length,
|
|
1791
|
+
blockingReasons: parsed.data.blockingReasons,
|
|
1792
|
+
};
|
|
1793
|
+
}
|
|
1794
|
+
function projectFrontendRecovery(state) {
|
|
1795
|
+
const s = state.frontendRecoveryState;
|
|
1796
|
+
const r = state.frontendRecoveryResult;
|
|
1797
|
+
if (!s && !r)
|
|
1798
|
+
return undefined;
|
|
1799
|
+
return {
|
|
1800
|
+
...(s?.recoveryRootRunId ? { recoveryRootRunId: s.recoveryRootRunId } : {}),
|
|
1801
|
+
...(s?.parentRunId ? { parentRunId: s.parentRunId } : {}),
|
|
1802
|
+
...(s?.childRunId ? { childRunId: s.childRunId } : {}),
|
|
1803
|
+
...(s?.attemptIndex !== undefined ? { attemptIndex: s.attemptIndex } : {}),
|
|
1804
|
+
...(s?.continuationCount !== undefined
|
|
1805
|
+
? { continuationCount: s.continuationCount }
|
|
1806
|
+
: {}),
|
|
1807
|
+
...(s?.phase ? { phase: s.phase } : {}),
|
|
1808
|
+
...(r?.outcome ? { outcome: r.outcome } : {}),
|
|
1809
|
+
};
|
|
1810
|
+
}
|
|
1811
|
+
async function parseDagStateFile(statePath, now) {
|
|
1812
|
+
try {
|
|
1813
|
+
if (!existsSync(statePath))
|
|
1814
|
+
return undefined;
|
|
1815
|
+
const raw = readFileSync(statePath, "utf-8");
|
|
1816
|
+
const parsed = safeParseJson(raw);
|
|
1817
|
+
if (!parsed)
|
|
1818
|
+
return undefined;
|
|
1819
|
+
const runDir = path.dirname(statePath);
|
|
1820
|
+
const lifecycleName = path.basename(path.dirname(runDir));
|
|
1821
|
+
const lifecycle = lifecycleName === "active" ||
|
|
1822
|
+
lifecycleName === "paused" ||
|
|
1823
|
+
lifecycleName === "completed" ||
|
|
1824
|
+
lifecycleName === "dry-run" ||
|
|
1825
|
+
lifecycleName === "init-only"
|
|
1826
|
+
? lifecycleName
|
|
1827
|
+
: undefined;
|
|
1828
|
+
const dagRunId = readString(parsed, "runId") ?? path.basename(runDir);
|
|
1829
|
+
const status = readString(parsed, "status");
|
|
1830
|
+
const executionModeRaw = readString(parsed, "executionMode");
|
|
1831
|
+
const executionMode = executionModeRaw === "execute" ||
|
|
1832
|
+
executionModeRaw === "dry-run" ||
|
|
1833
|
+
executionModeRaw === "init-only"
|
|
1834
|
+
? executionModeRaw
|
|
1835
|
+
: undefined;
|
|
1836
|
+
const title = readString(parsed, "title");
|
|
1837
|
+
const startedAt = readString(parsed, "startedAt");
|
|
1838
|
+
const finishedAt = readString(parsed, "finishedAt");
|
|
1839
|
+
const durationMs = computeDurationMs(startedAt, finishedAt);
|
|
1840
|
+
const ranks = readStringMatrix(parsed, "ranks");
|
|
1841
|
+
const rankByNode = buildRankIndex(ranks);
|
|
1842
|
+
const runSpecPath = path.join(runDir, "run.json");
|
|
1843
|
+
const modelByNode = await loadDagNodeModels(runSpecPath);
|
|
1844
|
+
const nodes = parseDagNodes(parsed, rankByNode, modelByNode);
|
|
1845
|
+
const edges = await parseDagEdges(runSpecPath, nodes);
|
|
1846
|
+
const state = parsed;
|
|
1847
|
+
const controlEdges = await deriveControlLoopEdges(runSpecPath, nodes, state);
|
|
1848
|
+
const convergence = projectConvergenceSummary(state);
|
|
1849
|
+
applyNodePasses(nodes, state);
|
|
1850
|
+
applyNodeExecutionSummaries(nodes, state);
|
|
1851
|
+
const liveness = assessDagRunLiveness({ state, now });
|
|
1852
|
+
const effectiveStatus = lifecycle
|
|
1853
|
+
? deriveDagRunEffectiveStatus({
|
|
1854
|
+
lifecycle,
|
|
1855
|
+
state,
|
|
1856
|
+
liveness: liveness.status,
|
|
1857
|
+
})
|
|
1858
|
+
: "unknown";
|
|
1859
|
+
const stateConsistent = lifecycle === undefined
|
|
1860
|
+
? false
|
|
1861
|
+
: !((lifecycle === "paused" && state.status !== "paused") ||
|
|
1862
|
+
(lifecycle === "completed" &&
|
|
1863
|
+
![
|
|
1864
|
+
"finished",
|
|
1865
|
+
"failed",
|
|
1866
|
+
"partial_failed",
|
|
1867
|
+
"superseded",
|
|
1868
|
+
"abandoned",
|
|
1869
|
+
].includes(state.status)));
|
|
1870
|
+
const recoveryEligibility = lifecycle
|
|
1871
|
+
? assessDagRunRecoveryEligibility({
|
|
1872
|
+
lifecycle,
|
|
1873
|
+
state,
|
|
1874
|
+
liveness: liveness.status,
|
|
1875
|
+
})
|
|
1876
|
+
: undefined;
|
|
1877
|
+
const [backendTest, frontendLint] = await Promise.all([
|
|
1878
|
+
loadBackendTestProjection(runDir, nodes),
|
|
1879
|
+
loadFrontendLintProjection(runDir),
|
|
1880
|
+
]);
|
|
1881
|
+
const frontendRecovery = projectFrontendRecovery(state);
|
|
1882
|
+
const continuationRecord = state.continuation &&
|
|
1883
|
+
typeof state.continuation.parentRunId === "string" &&
|
|
1884
|
+
typeof state.continuation.effectiveFromNodeId === "string" &&
|
|
1885
|
+
typeof state.continuation.operatorSelectedNodeId === "string"
|
|
1886
|
+
? state.continuation
|
|
1887
|
+
: undefined;
|
|
1888
|
+
const rerunPlan = continuationRecord
|
|
1889
|
+
? await safeReadJson(path.join(runDir, ".runtime", "rerun-plan.json"))
|
|
1890
|
+
: undefined;
|
|
1891
|
+
const verification = rerunPlan && typeof rerunPlan.verificationPolicy === "object"
|
|
1892
|
+
? rerunPlan.verificationPolicy
|
|
1893
|
+
: undefined;
|
|
1894
|
+
const continuation = continuationRecord
|
|
1895
|
+
? {
|
|
1896
|
+
parentRunId: continuationRecord.parentRunId,
|
|
1897
|
+
effectiveFromNodeId: continuationRecord.effectiveFromNodeId,
|
|
1898
|
+
operatorSelectedNodeId: continuationRecord.operatorSelectedNodeId,
|
|
1899
|
+
...(verification &&
|
|
1900
|
+
(verification.mode === "inherited" ||
|
|
1901
|
+
verification.mode === "rerun") &&
|
|
1902
|
+
Array.isArray(verification.nodeIds) &&
|
|
1903
|
+
verification.nodeIds.every((nodeId) => typeof nodeId === "string")
|
|
1904
|
+
? {
|
|
1905
|
+
verificationPolicy: {
|
|
1906
|
+
mode: verification.mode,
|
|
1907
|
+
nodeIds: verification.nodeIds,
|
|
1908
|
+
...(typeof verification.inheritedFromRunId === "string"
|
|
1909
|
+
? { inheritedFromRunId: verification.inheritedFromRunId }
|
|
1910
|
+
: {}),
|
|
1911
|
+
},
|
|
1912
|
+
}
|
|
1913
|
+
: {}),
|
|
1914
|
+
}
|
|
1915
|
+
: undefined;
|
|
1916
|
+
const failureCategory = readString(parsed, "failureCategory");
|
|
1917
|
+
return {
|
|
1918
|
+
dagRunId,
|
|
1919
|
+
status,
|
|
1920
|
+
...(lifecycle ? { lifecycle } : {}),
|
|
1921
|
+
...(executionMode ? { executionMode } : {}),
|
|
1922
|
+
liveness: liveness.status,
|
|
1923
|
+
effectiveStatus,
|
|
1924
|
+
...(failureCategory ? { failureCategory } : {}),
|
|
1925
|
+
stateConsistent,
|
|
1926
|
+
...(recoveryEligibility ? { recoveryEligibility } : {}),
|
|
1927
|
+
...(title ? { title } : {}),
|
|
1928
|
+
...(startedAt ? { startedAt } : {}),
|
|
1929
|
+
...(finishedAt ? { finishedAt } : {}),
|
|
1930
|
+
...(durationMs !== undefined ? { durationMs } : {}),
|
|
1931
|
+
...(ranks.length > 0 ? { ranks } : {}),
|
|
1932
|
+
nodes,
|
|
1933
|
+
edges,
|
|
1934
|
+
...(controlEdges.length > 0 ? { controlEdges } : {}),
|
|
1935
|
+
...(convergence ? { convergence } : {}),
|
|
1936
|
+
dagPath: runDir,
|
|
1937
|
+
...(continuation ? { continuation } : {}),
|
|
1938
|
+
...(backendTest ? { backendTest } : {}),
|
|
1939
|
+
...(frontendLint ? { frontendLint } : {}),
|
|
1940
|
+
...(frontendRecovery ? { frontendRecovery } : {}),
|
|
1941
|
+
};
|
|
1942
|
+
}
|
|
1943
|
+
catch {
|
|
1944
|
+
return undefined;
|
|
1945
|
+
}
|
|
1946
|
+
}
|
|
1947
|
+
async function parseDagEdges(runPath, nodes) {
|
|
1948
|
+
const parsed = await safeReadJson(runPath);
|
|
1949
|
+
if (!parsed)
|
|
1950
|
+
return [];
|
|
1951
|
+
const tasks = readObjectArray(parsed, "tasks");
|
|
1952
|
+
if (tasks.length === 0)
|
|
1953
|
+
return [];
|
|
1954
|
+
const knownNodeIds = new Set(nodes.map((node) => node.nodeId));
|
|
1955
|
+
const edges = new Map();
|
|
1956
|
+
for (const task of tasks) {
|
|
1957
|
+
const taskId = readString(task, "id") ?? readString(task, "nodeId");
|
|
1958
|
+
if (!taskId || !knownNodeIds.has(taskId))
|
|
1959
|
+
continue;
|
|
1960
|
+
const dependencies = readStringArray(task, "depends_on");
|
|
1961
|
+
const compatibleDependencies = dependencies.length > 0
|
|
1962
|
+
? dependencies
|
|
1963
|
+
: readStringArray(task, "dependsOn");
|
|
1964
|
+
for (const dependencyId of compatibleDependencies) {
|
|
1965
|
+
if (dependencyId === taskId || !knownNodeIds.has(dependencyId))
|
|
1966
|
+
continue;
|
|
1967
|
+
const edge = { from: dependencyId, to: taskId };
|
|
1968
|
+
edges.set(`${edge.from}\u0000${edge.to}`, edge);
|
|
1969
|
+
}
|
|
1970
|
+
}
|
|
1971
|
+
return [...edges.values()].sort((a, b) => a.from.localeCompare(b.from) || a.to.localeCompare(b.to));
|
|
1972
|
+
}
|
|
1973
|
+
/**
|
|
1974
|
+
* Derive presentation-only control-loop edges from the run spec's convergence
|
|
1975
|
+
* chain. When the chain contains both `review-gate-shell` and `repair-pi`, the
|
|
1976
|
+
* bounded repair-reverify-review loop exists at runtime and Observe renders it
|
|
1977
|
+
* as a dashed feedback loop. The derived edge is never merged into `edges` or
|
|
1978
|
+
* `ranks`, so DAG topological layout stays acyclic.
|
|
1979
|
+
*/
|
|
1980
|
+
async function deriveControlLoopEdges(runPath, nodes, state) {
|
|
1981
|
+
const runtimeConvergence = state.convergence;
|
|
1982
|
+
if (!runtimeConvergence?.enabled ||
|
|
1983
|
+
runtimeConvergence.terminalReason === "feature-flag-off" ||
|
|
1984
|
+
runtimeConvergence.terminalReason === "unsupported-dag-shape") {
|
|
1985
|
+
return [];
|
|
1986
|
+
}
|
|
1987
|
+
const parsed = await safeReadJson(runPath);
|
|
1988
|
+
if (!parsed)
|
|
1989
|
+
return [];
|
|
1990
|
+
const convergence = readObject(parsed, "convergence");
|
|
1991
|
+
if (!convergence)
|
|
1992
|
+
return [];
|
|
1993
|
+
const chainNodeIds = readStringArray(convergence, "chainNodeIds");
|
|
1994
|
+
if (chainNodeIds.length === 0)
|
|
1995
|
+
return [];
|
|
1996
|
+
if (!chainNodeIds.includes("review-gate-shell") ||
|
|
1997
|
+
!chainNodeIds.includes("repair-pi")) {
|
|
1998
|
+
return [];
|
|
1999
|
+
}
|
|
2000
|
+
const known = new Set(nodes.map((node) => node.nodeId));
|
|
2001
|
+
if (!known.has("review-gate-shell") || !known.has("repair-pi")) {
|
|
2002
|
+
return [];
|
|
2003
|
+
}
|
|
2004
|
+
return [{ from: "review-gate-shell", to: "repair-pi", kind: "control-loop" }];
|
|
2005
|
+
}
|
|
2006
|
+
/**
|
|
2007
|
+
* Project `state.convergence` into the slim Observe summary. Keeps the budget
|
|
2008
|
+
* envelope even when passHistory is empty.
|
|
2009
|
+
*/
|
|
2010
|
+
function projectConvergenceSummary(state) {
|
|
2011
|
+
const convergence = state.convergence;
|
|
2012
|
+
if (!convergence)
|
|
2013
|
+
return undefined;
|
|
2014
|
+
return {
|
|
2015
|
+
enabled: convergence.enabled,
|
|
2016
|
+
maxPasses: convergence.maxPasses,
|
|
2017
|
+
currentPass: convergence.currentPass,
|
|
2018
|
+
...(convergence.terminalReason
|
|
2019
|
+
? { terminalReason: convergence.terminalReason }
|
|
2020
|
+
: {}),
|
|
2021
|
+
passHistory: convergence.passHistory.map((pass) => ({
|
|
2022
|
+
pass: pass.pass,
|
|
2023
|
+
...(pass.reason ? { reason: pass.reason } : {}),
|
|
2024
|
+
...(pass.status ? { status: pass.status } : {}),
|
|
2025
|
+
...(pass.hardVerifyStatus
|
|
2026
|
+
? { hardVerifyStatus: pass.hardVerifyStatus }
|
|
2027
|
+
: {}),
|
|
2028
|
+
...(pass.reviewVerdict ? { reviewVerdict: pass.reviewVerdict } : {}),
|
|
2029
|
+
...(pass.reviewGateStatus
|
|
2030
|
+
? { reviewGateStatus: pass.reviewGateStatus }
|
|
2031
|
+
: {}),
|
|
2032
|
+
})),
|
|
2033
|
+
};
|
|
2034
|
+
}
|
|
2035
|
+
/**
|
|
2036
|
+
* Record convergence participation from archived pass artifacts plus the live
|
|
2037
|
+
* current pass. The controller archives only retry/terminal failures, so an
|
|
2038
|
+
* active pass or a successful final pass must be derived from current node
|
|
2039
|
+
* state to avoid dropping its pass marker from Observe.
|
|
2040
|
+
*/
|
|
2041
|
+
function applyNodePasses(nodes, state) {
|
|
2042
|
+
const passesByNode = new Map();
|
|
2043
|
+
const addPass = (nodeId, pass) => {
|
|
2044
|
+
const passes = passesByNode.get(nodeId) ?? new Set();
|
|
2045
|
+
passes.add(pass);
|
|
2046
|
+
passesByNode.set(nodeId, passes);
|
|
2047
|
+
};
|
|
2048
|
+
for (const pass of state.convergence?.passHistory ?? []) {
|
|
2049
|
+
for (const ref of pass.artifactRefs)
|
|
2050
|
+
addPass(ref.nodeId, pass.pass);
|
|
2051
|
+
}
|
|
2052
|
+
const initialPassNodeIds = new Set([
|
|
2053
|
+
"initial-review-pi",
|
|
2054
|
+
"initial-review-gate-shell",
|
|
2055
|
+
"initial-review-pass-shell",
|
|
2056
|
+
]);
|
|
2057
|
+
for (const node of nodes) {
|
|
2058
|
+
if (!initialPassNodeIds.has(node.nodeId))
|
|
2059
|
+
continue;
|
|
2060
|
+
const status = (node.status ?? "").toUpperCase();
|
|
2061
|
+
if (["RUNNING", "FINISHED", "ERROR"].includes(status)) {
|
|
2062
|
+
addPass(node.nodeId, 1);
|
|
2063
|
+
}
|
|
2064
|
+
}
|
|
2065
|
+
const currentPass = state.convergence?.currentPass;
|
|
2066
|
+
if (currentPass && currentPass > 0) {
|
|
2067
|
+
const convergenceNodeIds = new Set([
|
|
2068
|
+
"process-supervisor-pi",
|
|
2069
|
+
"process-gate-shell",
|
|
2070
|
+
"repair-pi",
|
|
2071
|
+
"hard-verify-shell",
|
|
2072
|
+
"review-pi",
|
|
2073
|
+
"review-verdict-recovery-pi",
|
|
2074
|
+
"review-gate-shell",
|
|
2075
|
+
]);
|
|
2076
|
+
for (const node of nodes) {
|
|
2077
|
+
if (!convergenceNodeIds.has(node.nodeId))
|
|
2078
|
+
continue;
|
|
2079
|
+
const status = (node.status ?? "").toUpperCase();
|
|
2080
|
+
if (["RUNNING", "FINISHED", "ERROR"].includes(status)) {
|
|
2081
|
+
addPass(node.nodeId, currentPass);
|
|
2082
|
+
}
|
|
2083
|
+
}
|
|
2084
|
+
}
|
|
2085
|
+
for (const node of nodes) {
|
|
2086
|
+
const passes = passesByNode.get(node.nodeId);
|
|
2087
|
+
if (passes && passes.size > 0) {
|
|
2088
|
+
node.passes = [...passes].sort((left, right) => left - right);
|
|
2089
|
+
}
|
|
2090
|
+
}
|
|
2091
|
+
}
|
|
2092
|
+
function buildRankIndex(ranks) {
|
|
2093
|
+
const index = new Map();
|
|
2094
|
+
for (let i = 0; i < ranks.length; i++) {
|
|
2095
|
+
const rank = ranks[i];
|
|
2096
|
+
if (!Array.isArray(rank))
|
|
2097
|
+
continue;
|
|
2098
|
+
const rankLabel = String(i);
|
|
2099
|
+
for (const nodeId of rank) {
|
|
2100
|
+
if (typeof nodeId === "string") {
|
|
2101
|
+
index.set(nodeId, rankLabel);
|
|
2102
|
+
}
|
|
2103
|
+
}
|
|
2104
|
+
}
|
|
2105
|
+
return index;
|
|
2106
|
+
}
|
|
2107
|
+
async function loadDagNodeModels(runPath) {
|
|
2108
|
+
const models = new Map();
|
|
2109
|
+
const raw = await safeReadJson(runPath);
|
|
2110
|
+
if (!raw)
|
|
2111
|
+
return models;
|
|
2112
|
+
try {
|
|
2113
|
+
const spec = parseDagSpec(raw);
|
|
2114
|
+
for (const task of spec.tasks) {
|
|
2115
|
+
const model = resolveModelForTask(task, spec.executorModels);
|
|
2116
|
+
if (model)
|
|
2117
|
+
models.set(task.id, model);
|
|
2118
|
+
}
|
|
2119
|
+
}
|
|
2120
|
+
catch {
|
|
2121
|
+
// Historical specs may predate the current schema (including the removed
|
|
2122
|
+
// Cursor executor). Preserve their recorded model labels for observation
|
|
2123
|
+
// without allowing those specs back into the executable DAG path.
|
|
2124
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw))
|
|
2125
|
+
return models;
|
|
2126
|
+
const legacy = raw;
|
|
2127
|
+
const tasks = legacy.tasks;
|
|
2128
|
+
const executorModels = legacy.executorModels;
|
|
2129
|
+
if (!Array.isArray(tasks) ||
|
|
2130
|
+
typeof executorModels !== "object" ||
|
|
2131
|
+
executorModels === null ||
|
|
2132
|
+
Array.isArray(executorModels)) {
|
|
2133
|
+
return models;
|
|
2134
|
+
}
|
|
2135
|
+
for (const task of tasks) {
|
|
2136
|
+
if (!task || typeof task !== "object" || Array.isArray(task))
|
|
2137
|
+
continue;
|
|
2138
|
+
const taskRecord = task;
|
|
2139
|
+
const taskId = readString(taskRecord, "id");
|
|
2140
|
+
const executor = readString(taskRecord, "executor") ?? "pi";
|
|
2141
|
+
const complexity = readString(taskRecord, "complexity");
|
|
2142
|
+
if (!taskId || !complexity)
|
|
2143
|
+
continue;
|
|
2144
|
+
const matrix = executorModels[executor];
|
|
2145
|
+
if (!matrix || typeof matrix !== "object" || Array.isArray(matrix))
|
|
2146
|
+
continue;
|
|
2147
|
+
const model = readString(matrix, complexity);
|
|
2148
|
+
if (model)
|
|
2149
|
+
models.set(taskId, model);
|
|
2150
|
+
}
|
|
2151
|
+
}
|
|
2152
|
+
return models;
|
|
2153
|
+
}
|
|
2154
|
+
const EXECUTED_NODE_STATUSES = new Set([
|
|
2155
|
+
"finished",
|
|
2156
|
+
"completed",
|
|
2157
|
+
"succeeded",
|
|
2158
|
+
"done",
|
|
2159
|
+
"error",
|
|
2160
|
+
"failed",
|
|
2161
|
+
"partial_failed",
|
|
2162
|
+
"running",
|
|
2163
|
+
"started",
|
|
2164
|
+
"paused",
|
|
2165
|
+
]);
|
|
2166
|
+
function isExecutedNodeStatus(status) {
|
|
2167
|
+
if (!status)
|
|
2168
|
+
return false;
|
|
2169
|
+
return EXECUTED_NODE_STATUSES.has(status.toLowerCase());
|
|
2170
|
+
}
|
|
2171
|
+
const PASS_SUCCESS_STATUSES = new Set([
|
|
2172
|
+
"finished",
|
|
2173
|
+
"completed",
|
|
2174
|
+
"succeeded",
|
|
2175
|
+
"done",
|
|
2176
|
+
]);
|
|
2177
|
+
const PASS_FAILURE_STATUSES = new Set([
|
|
2178
|
+
"error",
|
|
2179
|
+
"failed",
|
|
2180
|
+
"partial_failed",
|
|
2181
|
+
"interrupted",
|
|
2182
|
+
]);
|
|
2183
|
+
/**
|
|
2184
|
+
* Project the lightweight execution history summary for one DAG node.
|
|
2185
|
+
* Precedence: recorded attempts > convergence passes > implicit single
|
|
2186
|
+
* execution > no summary (legacy). Pure and deterministic; never throws on
|
|
2187
|
+
* legacy shapes (missing attempts/passes/ok flags).
|
|
2188
|
+
*/
|
|
2189
|
+
export function projectExecutionSummary(options) {
|
|
2190
|
+
const rawAttempts = Array.isArray(options.attempts)
|
|
2191
|
+
? options.attempts
|
|
2192
|
+
: undefined;
|
|
2193
|
+
if (rawAttempts && rawAttempts.length > 0) {
|
|
2194
|
+
const attempts = rawAttempts.map((raw, index) => {
|
|
2195
|
+
const record = raw && typeof raw === "object" && !Array.isArray(raw)
|
|
2196
|
+
? raw
|
|
2197
|
+
: {};
|
|
2198
|
+
const ok = record.ok === true;
|
|
2199
|
+
const failureCategory = readString(record, "failureCategory");
|
|
2200
|
+
const durationMs = readNumber(record, "durationMs");
|
|
2201
|
+
const artifactPath = readString(record, "artifactPath");
|
|
2202
|
+
return {
|
|
2203
|
+
attempt: index + 1,
|
|
2204
|
+
status: ok ? "success" : "failure",
|
|
2205
|
+
...(failureCategory ? { failureCategory } : {}),
|
|
2206
|
+
...(durationMs !== undefined ? { durationMs } : {}),
|
|
2207
|
+
...(artifactPath ? { artifactPath } : {}),
|
|
2208
|
+
};
|
|
2209
|
+
});
|
|
2210
|
+
const totalAttemptCount = attempts.length;
|
|
2211
|
+
const firstOutcome = attempts[0].status;
|
|
2212
|
+
const finalOutcome = attempts[totalAttemptCount - 1].status;
|
|
2213
|
+
const recovered = totalAttemptCount > 1 &&
|
|
2214
|
+
firstOutcome === "failure" &&
|
|
2215
|
+
finalOutcome === "success";
|
|
2216
|
+
// 一次就成功/失败:卡片不展示 historyLabel;Inspector 仍可拿 attempts 元数据。
|
|
2217
|
+
const historyLabel = totalAttemptCount <= 1
|
|
2218
|
+
? undefined
|
|
2219
|
+
: recovered
|
|
2220
|
+
? `${totalAttemptCount} 次 · 先失败后成功`
|
|
2221
|
+
: `${totalAttemptCount} 次 · 最终${finalOutcome === "success" ? "成功" : "失败"}`;
|
|
2222
|
+
return {
|
|
2223
|
+
totalAttemptCount,
|
|
2224
|
+
...(recovered ? { recovered: true } : {}),
|
|
2225
|
+
firstOutcome,
|
|
2226
|
+
finalOutcome,
|
|
2227
|
+
...(historyLabel ? { historyLabel } : {}),
|
|
2228
|
+
attempts,
|
|
2229
|
+
};
|
|
2230
|
+
}
|
|
2231
|
+
const passes = (options.passes ?? []).filter((pass) => Number.isFinite(pass) && pass >= 1);
|
|
2232
|
+
if (passes.length > 0) {
|
|
2233
|
+
const byPass = new Map((options.passRecords ?? [])
|
|
2234
|
+
.filter((record) => passes.includes(record.pass))
|
|
2235
|
+
.map((record) => [record.pass, record]));
|
|
2236
|
+
const previews = passes.map((pass) => {
|
|
2237
|
+
const record = byPass.get(pass);
|
|
2238
|
+
return {
|
|
2239
|
+
pass,
|
|
2240
|
+
...(record?.status ? { status: record.status } : {}),
|
|
2241
|
+
...(record?.failureCategory
|
|
2242
|
+
? { failureCategory: record.failureCategory }
|
|
2243
|
+
: {}),
|
|
2244
|
+
...(record?.nodeRecordPath
|
|
2245
|
+
? { nodeRecordPath: record.nodeRecordPath }
|
|
2246
|
+
: {}),
|
|
2247
|
+
};
|
|
2248
|
+
});
|
|
2249
|
+
const finalRecord = byPass.get(passes[passes.length - 1]);
|
|
2250
|
+
const finalStatus = finalRecord?.status;
|
|
2251
|
+
let finalOutcome;
|
|
2252
|
+
if (finalStatus) {
|
|
2253
|
+
const normalized = finalStatus.toLowerCase();
|
|
2254
|
+
if (PASS_SUCCESS_STATUSES.has(normalized))
|
|
2255
|
+
finalOutcome = "success";
|
|
2256
|
+
else if (PASS_FAILURE_STATUSES.has(normalized))
|
|
2257
|
+
finalOutcome = "failure";
|
|
2258
|
+
}
|
|
2259
|
+
// 仅一轮 pass 时不在卡片展示(无叙事价值);多轮才展示中文「修复轮次 N」
|
|
2260
|
+
// 主文案,裸 P1/P2 编号不再作为主标题(原始编号仅 hover/title/诊断)。
|
|
2261
|
+
const historyLabel = passes.length <= 1
|
|
2262
|
+
? undefined
|
|
2263
|
+
: `修复轮次 ${passes.join(",")}${finalOutcome === "success"
|
|
2264
|
+
? " · 最终成功"
|
|
2265
|
+
: finalOutcome === "failure"
|
|
2266
|
+
? " · 最终失败"
|
|
2267
|
+
: ""}`;
|
|
2268
|
+
return {
|
|
2269
|
+
totalAttemptCount: passes.length,
|
|
2270
|
+
...(finalOutcome ? { finalOutcome } : {}),
|
|
2271
|
+
...(historyLabel ? { historyLabel } : {}),
|
|
2272
|
+
passes: previews,
|
|
2273
|
+
};
|
|
2274
|
+
}
|
|
2275
|
+
// 一次执行成功/失败不需要卡片历史行;不投影无叙事的 "1 次"。
|
|
2276
|
+
if (isExecutedNodeStatus(options.status)) {
|
|
2277
|
+
return undefined;
|
|
2278
|
+
}
|
|
2279
|
+
return undefined;
|
|
2280
|
+
}
|
|
2281
|
+
/**
|
|
2282
|
+
* Fill executionSummary for nodes without attempt records: passes-driven
|
|
2283
|
+
* summaries from convergence passHistory statuses, then implicit single
|
|
2284
|
+
* execution for executed legacy nodes. Attempt-driven summaries projected in
|
|
2285
|
+
* parseDagNodes take precedence and are never overwritten.
|
|
2286
|
+
*/
|
|
2287
|
+
function applyNodeExecutionSummaries(nodes, state) {
|
|
2288
|
+
const passStatusByNode = buildNodePassStatusIndex(state);
|
|
2289
|
+
for (const node of nodes) {
|
|
2290
|
+
if (node.executionSummary)
|
|
2291
|
+
continue;
|
|
2292
|
+
const summary = projectExecutionSummary({
|
|
2293
|
+
passes: node.passes,
|
|
2294
|
+
status: node.status,
|
|
2295
|
+
passRecords: passStatusByNode.get(node.nodeId),
|
|
2296
|
+
});
|
|
2297
|
+
if (summary)
|
|
2298
|
+
node.executionSummary = summary;
|
|
2299
|
+
}
|
|
2300
|
+
}
|
|
2301
|
+
function buildNodePassStatusIndex(state) {
|
|
2302
|
+
const index = new Map();
|
|
2303
|
+
for (const pass of state.convergence?.passHistory ?? []) {
|
|
2304
|
+
for (const ref of pass.artifactRefs) {
|
|
2305
|
+
if (!ref.nodeId)
|
|
2306
|
+
continue;
|
|
2307
|
+
// Copy before push: passHistory artifactRefs are readonly in the run
|
|
2308
|
+
// state shape, so the accumulator must not alias the source array.
|
|
2309
|
+
const records = [...(index.get(ref.nodeId) ?? [])];
|
|
2310
|
+
records.push({
|
|
2311
|
+
pass: pass.pass,
|
|
2312
|
+
...(ref.status ? { status: ref.status } : {}),
|
|
2313
|
+
...(ref.failureCategory
|
|
2314
|
+
? { failureCategory: ref.failureCategory }
|
|
2315
|
+
: {}),
|
|
2316
|
+
...(ref.nodeRecordPath ? { nodeRecordPath: ref.nodeRecordPath } : {}),
|
|
2317
|
+
});
|
|
2318
|
+
index.set(ref.nodeId, records);
|
|
2319
|
+
}
|
|
2320
|
+
}
|
|
2321
|
+
return index;
|
|
2322
|
+
}
|
|
2323
|
+
function parseDagNodes(parsed, rankByNode, modelByNode) {
|
|
2324
|
+
const nodes = [];
|
|
2325
|
+
const nodesValue = parsed.nodes;
|
|
2326
|
+
if (nodesValue &&
|
|
2327
|
+
typeof nodesValue === "object" &&
|
|
2328
|
+
!Array.isArray(nodesValue)) {
|
|
2329
|
+
for (const [nodeId, nodeVal] of Object.entries(nodesValue)) {
|
|
2330
|
+
if (!nodeVal || typeof nodeVal !== "object" || Array.isArray(nodeVal))
|
|
2331
|
+
continue;
|
|
2332
|
+
const node = nodeVal;
|
|
2333
|
+
const resolvedId = readString(node, "id") ?? nodeId;
|
|
2334
|
+
const nodeStatus = readString(node, "status");
|
|
2335
|
+
const outputPreview = nodeOutputPreview(node);
|
|
2336
|
+
const errorPreview = nodeErrorPreview(node, nodeStatus);
|
|
2337
|
+
const attempts = Array.isArray(node.attempts) ? node.attempts : undefined;
|
|
2338
|
+
// Only attempt-driven summaries are projected here; pass-driven and
|
|
2339
|
+
// implicit single-execution summaries are filled afterwards by
|
|
2340
|
+
// applyNodeExecutionSummaries so they are never shadowed by the
|
|
2341
|
+
// implicit-single fallback.
|
|
2342
|
+
const executionSummary = attempts && attempts.length > 0
|
|
2343
|
+
? projectExecutionSummary({ attempts, status: nodeStatus })
|
|
2344
|
+
: undefined;
|
|
2345
|
+
const origin = node.origin &&
|
|
2346
|
+
typeof node.origin === "object" &&
|
|
2347
|
+
!Array.isArray(node.origin)
|
|
2348
|
+
? node.origin
|
|
2349
|
+
: undefined;
|
|
2350
|
+
nodes.push({
|
|
2351
|
+
nodeId: resolvedId,
|
|
2352
|
+
rank: rankByNode.get(nodeId) ?? rankByNode.get(resolvedId),
|
|
2353
|
+
executor: readString(node, "executor"),
|
|
2354
|
+
model: readString(node, "model") ?? modelByNode.get(resolvedId),
|
|
2355
|
+
status: nodeStatus,
|
|
2356
|
+
label: readString(node, "label") ?? readString(node, "title") ?? nodeId,
|
|
2357
|
+
durationMs: readNumber(node, "durationMs"),
|
|
2358
|
+
startedAt: readString(node, "startedAt"),
|
|
2359
|
+
finishedAt: readString(node, "finishedAt"),
|
|
2360
|
+
...(outputPreview ? { outputPreview } : {}),
|
|
2361
|
+
...(errorPreview ? { errorPreview } : {}),
|
|
2362
|
+
...(attempts && attempts.length > 0
|
|
2363
|
+
? { attemptCount: attempts.length }
|
|
2364
|
+
: {}),
|
|
2365
|
+
...(executionSummary ? { executionSummary } : {}),
|
|
2366
|
+
...(readString(node, "failureCategory")
|
|
2367
|
+
? { failureCategory: readString(node, "failureCategory") }
|
|
2368
|
+
: {}),
|
|
2369
|
+
...(origin?.kind === "imported" || origin?.kind === "executed"
|
|
2370
|
+
? { originKind: origin.kind }
|
|
2371
|
+
: {}),
|
|
2372
|
+
});
|
|
2373
|
+
}
|
|
2374
|
+
return nodes.sort((a, b) => a.nodeId.localeCompare(b.nodeId));
|
|
2375
|
+
}
|
|
2376
|
+
const nodeArray = readObjectArray(parsed, "nodes");
|
|
2377
|
+
for (const node of nodeArray) {
|
|
2378
|
+
const nodeId = readString(node, "id") ?? readString(node, "nodeId") ?? "unknown";
|
|
2379
|
+
const nodeStatus = readString(node, "status");
|
|
2380
|
+
const outputPreview = nodeOutputPreview(node);
|
|
2381
|
+
const errorPreview = nodeErrorPreview(node, nodeStatus);
|
|
2382
|
+
const attempts = Array.isArray(node.attempts) ? node.attempts : undefined;
|
|
2383
|
+
// Only attempt-driven summaries are projected here; pass-driven and
|
|
2384
|
+
// implicit single-execution summaries are filled afterwards by
|
|
2385
|
+
// applyNodeExecutionSummaries so they are never shadowed by the
|
|
2386
|
+
// implicit-single fallback.
|
|
2387
|
+
const executionSummary = attempts && attempts.length > 0
|
|
2388
|
+
? projectExecutionSummary({ attempts, status: nodeStatus })
|
|
2389
|
+
: undefined;
|
|
2390
|
+
const origin = node.origin &&
|
|
2391
|
+
typeof node.origin === "object" &&
|
|
2392
|
+
!Array.isArray(node.origin)
|
|
2393
|
+
? node.origin
|
|
2394
|
+
: undefined;
|
|
2395
|
+
nodes.push({
|
|
2396
|
+
nodeId,
|
|
2397
|
+
rank: rankByNode.get(nodeId),
|
|
2398
|
+
executor: readString(node, "executor"),
|
|
2399
|
+
model: readString(node, "model") ?? modelByNode.get(nodeId),
|
|
2400
|
+
status: nodeStatus,
|
|
2401
|
+
label: readString(node, "label") ?? readString(node, "title") ?? nodeId,
|
|
2402
|
+
durationMs: readNumber(node, "durationMs"),
|
|
2403
|
+
startedAt: readString(node, "startedAt"),
|
|
2404
|
+
finishedAt: readString(node, "finishedAt"),
|
|
2405
|
+
...(outputPreview ? { outputPreview } : {}),
|
|
2406
|
+
...(errorPreview ? { errorPreview } : {}),
|
|
2407
|
+
...(attempts && attempts.length > 0
|
|
2408
|
+
? { attemptCount: attempts.length }
|
|
2409
|
+
: {}),
|
|
2410
|
+
...(executionSummary ? { executionSummary } : {}),
|
|
2411
|
+
...(readString(node, "failureCategory")
|
|
2412
|
+
? { failureCategory: readString(node, "failureCategory") }
|
|
2413
|
+
: {}),
|
|
2414
|
+
...(origin?.kind === "imported" || origin?.kind === "executed"
|
|
2415
|
+
? { originKind: origin.kind }
|
|
2416
|
+
: {}),
|
|
2417
|
+
});
|
|
2418
|
+
}
|
|
2419
|
+
return nodes.sort((a, b) => a.nodeId.localeCompare(b.nodeId));
|
|
2420
|
+
}
|
|
2421
|
+
async function safeReadText(filePath) {
|
|
2422
|
+
try {
|
|
2423
|
+
return await readFile(filePath, "utf-8");
|
|
2424
|
+
}
|
|
2425
|
+
catch {
|
|
2426
|
+
return undefined;
|
|
2427
|
+
}
|
|
2428
|
+
}
|
|
2429
|
+
async function safeReadJson(filePath) {
|
|
2430
|
+
const raw = await safeReadText(filePath);
|
|
2431
|
+
if (!raw)
|
|
2432
|
+
return undefined;
|
|
2433
|
+
const parsed = safeParseJson(raw);
|
|
2434
|
+
if (!parsed || Array.isArray(parsed))
|
|
2435
|
+
return undefined;
|
|
2436
|
+
return parsed;
|
|
2437
|
+
}
|
|
2438
|
+
function safeParseJson(raw) {
|
|
2439
|
+
try {
|
|
2440
|
+
const parsed = JSON.parse(raw);
|
|
2441
|
+
if (parsed === null ||
|
|
2442
|
+
typeof parsed !== "object" ||
|
|
2443
|
+
Array.isArray(parsed)) {
|
|
2444
|
+
return undefined;
|
|
2445
|
+
}
|
|
2446
|
+
return parsed;
|
|
2447
|
+
}
|
|
2448
|
+
catch {
|
|
2449
|
+
return undefined;
|
|
2450
|
+
}
|
|
2451
|
+
}
|
|
2452
|
+
function readObject(value, key) {
|
|
2453
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
2454
|
+
return undefined;
|
|
2455
|
+
const child = value[key];
|
|
2456
|
+
if (!child || typeof child !== "object" || Array.isArray(child))
|
|
2457
|
+
return undefined;
|
|
2458
|
+
return child;
|
|
2459
|
+
}
|
|
2460
|
+
function readObjectArray(value, key) {
|
|
2461
|
+
if (!value || typeof value !== "object")
|
|
2462
|
+
return [];
|
|
2463
|
+
const child = value[key];
|
|
2464
|
+
if (!Array.isArray(child))
|
|
2465
|
+
return [];
|
|
2466
|
+
return child.filter((item) => Boolean(item) && typeof item === "object" && !Array.isArray(item));
|
|
2467
|
+
}
|
|
2468
|
+
function readString(value, key) {
|
|
2469
|
+
if (!value || typeof value !== "object")
|
|
2470
|
+
return undefined;
|
|
2471
|
+
const child = value[key];
|
|
2472
|
+
return typeof child === "string" ? child : undefined;
|
|
2473
|
+
}
|
|
2474
|
+
function readStringArray(value, key) {
|
|
2475
|
+
if (!value || typeof value !== "object")
|
|
2476
|
+
return [];
|
|
2477
|
+
const child = value[key];
|
|
2478
|
+
if (!Array.isArray(child))
|
|
2479
|
+
return [];
|
|
2480
|
+
return child.filter((item) => typeof item === "string");
|
|
2481
|
+
}
|
|
2482
|
+
function readNumber(value, key) {
|
|
2483
|
+
if (!value || typeof value !== "object")
|
|
2484
|
+
return undefined;
|
|
2485
|
+
const child = value[key];
|
|
2486
|
+
return typeof child === "number" && Number.isFinite(child)
|
|
2487
|
+
? child
|
|
2488
|
+
: undefined;
|
|
2489
|
+
}
|
|
2490
|
+
function readStringMatrix(value, key) {
|
|
2491
|
+
if (!value || typeof value !== "object")
|
|
2492
|
+
return [];
|
|
2493
|
+
const child = value[key];
|
|
2494
|
+
if (!Array.isArray(child))
|
|
2495
|
+
return [];
|
|
2496
|
+
return child
|
|
2497
|
+
.filter((rank) => Array.isArray(rank))
|
|
2498
|
+
.map((rank) => rank.filter((id) => typeof id === "string"));
|
|
2499
|
+
}
|