@tea-agent/loop-agent 0.39.0-beta.1 → 0.39.0-beta.11
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 +4 -1
- package/CHANGELOG.md +349 -97
- package/README.md +9 -5
- package/bin/loop-agent.js +7 -3
- package/dist/application/task-lifecycle/advance.js +12 -0
- package/dist/application/task-lifecycle/recommendations.js +13 -2
- package/dist/build-stamp.json +3 -3
- package/dist/cli/command-definitions.js +23 -5
- package/dist/cli/program.js +21 -2
- package/dist/cli/update/notifier.js +2 -2
- package/dist/cli/update/runtime-activity.js +1 -29
- package/dist/cli.js +2 -1
- package/dist/commands/client-recovery.js +3 -0
- package/dist/commands/dag-follow-up.js +138 -0
- package/dist/commands/dag-rerun.js +55 -1
- package/dist/commands/init-model-catalog.js +464 -0
- package/dist/commands/init-upgrade.js +265 -97
- package/dist/commands/init.js +455 -28
- package/dist/commands/inspect-next.js +8 -0
- package/dist/commands/task-advance.js +19 -0
- package/dist/executors/dag-pi-executor.js +2370 -64
- package/dist/executors/pi-executor.js +11 -2
- package/dist/executors/pi-extension-resolver.js +233 -0
- package/dist/executors/pi-playwright-cli-tool.js +74 -28
- package/dist/executors/pi-read-budget-policy.js +239 -0
- package/dist/executors/pi-sdk-executor.js +225 -33
- package/dist/executors/pi-writer-tool-policy.js +57 -1
- package/dist/executors/shell-executor.js +1258 -224
- package/dist/executors/shell-write-guard.js +14 -0
- package/dist/executors/workspace-write-snapshot.js +68 -0
- package/dist/infrastructure/harness/atomic-write.js +23 -0
- package/dist/shared/dag-failure-category.js +150 -0
- package/dist/shared/dag-prompt-override.js +27 -0
- package/dist/shared/openspec-spec.js +70 -4
- package/dist/shared/operator/capabilities.js +39 -0
- package/dist/shared/playwright-cli-command-policy.js +15 -0
- package/dist/shared/update/console-notifier.js +100 -0
- package/dist/{cli → shared}/update/npm-client.js +70 -15
- package/dist/{cli → shared}/update/state.js +41 -13
- package/dist/task/config-types.js +50 -5
- package/dist/task/contract/apply.js +11 -1
- package/dist/task/contract/constants.js +2 -0
- package/dist/task/contract/hash.js +3 -0
- package/dist/task/contract/observe.js +25 -4
- package/dist/task/contract/paths.js +2 -1
- package/dist/task/contract/project.js +18 -1
- package/dist/task/contract/schema.js +4 -1
- package/dist/task/contract/transaction.js +12 -0
- package/dist/task/frontend-project-capability.js +199 -18
- package/dist/task/source-prepare/completeness.js +188 -0
- package/dist/task/source-prepare/fragment-inventory.js +477 -0
- package/dist/task/source-prepare/index.js +3 -0
- package/dist/task/source-prepare/ledger-reconciliation.js +127 -0
- package/dist/task/source-prepare/ledger-review.js +214 -0
- package/dist/task/source-prepare/ledger.js +545 -0
- package/dist/task/source-prepare/parse-intent.js +24 -4
- package/dist/task/source-prepare/prepare.js +298 -1
- package/dist/task/source-prepare/semantic-intake.js +25 -5
- package/dist/task/source-prepare/source-fidelity-pi.js +384 -0
- package/dist/task/source-prepare/types.js +22 -0
- package/dist/task/source-references.js +22 -1
- package/dist/worker/cli.js +18 -2
- package/dist/worker/console/chat/chat-event-store.js +75 -5
- package/dist/worker/console/chat/context-panel.js +7 -0
- package/dist/worker/console/chat/deferred-turn.js +12 -0
- package/dist/worker/console/chat/operation-card.js +8 -1
- package/dist/worker/console/chat/pi-console-config.js +81 -10
- package/dist/worker/console/chat/pi-runtime.js +331 -18
- package/dist/worker/console/chat/provider-error.js +98 -0
- package/dist/worker/console/chat/routes.js +395 -59
- package/dist/worker/console/chat/session-stats.js +179 -0
- package/dist/worker/console/chat/session-store.js +93 -63
- package/dist/worker/console/chat/todos.js +115 -0
- package/dist/worker/console/chat/tool-preview.js +42 -0
- package/dist/worker/console/chat/turn-process.js +20 -55
- package/dist/worker/console/chat/user-questions.js +266 -0
- package/dist/worker/console/chat/workspace-landing.js +79 -0
- package/dist/worker/console/console-handoff.js +82 -0
- package/dist/worker/console/console-update-and-init.js +115 -0
- package/dist/worker/console/console-update-runtime.js +84 -0
- package/dist/worker/console/dag-execution-receipt.js +33 -0
- package/dist/worker/console/draft-store.js +49 -0
- package/dist/worker/console/frontend-human-decision-adapter.js +19 -0
- package/dist/worker/console/frontend-split-operation-adapter.js +20 -0
- package/dist/worker/console/index.js +3 -0
- package/dist/worker/console/inspect-split.js +18 -0
- package/dist/worker/console/native-directory-picker.js +13 -0
- package/dist/worker/console/operation-run-facts.js +19 -3
- package/dist/worker/console/operation-store.js +227 -7
- package/dist/worker/console/operator-actions.js +388 -0
- package/dist/worker/console/operator-surface-health.js +1 -0
- package/dist/worker/console/operator-user-error.js +2 -2
- package/dist/worker/console/prd-intake-bridge.js +13 -0
- package/dist/worker/console/routes.js +355 -3
- package/dist/worker/console/security.js +67 -13
- package/dist/worker/console/server.js +609 -164
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-C4kVYweA.js → abnfDiagram-N423BO3Z-CXj_GnSb.js} +1 -1
- package/dist/worker/console/static/assets/{arc-Bj2M8iO5.js → arc-BZp6JAp7.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-CLlXe4-6.js → architectureDiagram-T3A2C74G-DfpcEuYU.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-psEp5xH0.js → blockDiagram-VBNYF7ZC-_Gf0xadb.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-DeKcOCGJ.js → c4Diagram-5PPSVZJV-Ct2QPCmv.js} +1 -1
- package/dist/worker/console/static/assets/channel-3TxJgYaH.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-K0C744p1.js → chunk-2GRJ4B5K-BfklkzKl.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-xbNw4J9-.js → chunk-2Q5K7J3B-Dy25vZJV.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-DjLaSDUO.js → chunk-5RXB4S5H-BFlCRZep.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-CtiPlKel.js → chunk-5VM5RSS4-op3oVxIE.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-DZtolir7.js → chunk-6Q2QTUOP-C0R7rzF2.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-CPcdNeew.js → chunk-GF5L2VYU-Bt44TCGy.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-NgHc6V37.js → chunk-JWPE2WC7-_uEE_XFx.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-DQ_T-hg8.js → chunk-KBJHAD2P-C3TOYGZ9.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-DgLsxcVP.js → chunk-RYQCIY6F-Cz60oBPV.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-UrVoM9z1.js → chunk-XXDRQBXY-8Bik0qis.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-BHIkXpp3.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-BHIkXpp3.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-CLkYvTb3.js → cose-bilkent-JH36ORCC-C2QIOA4a.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-BwT_xKrE.js → cynefin-VYW2F7L2-CSH_yUUd.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-Bi9tuzif.js → cynefinDiagram-MW4NZA55-BDLw2XFM.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-BCIqkWBV.js → dagre-VZM6K2ZE-CcD9ZtF4.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-Dip6l9_Z.js → diagram-7IWD3JNH-DqwtkBsI.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-B-xkh_wK.js → diagram-B4RE2ZJO-BWVEcqsC.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-DZO0kTt_.js → diagram-LBJQPF4R-M-WAbtQi.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-D6ZplNbZ.js → diagram-Q27KOJAE-DKW7SixP.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-BPekbhoS.js → diagram-UB23O5K3-PHHPPtrj.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-BtmaJpK5.js → ebnfDiagram-BXEA7PRR-BQD-B4RX.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-6DS0Xc44.js → erDiagram-JOGREHBK-BFvULb51.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-CihnROSm.js → flowDiagram-UKHOOZJN-Bw-aXTCb.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-CZC4zOE2.js → ganttDiagram-PKOTCBZU-BGKw04Qx.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-Dq1fhzGN.js → gitGraphDiagram-DS77QQ5N-RqKaPR-I.js} +1 -1
- package/dist/worker/console/static/assets/index-B_D8rbWc.js +407 -0
- package/dist/worker/console/static/assets/index-BdNx6fj0.css +1 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-DlSl4BGx.js → infoDiagram-6WML65LV-YO5dnzrJ.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-snnzFl_U.js → ishikawaDiagram-WSZJBQD7-Bi1VZHMq.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-BDE7qpMx.js → journeyDiagram-NVQOT4AX-Bszls1DD.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-CD2Ci04G.js → kanban-definition-27J2QSJJ-PhzeaZ09.js} +1 -1
- package/dist/worker/console/static/assets/{linear-DdnapKIH.js → linear-BsjbDoXi.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-BVjAT9b8.js → mermaid.core-0B7NnWKk.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-D0yJk-zA.js → mindmap-definition-FAOFIHXS-BJr4Fj-q.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-BVo9tFP-.js → pegDiagram-VL7TDLO6-moC4fpGB.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-DnOV-mZ_.js → pieDiagram-7S7Q4E2Y-BEw37-2c.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-CzUJo58i.js → quadrantDiagram-CIZ2JOQS-Cq6LyasU.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-Bvikrolh.js → railroadDiagram-AXF67PYL-DUCMcK0D.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-DtaSVCap.js → requirementDiagram-LRYGKXZP-C3upTZm7.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-C2c9A0wW.js → sankeyDiagram-W5VNT64P-BI_gMsCW.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-DmXJcU7r.js → sequenceDiagram-SI44F4Z6-YFOIRzfN.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-B4GUFW92.js → sizeCapture-X5ZJPWSS-dOnB7UDD.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-Dsad2MXf.js → stateDiagram-OKZ733FA-BXUniaIh.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-Cdi6UhLa.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-DGq48Fbi.js → swimlanes-SLNWSIFB-2tA4wTNu.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-D-RJBbb0.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-CSSi6OFf.js → timeline-definition-Z64GVDOM-DO0HkJXC.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-BqyzPrWv.js → vennDiagram-T6HMQDX7-DvIOzixv.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-DlznVb6S.js → wardleyDiagram-T6FBY63Y-DXDZ0cTj.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-CfBcgJ_K.js → xychartDiagram-ELKLHX3M-B32Ark0D.js} +1 -1
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/active-run-badge.js +17 -0
- package/dist/worker/console/static-src/app/console-types.js +68 -4
- package/dist/worker/console/static-src/app/useConsoleShell.js +30 -12
- package/dist/worker/console/static-src/app/useOperatorActions.js +41 -6
- package/dist/worker/console/static-src/app/useRecoveryConsole.js +45 -10
- package/dist/worker/console/static-src/app/useRunProgress.js +47 -0
- package/dist/worker/console/static-src/app/useTaskWizard.js +74 -2
- package/dist/worker/console/static-src/night/useNightBoard.js +8 -5
- package/dist/worker/console/static-src/operator-chat/cards/failure-category-advice.js +25 -0
- package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +141 -8
- package/dist/worker/console/static-src/operator-chat/compaction-message.js +79 -0
- package/dist/worker/console/static-src/operator-chat/details-dag-actions.js +122 -0
- package/dist/worker/console/static-src/operator-chat/input-history.js +76 -0
- package/dist/worker/console/static-src/operator-chat/mutation-gate.js +1 -1
- package/dist/worker/console/static-src/operator-chat/refs.js +3 -0
- package/dist/worker/console/static-src/operator-chat/runtime-selection-labels.js +27 -0
- package/dist/worker/console/static-src/operator-chat/spatial-overlay.js +1 -2
- package/dist/worker/console/static-src/operator-chat/turn-stream-controller.js +25 -0
- package/dist/worker/console/static-src/operator-chat/turn-submission.js +16 -3
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +181 -87
- package/dist/worker/console/static-src/operator-chat/useChatStream.js +183 -48
- package/dist/worker/console/static-src/operator-chat/useChatThread.js +177 -8
- package/dist/worker/console/static-src/operator-chat/useComposer.js +32 -0
- package/dist/worker/console/static-src/operator-chat/useRepoBrowser.js +63 -9
- package/dist/worker/console/static-src/operator-chat/useWorkspaceBrowserGate.js +4 -0
- package/dist/worker/console/static-src/operator-chat/workspace-layout-mode.js +3 -3
- package/dist/worker/console/static-src/pages/tasks/run-id-resolution.js +79 -0
- package/dist/worker/console/static-src/pages/tasks/run-ownership-verify.js +23 -0
- package/dist/worker/console/static-src/pages/tasks/run-panel-progress.js +110 -0
- package/dist/worker/console/static-src/shell/useWorkspaces.js +299 -0
- package/dist/worker/console/static-src/shell/workspace-route.js +339 -0
- package/dist/worker/console/workspace-context.js +344 -0
- package/dist/worker/console/workspace-registry.js +214 -0
- package/dist/worker/loop-agent/loop-agent-client.js +7 -3
- package/dist/worker/materialize/frontend-split-task-materializer.js +72 -0
- package/dist/worker/materialize/harness-task-lineage.js +5 -2
- package/dist/worker/observability/init-runtime-activity.js +26 -0
- package/dist/worker/observability/read-model.js +22 -0
- package/dist/worker/observe/node-input.js +160 -3
- package/dist/worker/observe/node-process.js +377 -0
- package/dist/worker/observe/routes.js +224 -3
- package/dist/worker/observe/server.js +4 -0
- package/dist/worker/observe/shell-handler-keys.js +34 -0
- package/dist/worker/observe/static/api.js +90 -4
- package/dist/worker/observe/static/app.js +18 -70
- package/dist/worker/observe/static/constants.js +8 -1
- package/dist/worker/observe/static/dag-history-labels.js +4 -0
- package/dist/worker/observe/static/dag-node-purpose.d.ts +6 -0
- package/dist/worker/observe/static/dag-node-purpose.js +208 -0
- package/dist/worker/observe/static/format.js +60 -0
- package/dist/worker/observe/static/index.html +6 -1
- package/dist/worker/observe/static/inspect-workspace.js +307 -0
- package/dist/worker/observe/static/markdown-render.js +20 -1
- package/dist/worker/observe/static/operator-chrome.css +145 -20
- package/dist/worker/observe/static/operator-chrome.d.ts +20 -3
- package/dist/worker/observe/static/operator-chrome.js +330 -96
- package/dist/worker/observe/static/prompt-restart-candidates.d.ts +18 -0
- package/dist/worker/observe/static/prompt-restart-candidates.js +36 -0
- package/dist/worker/observe/static/router.d.ts +46 -0
- package/dist/worker/observe/static/router.js +25 -1
- package/dist/worker/observe/static/state.d.ts +50 -0
- package/dist/worker/observe/static/state.js +184 -1
- package/dist/worker/observe/static/styles.css +395 -0
- package/dist/worker/observe/static/views/dag-graph.js +4 -2
- package/dist/worker/observe/static/views/dag-inspector.js +1067 -9
- package/dist/worker/observe/static/views/dag.d.ts +13 -0
- package/dist/worker/observe/static/views/dag.js +14 -5
- package/dist/worker/observe/static/views/dashboard.js +6 -4
- package/dist/worker/observe/static/views/failures.js +5 -2
- package/dist/worker/observe/static/views/night.js +2 -2
- package/dist/worker/observe/static/views/pool.js +1 -0
- package/dist/worker/observe/static/views/run.js +3 -1
- package/dist/worker/observe/static/views/session-timeline.js +78 -9
- package/dist/worker/observe/static/views/task.js +56 -1
- package/dist/workflows/dag/backend-test-case-coverage-analysis.js +49 -3
- package/dist/workflows/dag/backend-test-markdown-workflow.js +176 -1
- package/dist/workflows/dag/backend-test-pytest-collection.js +98 -14
- package/dist/workflows/dag/backend-test-writer-completeness.js +17 -17
- package/dist/workflows/dag/contract-output-registry.js +15 -0
- package/dist/workflows/dag/contract-validator-registrations.js +1 -2
- package/dist/workflows/dag/dynamic-runtime/loop-until.js +1 -1
- package/dist/workflows/dag/dynamic-runtime/map.js +2 -1
- package/dist/workflows/dag/failure-category.js +7 -116
- package/dist/workflows/dag/frontend-closeout.js +221 -0
- package/dist/workflows/dag/frontend-design-policy.js +400 -0
- package/dist/workflows/dag/frontend-human-decision.js +182 -0
- package/dist/workflows/dag/frontend-implementation-contract.js +538 -166
- package/dist/workflows/dag/frontend-plan-render.js +24 -0
- package/dist/workflows/dag/frontend-prewrite-gate.js +388 -392
- package/dist/workflows/dag/frontend-provider-capability-matrix.js +159 -0
- package/dist/workflows/dag/frontend-recovery-capsule.js +455 -0
- package/dist/workflows/dag/frontend-recovery-controller.js +202 -0
- package/dist/workflows/dag/frontend-recovery-lineage.js +178 -0
- package/dist/workflows/dag/frontend-recovery-plan.js +17 -10
- package/dist/workflows/dag/frontend-recovery-run.js +33 -20
- package/dist/workflows/dag/frontend-repair.js +7 -424
- package/dist/workflows/dag/frontend-review-context.js +288 -14
- package/dist/workflows/dag/frontend-review-findings.js +270 -0
- package/dist/workflows/dag/frontend-risk.js +15 -2
- package/dist/workflows/dag/frontend-shadow-dual-write.js +814 -0
- package/dist/workflows/dag/frontend-shape-capsule-store.js +191 -0
- package/dist/workflows/dag/frontend-shape-facts.js +409 -0
- package/dist/workflows/dag/frontend-shape.js +427 -0
- package/dist/workflows/dag/frontend-source-fidelity-ledger.js +108 -0
- package/dist/workflows/dag/frontend-split-application-service.js +203 -0
- package/dist/workflows/dag/frontend-split-orchestrator.js +899 -0
- package/dist/workflows/dag/frontend-test-case-checklist.js +30 -4
- package/dist/workflows/dag/frontend-test-case-manifest.js +11 -4
- package/dist/workflows/dag/frontend-test-case-quality.js +11 -16
- package/dist/workflows/dag/frontend-test-environment-probe.js +230 -0
- package/dist/workflows/dag/frontend-test-html-report.js +3 -1
- package/dist/workflows/dag/frontend-test-l5-report.js +3 -1
- package/dist/workflows/dag/frontend-test-layout.js +159 -0
- package/dist/workflows/dag/frontend-test-markdown.js +61 -0
- package/dist/workflows/dag/frontend-test-result-contract.js +188 -42
- package/dist/workflows/dag/frontend-test-standard-scenarios.js +70 -0
- package/dist/workflows/dag/frontend-typed-event-store.js +452 -0
- package/dist/workflows/dag/frontend-typed-event-transaction.js +180 -0
- package/dist/workflows/dag/frontend-verification-trace.js +52 -17
- package/dist/workflows/dag/frontend-worktree-diff.js +250 -17
- package/dist/workflows/dag/frontend-writer-admission.js +272 -0
- package/dist/workflows/dag/frontend-writer-status.js +244 -0
- package/dist/workflows/dag/init-hybrid.js +1072 -660
- package/dist/workflows/dag/interrupt-request.js +10 -3
- package/dist/workflows/dag/lifecycle.js +3 -3
- package/dist/workflows/dag/node-execution.js +552 -58
- package/dist/workflows/dag/prompt.js +44 -19
- package/dist/workflows/dag/recovery-lease.js +80 -0
- package/dist/workflows/dag/report.js +37 -1
- package/dist/workflows/dag/rerun-feedback.js +124 -1
- package/dist/workflows/dag/rerun-plan.js +144 -5
- package/dist/workflows/dag/rerun-run.js +81 -6
- package/dist/workflows/dag/rerun-task.js +29 -0
- package/dist/workflows/dag/retry-policy.js +318 -8
- package/dist/workflows/dag/runner.js +429 -122
- package/dist/workflows/dag/scheduler.js +114 -16
- package/dist/workflows/dag/structured-output-repair.js +712 -0
- package/dist/workflows/dag/types.js +338 -31
- package/dist/workflows/dag/upstream-artifacts.js +0 -4
- package/dist/workflows/dag/validate.js +92 -32
- package/docs/README.md +2 -0
- package/docs/architecture/evolution.md +45 -0
- package/docs/init-surface.manifest.json +9 -0
- package/docs/operations/README.md +1 -1
- package/docs/operations/local-development-environment.md +21 -0
- package/docs/templates/README.md +2 -1
- package/docs/templates/agent-dag-report.schema.json +8 -2
- package/docs/templates/agent-dag.schema.json +63 -1
- package/docs/templates/backend-test-dag.json +265 -237
- package/docs/templates/frontend-implementation-contract.schema.json +684 -24
- package/docs/templates/frontend-test-case-checklist.md +2 -2
- package/docs/templates/frontend-test-dag.json +8 -10
- package/docs/templates/frontend-test-dag.retrieve-context.prompt.md +1 -1
- package/docs/templates/init-managed-agents.md +13 -3
- package/docs/templates/spec-registry.schema.json +45 -0
- package/harness.json +1 -1
- package/package.json +7 -1
- package/scripts/next-info.mjs +356 -0
- package/scripts/next-publish-gate.mjs +238 -0
- package/scripts/release-source-binding.mjs +251 -0
- package/skills/codebase-scout/SKILL.md +1 -1
- package/skills/fe-test-ui-scout/SKILL.md +65 -0
- package/skills/fe-test-ui-scout/references/ledger-schema.md +62 -0
- package/skills/fe-test-ui-scout/references/recon-protocol.md +54 -0
- package/skills/frontend-bounded-implement/SKILL.md +8 -14
- package/skills/frontend-design-review/SKILL.md +20 -41
- package/skills/frontend-implementation/SKILL.md +3 -4
- package/skills/frontend-implementation/references/design-spec.md +15 -10
- package/skills/frontend-implementation/references/node-contracts.md +23 -13
- package/skills/frontend-review/SKILL.md +22 -14
- package/skills/frontend-review/references/review-findings.md +6 -7
- package/skills/loop-agent/references/command-reference.md +12 -4
- package/skills/loop-agent/references/hybrid-dag.md +8 -1
- package/skills/playwright-cli/SKILL.md +23 -1
- package/skills/playwright-cli-case-generator/SKILL.md +29 -8
- package/dist/worker/console/static/assets/channel-CPF4N7pf.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-C2DwA_Y1.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-C2DwA_Y1.js +0 -1
- package/dist/worker/console/static/assets/index-DTZOKgAn.js +0 -404
- package/dist/worker/console/static/assets/index-Ya5FE7cD.css +0 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-B8FB_cNk.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-CrZYaWfx.js +0 -8
- package/dist/worker/console/static-src/operator-chat/activity-rail-presentation.js +0 -73
- package/dist/worker/console/static-src/operator-chat/useActivityRailTransition.js +0 -59
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* User-level Workspace Registry (2026-08-22 multi-workspace foundation, W1).
|
|
3
|
+
*
|
|
4
|
+
* Persists the set of registered Console workspaces at
|
|
5
|
+
* `<appDataRoot>/workspaces.json` (user level, shared across Console boots),
|
|
6
|
+
* independent of per-repo fingerprint namespaces.
|
|
7
|
+
*
|
|
8
|
+
* Invariants:
|
|
9
|
+
* - workspaceId is stable per normalized realpath: it is derived from
|
|
10
|
+
* sha256("loop-console-workspace-v1\0" + realpath) and never minted twice.
|
|
11
|
+
* - REMOVE is soft (hidden=true); on-disk directories, AppData namespaces,
|
|
12
|
+
* Sessions, Operations and DAGs are never deleted.
|
|
13
|
+
* - Re-adding a previously removed workspace RESTORES the original record
|
|
14
|
+
* (same workspaceId + repoFingerprint; only lastAccessedAt advances).
|
|
15
|
+
* - Duplicate visible realpath is rejected; add failures leave the registry
|
|
16
|
+
* byte-identical (same ids and order).
|
|
17
|
+
*/
|
|
18
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
19
|
+
import { realpathSync, statSync } from "node:fs";
|
|
20
|
+
import path from "node:path";
|
|
21
|
+
import { readJsonIfExists, writeSecureJson, } from "./app-data.js";
|
|
22
|
+
import { normalizeWorktreeRealpath, repoFingerprintV1, } from "./repo-fingerprint.js";
|
|
23
|
+
export const WORKSPACE_REGISTRY_SCHEMA_VERSION = 1;
|
|
24
|
+
/** Stable workspaceId: sha256("loop-console-workspace-v1\0" + normalizedRealpath). */
|
|
25
|
+
export function workspaceIdForRealpath(normalizedRealpath) {
|
|
26
|
+
return createHash("sha256")
|
|
27
|
+
.update(`loop-console-workspace-v1\0${normalizedRealpath}`, "utf8")
|
|
28
|
+
.digest("hex");
|
|
29
|
+
}
|
|
30
|
+
export class WorkspaceRegistry {
|
|
31
|
+
appData;
|
|
32
|
+
options;
|
|
33
|
+
file;
|
|
34
|
+
cache;
|
|
35
|
+
constructor(appData, options = {}) {
|
|
36
|
+
this.appData = appData;
|
|
37
|
+
this.options = options;
|
|
38
|
+
this.file = path.join(appData.root, "workspaces.json");
|
|
39
|
+
}
|
|
40
|
+
/** Auto-register the startup repoRoot (idempotent). */
|
|
41
|
+
async ensureStartupWorkspace(repoRoot) {
|
|
42
|
+
const record = await this.add(repoRoot);
|
|
43
|
+
if (record.ok)
|
|
44
|
+
return record.record;
|
|
45
|
+
// Startup root may already be visible (duplicate) or previously hidden —
|
|
46
|
+
// re-adding a hidden record restores it via add(); the only expected
|
|
47
|
+
// failure left is DUPLICATE (already visible), which returns the existing.
|
|
48
|
+
const existing = await this.findByRealpath(repoRoot);
|
|
49
|
+
if (existing)
|
|
50
|
+
return existing;
|
|
51
|
+
throw new Error(`workspace registry: startup root rejected: ${record.code}`);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Register (or restore) a workspace directory.
|
|
55
|
+
* Failures never mutate the persisted registry.
|
|
56
|
+
* `options.name` overrides the default directory-name label.
|
|
57
|
+
*/
|
|
58
|
+
async add(rawPath, options = {}) {
|
|
59
|
+
const trimmed = rawPath.trim();
|
|
60
|
+
if (!trimmed) {
|
|
61
|
+
return {
|
|
62
|
+
ok: false,
|
|
63
|
+
code: "INVALID_PATH",
|
|
64
|
+
message: "path is required",
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
let real;
|
|
68
|
+
try {
|
|
69
|
+
real = realpathSync(path.resolve(trimmed));
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
return {
|
|
73
|
+
ok: false,
|
|
74
|
+
code: "NOT_A_DIRECTORY",
|
|
75
|
+
message: `path does not exist: ${trimmed}`,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
let st;
|
|
79
|
+
try {
|
|
80
|
+
st = statSync(real);
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
return {
|
|
84
|
+
ok: false,
|
|
85
|
+
code: "UNREADABLE",
|
|
86
|
+
message: `path is not readable: ${trimmed}`,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
if (!st.isDirectory()) {
|
|
90
|
+
return {
|
|
91
|
+
ok: false,
|
|
92
|
+
code: "NOT_A_DIRECTORY",
|
|
93
|
+
message: `path is not a directory: ${trimmed}`,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
const normalized = normalizeWorktreeRealpath(real);
|
|
97
|
+
const existing = await this.findByRealpathNormalized(normalized);
|
|
98
|
+
if (existing && !existing.hidden) {
|
|
99
|
+
return {
|
|
100
|
+
ok: false,
|
|
101
|
+
code: "DUPLICATE",
|
|
102
|
+
message: `workspace already registered: ${existing.displayPath}`,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
const nowIso = (this.options.now ?? (() => new Date()))().toISOString();
|
|
106
|
+
if (existing) {
|
|
107
|
+
// Restore the soft-removed record; identity (workspaceId,
|
|
108
|
+
// repoFingerprint, createdAt, order) is preserved.
|
|
109
|
+
existing.hidden = undefined;
|
|
110
|
+
existing.lastAccessedAt = nowIso;
|
|
111
|
+
if (options.name && options.name.trim()) {
|
|
112
|
+
existing.name = options.name.trim();
|
|
113
|
+
}
|
|
114
|
+
const registry = await this.load();
|
|
115
|
+
await this.persist(registry);
|
|
116
|
+
return { ok: true, record: existing, registryUnchanged: false };
|
|
117
|
+
}
|
|
118
|
+
const record = {
|
|
119
|
+
workspaceId: workspaceIdForRealpath(normalized),
|
|
120
|
+
name: options.name?.trim() || path.basename(normalized) || normalized,
|
|
121
|
+
displayPath: normalized,
|
|
122
|
+
repoRoot: real,
|
|
123
|
+
repoFingerprint: repoFingerprintV1(real),
|
|
124
|
+
createdAt: nowIso,
|
|
125
|
+
lastAccessedAt: nowIso,
|
|
126
|
+
};
|
|
127
|
+
const registry = await this.load();
|
|
128
|
+
registry.workspaces.push(record);
|
|
129
|
+
await this.persist(registry);
|
|
130
|
+
return { ok: true, record, registryUnchanged: false };
|
|
131
|
+
}
|
|
132
|
+
/** Visible list in canonical insertion order. */
|
|
133
|
+
async listVisible() {
|
|
134
|
+
const registry = await this.load();
|
|
135
|
+
return registry.workspaces.filter((workspace) => {
|
|
136
|
+
if (workspace.hidden)
|
|
137
|
+
return false;
|
|
138
|
+
try {
|
|
139
|
+
return statSync(workspace.repoRoot).isDirectory();
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
// Keep historical registry data, but do not project stale test roots
|
|
143
|
+
// into the Workspace browser after their directories disappear.
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
async get(workspaceId) {
|
|
149
|
+
const registry = await this.load();
|
|
150
|
+
return registry.workspaces.find((w) => w.workspaceId === workspaceId);
|
|
151
|
+
}
|
|
152
|
+
/** Get a VISIBLE workspace by id (removed workspaces are not operable). */
|
|
153
|
+
async getVisible(workspaceId) {
|
|
154
|
+
const record = await this.get(workspaceId);
|
|
155
|
+
if (!record || record.hidden)
|
|
156
|
+
return undefined;
|
|
157
|
+
try {
|
|
158
|
+
return statSync(record.repoRoot).isDirectory() ? record : undefined;
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
return undefined;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
/** Soft-remove: hides the record; never touches directories or AppData. */
|
|
165
|
+
async remove(workspaceId) {
|
|
166
|
+
const registry = await this.load();
|
|
167
|
+
const record = registry.workspaces.find((w) => w.workspaceId === workspaceId);
|
|
168
|
+
if (!record || record.hidden)
|
|
169
|
+
return false;
|
|
170
|
+
record.hidden = true;
|
|
171
|
+
await this.persist(registry);
|
|
172
|
+
return true;
|
|
173
|
+
}
|
|
174
|
+
async findByRealpath(rawPath) {
|
|
175
|
+
let normalized;
|
|
176
|
+
try {
|
|
177
|
+
normalized = normalizeWorktreeRealpath(path.resolve(rawPath.trim()));
|
|
178
|
+
}
|
|
179
|
+
catch {
|
|
180
|
+
return undefined;
|
|
181
|
+
}
|
|
182
|
+
return this.findByRealpathNormalized(normalized);
|
|
183
|
+
}
|
|
184
|
+
async findByRealpathNormalized(normalized) {
|
|
185
|
+
const registry = await this.load();
|
|
186
|
+
return registry.workspaces.find((w) => w.displayPath === normalized);
|
|
187
|
+
}
|
|
188
|
+
async load() {
|
|
189
|
+
if (this.cache)
|
|
190
|
+
return this.cache;
|
|
191
|
+
const raw = await readJsonIfExists(this.file).catch(() => undefined);
|
|
192
|
+
const registry = {
|
|
193
|
+
schemaVersion: 1,
|
|
194
|
+
workspaces: raw && Array.isArray(raw.workspaces)
|
|
195
|
+
? raw.workspaces.filter((w) => Boolean(w) &&
|
|
196
|
+
typeof w === "object" &&
|
|
197
|
+
typeof w.workspaceId === "string" &&
|
|
198
|
+
typeof w.displayPath === "string" &&
|
|
199
|
+
typeof w.repoRoot === "string")
|
|
200
|
+
: [],
|
|
201
|
+
};
|
|
202
|
+
this.cache = registry;
|
|
203
|
+
return registry;
|
|
204
|
+
}
|
|
205
|
+
async persist(registry) {
|
|
206
|
+
// writeSecureJson is atomic; a reader mid-write sees the previous bytes.
|
|
207
|
+
await writeSecureJson(this.file, registry);
|
|
208
|
+
this.cache = registry;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
/** Deterministic-looking nonce reserved for tests requiring unique roots. */
|
|
212
|
+
export function mintWorkspaceNonce() {
|
|
213
|
+
return randomBytes(8).toString("hex");
|
|
214
|
+
}
|
|
@@ -292,9 +292,13 @@ function assertControllerIdentityUnchangedBeforeSpawn(identity) {
|
|
|
292
292
|
}
|
|
293
293
|
}
|
|
294
294
|
function controllerIdentityDriftError(message, cause) {
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
295
|
+
// Preserve the raw drift detail verbatim; the actionable suffix serves
|
|
296
|
+
// every consumer surface — operation errorMessage, SSE events, and agent/
|
|
297
|
+
// API callers that never reach the UI translation layer — with the exact
|
|
298
|
+
// recovery step: the running Console holds a stale controller fingerprint
|
|
299
|
+
// after rebuilds or commits touching fingerprint-scoped paths
|
|
300
|
+
// (package.json, bin/**, dist/**, skills/**).
|
|
301
|
+
return new Error(`controller identity changed before spawn: ${message}. Fix: restart the Console process so it re-resolves the current build, then retry the operation; avoid rebuilding or committing fingerprint-scoped files (package.json, bin/**, dist/**, skills/**) mid-session.`, { cause });
|
|
298
302
|
}
|
|
299
303
|
/**
|
|
300
304
|
* Detect whether loopAgentBin is a Node-like executable and baseArgs
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { computeFrontendSplitDigest } from "../../workflows/dag/frontend-split-orchestrator.js";
|
|
3
|
+
const idSchema = z.string().trim().min(1);
|
|
4
|
+
const digestSchema = z.string().regex(/^[a-f0-9]{64}$/);
|
|
5
|
+
const positiveIntegerSchema = z.number().int().positive();
|
|
6
|
+
const pathSchema = z.string().trim().min(1);
|
|
7
|
+
export const frontendSplitTaskMaterializationRequestSchema = z.object({
|
|
8
|
+
kind: z.enum(["child", "integration"]),
|
|
9
|
+
parentOperationId: idSchema,
|
|
10
|
+
manifestDigest: digestSchema,
|
|
11
|
+
generation: positiveIntegerSchema,
|
|
12
|
+
taskId: idSchema,
|
|
13
|
+
operationId: idSchema,
|
|
14
|
+
payloadDigest: digestSchema,
|
|
15
|
+
allowedPaths: z.array(pathSchema),
|
|
16
|
+
writeSet: z.array(pathSchema),
|
|
17
|
+
verifyRefs: z.array(idSchema),
|
|
18
|
+
}).strict();
|
|
19
|
+
export const frontendSplitTaskMaterializationResultSchema = z.object({
|
|
20
|
+
idempotencyKey: digestSchema,
|
|
21
|
+
requestDigest: digestSchema,
|
|
22
|
+
taskId: idSchema,
|
|
23
|
+
operationId: idSchema,
|
|
24
|
+
}).strict();
|
|
25
|
+
function identityPayload(request) {
|
|
26
|
+
return { kind: request.kind, parentOperationId: request.parentOperationId, manifestDigest: request.manifestDigest, generation: request.generation, taskId: request.taskId, operationId: request.operationId };
|
|
27
|
+
}
|
|
28
|
+
export class FrontendSplitTaskMaterializer {
|
|
29
|
+
backend;
|
|
30
|
+
completed = new Map();
|
|
31
|
+
inFlight = new Map();
|
|
32
|
+
constructor(backend) {
|
|
33
|
+
this.backend = backend;
|
|
34
|
+
}
|
|
35
|
+
async materialize(raw) {
|
|
36
|
+
const request = frontendSplitTaskMaterializationRequestSchema.parse(raw);
|
|
37
|
+
const idempotencyKey = computeFrontendSplitDigest(identityPayload(request));
|
|
38
|
+
const requestDigest = computeFrontendSplitDigest(request);
|
|
39
|
+
const existing = this.completed.get(idempotencyKey);
|
|
40
|
+
if (existing) {
|
|
41
|
+
if (existing.requestDigest !== requestDigest)
|
|
42
|
+
throw new Error("frontend split materialization payload drift");
|
|
43
|
+
return existing;
|
|
44
|
+
}
|
|
45
|
+
const pending = this.inFlight.get(idempotencyKey);
|
|
46
|
+
if (pending) {
|
|
47
|
+
const result = await pending;
|
|
48
|
+
if (result.requestDigest !== requestDigest)
|
|
49
|
+
throw new Error("frontend split materialization payload drift");
|
|
50
|
+
return result;
|
|
51
|
+
}
|
|
52
|
+
const promise = this.perform(request, idempotencyKey, requestDigest);
|
|
53
|
+
this.inFlight.set(idempotencyKey, promise);
|
|
54
|
+
try {
|
|
55
|
+
const result = await promise;
|
|
56
|
+
this.completed.set(idempotencyKey, result);
|
|
57
|
+
return result;
|
|
58
|
+
}
|
|
59
|
+
finally {
|
|
60
|
+
this.inFlight.delete(idempotencyKey);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
async perform(request, idempotencyKey, requestDigest) {
|
|
64
|
+
const task = await this.backend.ensureTask({ idempotencyKey, taskId: request.taskId, payloadDigest: requestDigest, request });
|
|
65
|
+
if (!task.complete || task.taskId !== request.taskId)
|
|
66
|
+
throw new Error("frontend split task materialization incomplete or drifted");
|
|
67
|
+
const operation = await this.backend.ensureOperation({ idempotencyKey, taskId: request.taskId, operationId: request.operationId, payloadDigest: requestDigest, request });
|
|
68
|
+
if (!operation.complete || operation.taskId !== request.taskId || operation.operationId !== request.operationId)
|
|
69
|
+
throw new Error("frontend split operation materialization incomplete or drifted");
|
|
70
|
+
return frontendSplitTaskMaterializationResultSchema.parse({ idempotencyKey, requestDigest, taskId: request.taskId, operationId: request.operationId });
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -129,11 +129,14 @@ export async function resolveRevisionDigestsForTaskSpec(input) {
|
|
|
129
129
|
};
|
|
130
130
|
}
|
|
131
131
|
/** Union TaskSpec allowed_paths with workflow-owned write roots. */
|
|
132
|
-
export function expandAllowedPathsForWorkflow(workflow, allowedPaths) {
|
|
132
|
+
export function expandAllowedPathsForWorkflow(workflow, allowedPaths, options) {
|
|
133
133
|
const extras = workflow === "backend-test"
|
|
134
134
|
? ["testcase/**", "docs/test-reports/**"]
|
|
135
135
|
: workflow === "frontend-test"
|
|
136
|
-
? [
|
|
136
|
+
? [
|
|
137
|
+
`${options?.frontendTestRoot ?? "testcase/frontend"}/**`,
|
|
138
|
+
"docs/test-reports/**",
|
|
139
|
+
]
|
|
137
140
|
: [];
|
|
138
141
|
return Array.from(new Set([...allowedPaths, ...extras]));
|
|
139
142
|
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { readdir } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { buildGlobalSnapshot } from "./read-model.js";
|
|
4
|
+
/**
|
|
5
|
+
* Read-only runtime guard shared by Console maintenance and init-surface
|
|
6
|
+
* writes. Worker activity remains owned by the Observe projection so both
|
|
7
|
+
* callers use one canonical liveness interpretation.
|
|
8
|
+
*/
|
|
9
|
+
export async function readInitRuntimeActivity(repoRoot) {
|
|
10
|
+
const resolvedRoot = path.resolve(repoRoot);
|
|
11
|
+
const activeDagRoot = path.join(resolvedRoot, ".harness", "dag-runs", "active");
|
|
12
|
+
const entries = await readdir(activeDagRoot, { withFileTypes: true }).catch(() => []);
|
|
13
|
+
const activeDagRunIds = entries
|
|
14
|
+
.filter((entry) => entry.isDirectory() && entry.name !== ".gitkeep")
|
|
15
|
+
.map((entry) => entry.name)
|
|
16
|
+
.sort();
|
|
17
|
+
const snapshot = await buildGlobalSnapshot({ repoRoot: resolvedRoot });
|
|
18
|
+
return {
|
|
19
|
+
activeDagRunIds,
|
|
20
|
+
activeWorkerBatches: snapshot.health.worker.activeBatches,
|
|
21
|
+
activeWorkerTasks: snapshot.health.worker.activeTasks,
|
|
22
|
+
...(snapshot.projectionError
|
|
23
|
+
? { workerProjectionError: snapshot.projectionError.message }
|
|
24
|
+
: {}),
|
|
25
|
+
};
|
|
26
|
+
}
|
|
@@ -1483,6 +1483,9 @@ function mergeDagRun(existing, incoming) {
|
|
|
1483
1483
|
computeDurationMs(startedAt, finishedAt);
|
|
1484
1484
|
return {
|
|
1485
1485
|
dagRunId: incoming.dagRunId,
|
|
1486
|
+
...((incoming.taskId ?? existing.taskId)
|
|
1487
|
+
? { taskId: incoming.taskId ?? existing.taskId }
|
|
1488
|
+
: {}),
|
|
1486
1489
|
status: incoming.status ?? existing.status,
|
|
1487
1490
|
lifecycle: incoming.lifecycle ?? existing.lifecycle,
|
|
1488
1491
|
executionMode: incoming.executionMode ?? existing.executionMode,
|
|
@@ -1914,8 +1917,10 @@ async function parseDagStateFile(statePath, now) {
|
|
|
1914
1917
|
}
|
|
1915
1918
|
: undefined;
|
|
1916
1919
|
const failureCategory = readString(parsed, "failureCategory");
|
|
1920
|
+
const boundTaskId = await readRunSpecTaskBinding(runSpecPath);
|
|
1917
1921
|
return {
|
|
1918
1922
|
dagRunId,
|
|
1923
|
+
...(boundTaskId ? { taskId: boundTaskId } : {}),
|
|
1919
1924
|
status,
|
|
1920
1925
|
...(lifecycle ? { lifecycle } : {}),
|
|
1921
1926
|
...(executionMode ? { executionMode } : {}),
|
|
@@ -2435,6 +2440,23 @@ async function safeReadJson(filePath) {
|
|
|
2435
2440
|
return undefined;
|
|
2436
2441
|
return parsed;
|
|
2437
2442
|
}
|
|
2443
|
+
/** Task binding for a DAG run from run.json — same precedence as
|
|
2444
|
+
* console/resolve-dag-run-for-task (taskContractBinding → sourceBinding →
|
|
2445
|
+
* Hybrid DAG title). */
|
|
2446
|
+
async function readRunSpecTaskBinding(runSpecPath) {
|
|
2447
|
+
const parsed = await safeReadJson(runSpecPath);
|
|
2448
|
+
if (!parsed)
|
|
2449
|
+
return undefined;
|
|
2450
|
+
const binding = parsed.taskContractBinding;
|
|
2451
|
+
const source = parsed.sourceBinding;
|
|
2452
|
+
const fromBinding = (typeof binding?.taskId === "string" && binding.taskId.trim()) ||
|
|
2453
|
+
(typeof source?.taskId === "string" && source.taskId.trim());
|
|
2454
|
+
if (fromBinding)
|
|
2455
|
+
return fromBinding;
|
|
2456
|
+
const title = typeof parsed.title === "string" ? parsed.title.trim() : "";
|
|
2457
|
+
const hybrid = /^Hybrid DAG:\s*(.+)$/i.exec(title);
|
|
2458
|
+
return hybrid?.[1]?.trim() || undefined;
|
|
2459
|
+
}
|
|
2438
2460
|
function safeParseJson(raw) {
|
|
2439
2461
|
try {
|
|
2440
2462
|
const parsed = JSON.parse(raw);
|
|
@@ -2,7 +2,8 @@ import { readFile } from "node:fs/promises";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { redactSecrets, truncateUtf8Preview } from "../../shared/preview.js";
|
|
4
4
|
import { resolveDagNodeSkills } from "../../workflows/dag/skills.js";
|
|
5
|
-
import { resolveDagNodePromptRedacted, resolveDagRunJson, } from "./dag-run-artifacts.js";
|
|
5
|
+
import { resolveDagNodePromptRedacted, resolveDagRunArtifact, resolveDagRunJson, } from "./dag-run-artifacts.js";
|
|
6
|
+
import { namedShellHandlers } from "./shell-handler-keys.js";
|
|
6
7
|
export const NODE_INPUT_SCHEMA_VERSION = 1;
|
|
7
8
|
export const NODE_INPUT_PREVIEW_MAX_BYTES = 64 * 1024;
|
|
8
9
|
const SHELL_GATE_KEYS = [
|
|
@@ -30,9 +31,11 @@ function unavailableBody(dagRunId, nodeId, reason, warnings = []) {
|
|
|
30
31
|
summary: null,
|
|
31
32
|
dependency: null,
|
|
32
33
|
prompt: null,
|
|
34
|
+
promptCandidates: [],
|
|
33
35
|
executorInput: null,
|
|
34
36
|
boundaries: null,
|
|
35
37
|
promptFingerprint: null,
|
|
38
|
+
piExtensionRunFacts: null,
|
|
36
39
|
warnings,
|
|
37
40
|
};
|
|
38
41
|
}
|
|
@@ -137,7 +140,7 @@ function resolveModelHint(spec, task) {
|
|
|
137
140
|
}
|
|
138
141
|
return null;
|
|
139
142
|
}
|
|
140
|
-
function projectTask(dagRunId, nodeId, lifecycle, spec, task, fingerprint, warnings) {
|
|
143
|
+
function projectTask(dagRunId, nodeId, lifecycle, spec, task, fingerprint, piExtensionRunFacts, promptCandidates, warnings) {
|
|
141
144
|
const defaults = spec.defaults && typeof spec.defaults === "object"
|
|
142
145
|
? spec.defaults
|
|
143
146
|
: undefined;
|
|
@@ -216,6 +219,7 @@ function projectTask(dagRunId, nodeId, lifecycle, spec, task, fingerprint, warni
|
|
|
216
219
|
: null,
|
|
217
220
|
failFast: typeof shellRaw.failFast === "boolean" ? shellRaw.failFast : null,
|
|
218
221
|
gates: shellGateNames(shellRaw),
|
|
222
|
+
handlers: namedShellHandlers(shellRaw),
|
|
219
223
|
}
|
|
220
224
|
: null;
|
|
221
225
|
const staticPreview = staticRaw && typeof staticRaw.resultMarkdown === "string"
|
|
@@ -264,10 +268,12 @@ function projectTask(dagRunId, nodeId, lifecycle, spec, task, fingerprint, warni
|
|
|
264
268
|
maxBytes: promptBody.maxBytes,
|
|
265
269
|
source: promptSource,
|
|
266
270
|
},
|
|
271
|
+
promptCandidates,
|
|
267
272
|
executorInput: {
|
|
268
273
|
kind,
|
|
269
274
|
skills,
|
|
270
275
|
modelHint: resolveModelHint(spec, task),
|
|
276
|
+
piExtensions: redactStringList(task.piExtensions),
|
|
271
277
|
shell,
|
|
272
278
|
static: staticBlock,
|
|
273
279
|
},
|
|
@@ -284,9 +290,155 @@ function projectTask(dagRunId, nodeId, lifecycle, spec, task, fingerprint, warni
|
|
|
284
290
|
: null,
|
|
285
291
|
},
|
|
286
292
|
promptFingerprint: fingerprint,
|
|
293
|
+
piExtensionRunFacts,
|
|
287
294
|
warnings,
|
|
288
295
|
};
|
|
289
296
|
}
|
|
297
|
+
async function loadFailedPromptCandidates(repoRoot, dagRunId, nodeId, warnings) {
|
|
298
|
+
const stateResolved = resolveDagRunArtifact(repoRoot, dagRunId, ["state.json"]);
|
|
299
|
+
if (!stateResolved.ok)
|
|
300
|
+
return [];
|
|
301
|
+
let state;
|
|
302
|
+
try {
|
|
303
|
+
state = JSON.parse(await readFile(stateResolved.result.absolutePath, "utf-8"));
|
|
304
|
+
}
|
|
305
|
+
catch {
|
|
306
|
+
warnings.push("state.json could not be read for failed prompt candidates.");
|
|
307
|
+
return [];
|
|
308
|
+
}
|
|
309
|
+
const nodes = state.nodes && typeof state.nodes === "object" && !Array.isArray(state.nodes)
|
|
310
|
+
? state.nodes
|
|
311
|
+
: null;
|
|
312
|
+
const node = nodes?.[nodeId] &&
|
|
313
|
+
typeof nodes[nodeId] === "object" &&
|
|
314
|
+
!Array.isArray(nodes[nodeId])
|
|
315
|
+
? nodes[nodeId]
|
|
316
|
+
: null;
|
|
317
|
+
if (!node)
|
|
318
|
+
return [];
|
|
319
|
+
const failedAttempts = new Set();
|
|
320
|
+
if (Array.isArray(node.attempts)) {
|
|
321
|
+
for (const entry of node.attempts) {
|
|
322
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry))
|
|
323
|
+
continue;
|
|
324
|
+
const record = entry;
|
|
325
|
+
if (record.ok === false &&
|
|
326
|
+
typeof record.attempt === "number" &&
|
|
327
|
+
Number.isSafeInteger(record.attempt) &&
|
|
328
|
+
record.attempt > 0) {
|
|
329
|
+
failedAttempts.add(record.attempt);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
if (node.status === "ERROR" &&
|
|
334
|
+
typeof node.currentAttempt === "number" &&
|
|
335
|
+
Number.isSafeInteger(node.currentAttempt) &&
|
|
336
|
+
node.currentAttempt > 0) {
|
|
337
|
+
failedAttempts.add(node.currentAttempt);
|
|
338
|
+
}
|
|
339
|
+
const candidates = [];
|
|
340
|
+
for (const attempt of [...failedAttempts].sort((a, b) => a - b)) {
|
|
341
|
+
const fileName = `prompt-candidate-attempt-${attempt}.json`;
|
|
342
|
+
const artifact = path.posix.join(nodeId, fileName);
|
|
343
|
+
const resolved = resolveDagRunArtifact(repoRoot, dagRunId, [nodeId, fileName]);
|
|
344
|
+
if (!resolved.ok) {
|
|
345
|
+
warnings.push(`Failed prompt candidate missing for attempt ${attempt}.`);
|
|
346
|
+
continue;
|
|
347
|
+
}
|
|
348
|
+
try {
|
|
349
|
+
const raw = JSON.parse(await readFile(resolved.result.absolutePath, "utf-8"));
|
|
350
|
+
if (raw.schemaVersion !== 1 ||
|
|
351
|
+
raw.attempt !== attempt ||
|
|
352
|
+
typeof raw.failureCategory !== "string" ||
|
|
353
|
+
typeof raw.prompt !== "string" ||
|
|
354
|
+
typeof raw.truncated !== "boolean" ||
|
|
355
|
+
typeof raw.maxBytes !== "number" ||
|
|
356
|
+
!Number.isSafeInteger(raw.maxBytes) ||
|
|
357
|
+
raw.maxBytes < 1 ||
|
|
358
|
+
raw.maxBytes > NODE_INPUT_PREVIEW_MAX_BYTES ||
|
|
359
|
+
typeof raw.sha256 !== "string" ||
|
|
360
|
+
!/^([a-f0-9]{64})$/.test(raw.sha256)) {
|
|
361
|
+
throw new Error("invalid candidate schema");
|
|
362
|
+
}
|
|
363
|
+
const prompt = truncatedField(raw.prompt, raw.maxBytes);
|
|
364
|
+
candidates.push({
|
|
365
|
+
attempt,
|
|
366
|
+
failureCategory: redactSecrets(raw.failureCategory),
|
|
367
|
+
prompt: prompt.text,
|
|
368
|
+
truncated: raw.truncated || prompt.truncated,
|
|
369
|
+
maxBytes: raw.maxBytes,
|
|
370
|
+
sha256Prefix: raw.sha256.slice(0, 12),
|
|
371
|
+
artifact,
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
catch {
|
|
375
|
+
warnings.push(`Failed prompt candidate is invalid for attempt ${attempt}.`);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
return candidates;
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* Load run-time Pi extension facts from <node>/pi-extensions.json (plan
|
|
382
|
+
* 2026-08-22 R2). Returns null when the node declares no piExtensions
|
|
383
|
+
* (no expected artifact). Never throws: unreadable/corrupt artifacts degrade
|
|
384
|
+
* to parseError stubs so the node-input endpoint stays 200.
|
|
385
|
+
*/
|
|
386
|
+
async function loadPiExtensionRunFacts(repoRoot, dagRunId, nodeId, declared, warnings) {
|
|
387
|
+
if (declared.length === 0)
|
|
388
|
+
return null;
|
|
389
|
+
const artifact = path.posix.join(nodeId, "pi-extensions.json");
|
|
390
|
+
const resolved = resolveDagRunArtifact(repoRoot, dagRunId, [
|
|
391
|
+
nodeId,
|
|
392
|
+
"pi-extensions.json",
|
|
393
|
+
]);
|
|
394
|
+
if (!resolved.ok) {
|
|
395
|
+
if (resolved.reason === "ambiguous") {
|
|
396
|
+
warnings.push("Ambiguous pi-extensions.json lifecycle copies.");
|
|
397
|
+
}
|
|
398
|
+
return {
|
|
399
|
+
artifact,
|
|
400
|
+
exists: false,
|
|
401
|
+
requested: [...declared],
|
|
402
|
+
resolved: [],
|
|
403
|
+
missing: [],
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
const empty = {
|
|
407
|
+
artifact,
|
|
408
|
+
exists: false,
|
|
409
|
+
requested: [...declared],
|
|
410
|
+
resolved: [],
|
|
411
|
+
missing: [],
|
|
412
|
+
};
|
|
413
|
+
try {
|
|
414
|
+
const raw = JSON.parse(await readFile(resolved.result.absolutePath, "utf-8"));
|
|
415
|
+
const resolvedList = Array.isArray(raw.resolved) ? raw.resolved : [];
|
|
416
|
+
const missingList = Array.isArray(raw.missing) ? raw.missing : [];
|
|
417
|
+
const projected = resolvedList
|
|
418
|
+
.filter((entry) => Boolean(entry) && typeof entry === "object" && !Array.isArray(entry))
|
|
419
|
+
.map((entry) => ({
|
|
420
|
+
id: redactSecrets(String(entry.id ?? "")),
|
|
421
|
+
packageName: redactSecrets(String(entry.packageName ?? "")),
|
|
422
|
+
version: redactSecrets(String(entry.version ?? "")),
|
|
423
|
+
}));
|
|
424
|
+
const missingProjected = missingList
|
|
425
|
+
.filter((entry) => Boolean(entry) && typeof entry === "object" && !Array.isArray(entry))
|
|
426
|
+
.map((entry) => ({
|
|
427
|
+
id: redactSecrets(String(entry.id ?? "")),
|
|
428
|
+
reason: redactSecrets(String(entry.reason ?? "")),
|
|
429
|
+
}));
|
|
430
|
+
return {
|
|
431
|
+
artifact,
|
|
432
|
+
exists: true,
|
|
433
|
+
requested: redactStringList(raw.requested ?? declared),
|
|
434
|
+
resolved: projected,
|
|
435
|
+
missing: missingProjected,
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
catch {
|
|
439
|
+
return { ...empty, parseError: "read-failed" };
|
|
440
|
+
}
|
|
441
|
+
}
|
|
290
442
|
async function loadPromptFingerprint(repoRoot, dagRunId, nodeId, warnings) {
|
|
291
443
|
const resolved = resolveDagNodePromptRedacted(repoRoot, dagRunId, nodeId);
|
|
292
444
|
if (!resolved.ok) {
|
|
@@ -415,8 +567,13 @@ export async function buildDagNodeInput(repoRoot, dagRunId, nodeId) {
|
|
|
415
567
|
}
|
|
416
568
|
const warnings = [];
|
|
417
569
|
const fingerprint = await loadPromptFingerprint(repoRoot, dagRunId, nodeId, warnings);
|
|
570
|
+
const declaredPiExtensions = Array.isArray(task.piExtensions)
|
|
571
|
+
? task.piExtensions.filter((item) => typeof item === "string")
|
|
572
|
+
: [];
|
|
573
|
+
const piExtensionRunFacts = await loadPiExtensionRunFacts(repoRoot, dagRunId, nodeId, declaredPiExtensions, warnings);
|
|
574
|
+
const promptCandidates = await loadFailedPromptCandidates(repoRoot, dagRunId, nodeId, warnings);
|
|
418
575
|
// Reject unknown nested leakage by projecting only through allowlist helpers.
|
|
419
|
-
const body = projectTask(dagRunId, nodeId, runResolved.result.lifecycle, spec, task, fingerprint, warnings);
|
|
576
|
+
const body = projectTask(dagRunId, nodeId, runResolved.result.lifecycle, spec, task, fingerprint, piExtensionRunFacts, promptCandidates, warnings);
|
|
420
577
|
// Defense-in-depth: ensure raw task never sneaks in.
|
|
421
578
|
if ("task" in body) {
|
|
422
579
|
return {
|