@tea-agent/loop-agent 0.44.0-next.1 → 0.44.0-next.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/CHANGELOG.md +379 -416
- package/dist/application/evaluation/budget.js +21 -0
- package/dist/application/evaluation/corpus-hash.js +10 -15
- package/dist/application/evaluation/corpus.js +2 -1
- package/dist/application/evaluation/frontend-browser-acceptance.js +77 -0
- package/dist/application/evaluation/frontend-corpus-browser-acceptance.js +175 -0
- package/dist/application/evaluation/frontend-gateway-evidence.js +226 -0
- package/dist/application/evaluation/frontend-pair-registration.js +143 -0
- package/dist/application/evaluation/frontend-paired-summary.js +150 -0
- package/dist/application/evaluation/frontend-run-observation.js +144 -0
- package/dist/application/evaluation/frontend-shared-evidence.js +105 -0
- package/dist/application/evaluation/types.js +45 -12
- package/dist/application/task-lifecycle/observe.js +26 -1
- package/dist/build-stamp.json +3 -3
- package/dist/cli/command-definitions.js +11 -0
- package/dist/cli/program.js +4 -0
- package/dist/commands/task-advance.js +3 -5
- package/dist/commands/task-source-prepare.js +13 -1
- package/dist/executors/dag-pi/guards.js +4 -0
- package/dist/executors/dag-pi/model-config.js +30 -0
- package/dist/executors/dag-pi/plan/fact-adoption.js +55 -0
- package/dist/{workflows/dag/frontend-plan-completeness.js → executors/dag-pi/plan/facts.js} +22 -28
- package/dist/executors/dag-pi/plan/finalize-tools.js +259 -0
- package/dist/executors/dag-pi/plan/ledger-contract.js +55 -0
- package/dist/executors/dag-pi/plan/record-tools.js +861 -0
- package/dist/executors/dag-pi/plan/schema.js +129 -0
- package/dist/executors/dag-pi/plan/session.js +1 -0
- package/dist/executors/dag-pi/plan/tool-helpers.js +16 -0
- package/dist/executors/dag-pi/sessions/index.js +6 -0
- package/dist/executors/dag-pi/sessions/plan-batches.js +280 -0
- package/dist/executors/dag-pi/sessions/plan-prompts.js +367 -0
- package/dist/executors/dag-pi/sessions/scout-parallel.js +197 -0
- package/dist/executors/dag-pi/sessions/segmented-plan.js +1184 -0
- package/dist/executors/dag-pi/sessions/writer-evidence.js +60 -0
- package/dist/executors/dag-pi/tools/contract-tools.js +515 -0
- package/dist/executors/dag-pi/tools/design-terminal-tools.js +159 -0
- package/dist/executors/dag-pi/tools/plan-decision-tools.js +529 -0
- package/dist/executors/dag-pi/tools/review-terminal-tools.js +159 -0
- package/dist/executors/dag-pi/tools/scout-evidence-tools.js +270 -0
- package/dist/executors/dag-pi-executor.js +733 -5782
- package/dist/executors/pi-executor.js +6 -1
- package/dist/executors/pi-sdk-executor.js +112 -19
- package/dist/executors/shell-executor.js +88 -12
- package/dist/executors/shell-presets.js +1 -1
- package/dist/executors/shell-write-guard.js +26 -3
- package/dist/infrastructure/console/operation-store.js +48 -14
- package/dist/infrastructure/evaluation/corpus-store.js +37 -12
- package/dist/shared/file-identity.js +23 -0
- package/dist/shared/git-workspace-status.js +482 -0
- package/dist/shared/operator/capabilities.js +10 -5
- package/dist/shared/path-safety.js +2 -2
- package/dist/shared/wizard-local-telemetry.js +51 -0
- package/dist/task/config-types.js +22 -8
- package/dist/task/contract/project.js +13 -0
- package/dist/task/contract/schema.js +2 -7
- package/dist/task/frontend-preflight.js +1 -1
- package/dist/task/source-prepare/parse-intent.js +124 -5
- package/dist/task/source-prepare/prepare.js +6 -1
- package/dist/task/source-prepare/semantic-intake.js +18 -3
- package/dist/task/source-prepare/source-execution.js +65 -0
- package/dist/task/source-prepare/source-fidelity-pi.js +22 -9
- package/dist/task/source-prepare/source-provider-budget.js +290 -0
- package/dist/worker/console/chat/assistant-content.js +5 -34
- package/dist/worker/console/chat/chat-event-store.js +62 -10
- package/dist/worker/console/chat/context-insights.js +723 -0
- package/dist/worker/console/chat/conversation-nodes.js +383 -0
- package/dist/worker/console/chat/db-connections.js +854 -0
- package/dist/worker/console/chat/pi-runtime/custom-tools/operator-tools.js +1 -1
- package/dist/worker/console/chat/pi-runtime/custom-tools/sql-tools.js +655 -0
- package/dist/worker/console/chat/pi-runtime.js +445 -36
- package/dist/worker/console/chat/provider-error.js +0 -12
- package/dist/worker/console/chat/repo-browser.js +52 -4
- package/dist/worker/console/chat/routes.js +577 -72
- package/dist/worker/console/chat/sdd-data-alignment.js +8 -31
- package/dist/worker/console/chat/session-store.js +147 -19
- package/dist/worker/console/chat/shortcuts.js +7 -0
- package/dist/worker/console/chat/timeline-message-order.js +22 -0
- package/dist/worker/console/chat/tools.js +34 -0
- package/dist/worker/console/chat/turn-process.js +237 -66
- package/dist/worker/console/chat/user-turn-outline.js +19 -0
- package/dist/worker/console/console-update-runtime.js +2 -2
- package/dist/worker/console/dag-confirmation.js +3 -0
- package/dist/worker/console/dag-execution-receipt.js +3 -0
- package/dist/worker/console/operator-actions.js +32 -3
- package/dist/worker/console/operator-user-error.js +28 -1
- package/dist/worker/console/pi-readiness.js +4 -4
- package/dist/worker/console/prd-intake-bridge.js +5 -12
- package/dist/worker/console/recovery-cta.js +14 -14
- package/dist/worker/console/server.js +5 -1
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-8-j6y-sd.js → abnfDiagram-N423BO3Z-r1NAPU_5.js} +1 -1
- package/dist/worker/console/static/assets/{arc-eoQiMvuk.js → arc-dT5T4sVL.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-C4A3uMcI.js → architectureDiagram-T3A2C74G-CP_cOq46.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-D4zD2F-Q.js → blockDiagram-VBNYF7ZC-sToEZxEf.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-j1RkJziL.js → c4Diagram-5PPSVZJV-DSxwwi2m.js} +1 -1
- package/dist/worker/console/static/assets/channel-CyBY_yGk.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-YBHmhik1.js → chunk-2GRJ4B5K-B9fh0IkF.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-COfWyo9P.js → chunk-2Q5K7J3B-CAUNl-A6.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-I99OUkHY.js → chunk-5RXB4S5H-BomeqbNX.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-XKxoJNJ4.js → chunk-5VM5RSS4-BNAuTOLE.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-DLT_cYx1.js → chunk-6Q2QTUOP-DfzjRD5y.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-CxcKZ9mV.js → chunk-GF5L2VYU-C3uE-yc2.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-V1EqXdjY.js → chunk-JWPE2WC7-C3sQMMYr.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-DebTtdFp.js → chunk-KBJHAD2P-D_AtEYQL.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-B-ivNRDf.js → chunk-RYQCIY6F-DW4TwhKv.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-DC_Ds11b.js → chunk-XXDRQBXY-CTQDmJcp.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-D5lN4E_E.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-D5lN4E_E.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-sWEqKwIb.js → cose-bilkent-JH36ORCC-iMqPuBsL.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-BXa_dcu4.js → cynefin-VYW2F7L2-DktIuDWs.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-C-Mle84F.js → cynefinDiagram-MW4NZA55-BgVnjyEs.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-CXPDBITe.js → dagre-VZM6K2ZE-CR25E4bu.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-CC-WJQfa.js → diagram-7IWD3JNH-CPM2h5HY.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-CDAV5Vs4.js → diagram-B4RE2ZJO-DqF0x2g1.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-CmFiAcNz.js → diagram-LBJQPF4R-G8BYp2eb.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-DPBZHuyn.js → diagram-Q27KOJAE-BbC4kT_D.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-jUlm_Ds3.js → diagram-UB23O5K3-CKZwZ-G5.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-DWhQ3mfY.js → ebnfDiagram-BXEA7PRR-kJDmheia.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-Tr2gMqet.js → erDiagram-JOGREHBK-DFTVHyxB.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-DJjVQHPA.js → flowDiagram-UKHOOZJN-BqdN5_os.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-D74dQ4u0.js → ganttDiagram-PKOTCBZU-SYDjVakh.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-DwW0tZ0X.js → gitGraphDiagram-DS77QQ5N-Bf8bNpGA.js} +1 -1
- package/dist/worker/console/static/assets/index-B28Onmfy.css +1 -0
- package/dist/worker/console/static/assets/index-CTMZsDmh.js +486 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-ILCbxJyb.js → infoDiagram-6WML65LV-DIeAmJ45.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-DeuWBQ89.js → ishikawaDiagram-WSZJBQD7-oikqUAlo.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-CF5ih8Fk.js → journeyDiagram-NVQOT4AX-mKEF_xYT.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-C7yOSuRO.js → kanban-definition-27J2QSJJ-BdWJHPnN.js} +1 -1
- package/dist/worker/console/static/assets/{linear-BDZ9riWi.js → linear-DGRlieMH.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-7pKqYtpZ.js → mermaid.core-4P9yPC9c.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-LeJDybSU.js → mindmap-definition-FAOFIHXS-5PTSkos6.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-BJvT3pMD.js → pegDiagram-VL7TDLO6-DONIFpG5.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-_rGqpMan.js → pieDiagram-7S7Q4E2Y-D_Z4S70M.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-DPcSv5aA.js → quadrantDiagram-CIZ2JOQS-CAsjh8iY.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-Drxx4hkJ.js → railroadDiagram-AXF67PYL-Cx4Dd6ac.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-BCTUdU4z.js → requirementDiagram-LRYGKXZP-cQb7mwyH.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-B6wzbZmB.js → sankeyDiagram-W5VNT64P-Cd-DhUdf.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-BGt8d3QQ.js → sequenceDiagram-SI44F4Z6-AlKYRgIk.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-7nlhJKo7.js → sizeCapture-X5ZJPWSS-CUy_ET4e.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-Cv2stqMA.js → stateDiagram-OKZ733FA-CTuVe6Sa.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-BsWhs8O7.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-qDAo4Yc1.js → swimlanes-SLNWSIFB-ChFLJb-v.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-CJcioYGH.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-CKbDNKTr.js → timeline-definition-Z64GVDOM-DZZeiAPr.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-DI-9EHic.js → vennDiagram-T6HMQDX7-t4O91cZU.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-BrzzRDpX.js → wardleyDiagram-T6FBY63Y-CV5qGHZu.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-BclH5hGh.js → xychartDiagram-ELKLHX3M-DyXbytUI.js} +1 -1
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/app/console-types.js +9 -1
- package/dist/worker/console/static-src/operator-chat/animation-frame-batcher.js +38 -0
- package/dist/worker/console/static-src/operator-chat/chat-link.js +20 -0
- package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +186 -5
- package/dist/worker/console/static-src/operator-chat/chat-stream-continuity.js +101 -0
- package/dist/worker/console/static-src/operator-chat/compaction-message.js +18 -3
- package/dist/worker/console/static-src/operator-chat/context-insights-store.js +64 -0
- package/dist/worker/console/static-src/operator-chat/details-rail-surfaces.js +98 -0
- package/dist/worker/console/static-src/operator-chat/file-palette-nav.js +81 -0
- package/dist/worker/console/static-src/operator-chat/pending-user-message.js +17 -3
- package/dist/worker/console/static-src/operator-chat/process-label.js +27 -5
- package/dist/worker/console/static-src/operator-chat/tools-catalog.js +16 -7
- package/dist/worker/console/static-src/operator-chat/transcript-mode.js +21 -0
- package/dist/worker/console/static-src/operator-chat/turn-group-equality.js +4 -1
- package/dist/worker/console/static-src/operator-chat/turn-process-disclosure.js +13 -5
- package/dist/worker/console/static-src/operator-chat/turn-state-projection.js +166 -0
- package/dist/worker/console/static-src/operator-chat/turn-stream-controller.js +56 -2
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +211 -40
- package/dist/worker/console/static-src/operator-chat/useChatStream.js +124 -27
- package/dist/worker/console/static-src/operator-chat/useChatThread.js +88 -20
- package/dist/worker/console/static-src/operator-chat/useComposer.js +80 -6
- package/dist/worker/console/static-src/operator-chat/useRepoBrowser.js +13 -3
- package/dist/worker/console/static-src/operator-chat/useRuntimeControls.js +36 -17
- package/dist/worker/console/static-src/operator-chat/useRuntimeSnapshot.js +18 -0
- package/dist/worker/console/static-src/operator-chat/useWorkspaceBrowserGate.js +4 -0
- package/dist/worker/console/static-src/shell/console-update-reload.js +1 -1
- package/dist/worker/console/workspace-context.js +2 -0
- package/dist/worker/console/workspace-initialization.js +3 -3
- package/dist/worker/observability/read-model.js +23 -2
- package/dist/worker/observe/node-context-insights.js +479 -0
- package/dist/worker/observe/static/console-theme.css +174 -0
- package/dist/worker/observe/static/console-theme.js +36 -0
- package/dist/worker/observe/static/operator-chrome.css +85 -0
- package/dist/worker/observe/static/operator-chrome.js +51 -0
- package/dist/worker/scheduler/scheduled-goal-dispatch.js +3 -2
- package/dist/worker/scheduler/scheduled-goal-evidence.js +66 -3
- package/dist/worker/scheduler/scheduled-goal-store.js +10 -0
- package/dist/workflows/dag/budget-enforcement.js +31 -2
- package/dist/workflows/dag/checkpoint.js +686 -0
- package/dist/workflows/dag/frontend-closeout.js +3 -1
- package/dist/workflows/dag/frontend-contract-facts.js +11 -8
- package/dist/workflows/dag/frontend-design-policy.js +6 -6
- package/dist/workflows/dag/frontend-durable-tools.js +90 -70
- package/dist/workflows/dag/frontend-implementation-contract.js +146 -57
- package/dist/workflows/dag/frontend-plan-canary.js +66 -16
- package/dist/workflows/dag/frontend-plan-decision-contract.js +13 -21
- package/dist/workflows/dag/frontend-plan-progress.js +92 -0
- package/dist/workflows/dag/frontend-recovery-plan.js +10 -5
- package/dist/workflows/dag/frontend-recovery-run.js +51 -46
- package/dist/workflows/dag/frontend-repair-assertions.js +36 -0
- package/dist/workflows/dag/frontend-review-context.js +29 -1
- package/dist/workflows/dag/frontend-review-scopes.js +92 -2
- package/dist/workflows/dag/frontend-risk.js +8 -3
- package/dist/workflows/dag/frontend-root-observation.js +261 -0
- package/dist/workflows/dag/frontend-session-budget.js +52 -39
- package/dist/workflows/dag/frontend-session-context.js +10 -0
- package/dist/workflows/dag/frontend-test-execution-evidence.js +206 -48
- package/dist/workflows/dag/frontend-typed-event-store.js +11 -1
- package/dist/workflows/dag/frontend-verification-trace.js +6 -0
- package/dist/workflows/dag/frontend-writer-admission.js +3 -1
- package/dist/workflows/dag/hybrid/sources.js +1812 -0
- package/dist/workflows/dag/hybrid/templates/backend-test.js +1807 -0
- package/dist/workflows/dag/hybrid/templates/frontend-test.js +702 -0
- package/dist/workflows/dag/hybrid/templates/frontend.js +1529 -0
- package/dist/workflows/dag/hybrid/templates/index.js +8 -0
- package/dist/workflows/dag/hybrid/templates/kg-bootstrap.js +449 -0
- package/dist/workflows/dag/hybrid/templates/knowledge-sync.js +495 -0
- package/dist/workflows/dag/hybrid/templates/shared.js +101 -0
- package/dist/workflows/dag/hybrid/templates/standard.js +265 -0
- package/dist/workflows/dag/hybrid/types.js +32 -0
- package/dist/workflows/dag/init-hybrid.js +57 -7106
- package/dist/workflows/dag/node-execution.js +11 -11
- package/dist/workflows/dag/output-protocol.js +1 -0
- package/dist/workflows/dag/rerun-feedback.js +35 -4
- package/dist/workflows/dag/rerun-task.js +62 -66
- package/dist/workflows/dag/runner.js +38 -1159
- package/dist/workflows/dag/terminal-status.js +502 -0
- package/dist/workflows/dag/types.js +15 -8
- package/dist/workflows/loop/benchmark.js +11 -8
- package/docs/README.md +1 -1
- package/docs/architecture/dag-execution.md +1 -1
- package/docs/architecture/runtime-boundaries.md +1 -1
- package/docs/governance/harness-methodology-verification.md +11 -0
- package/docs/templates/backend-test-dag.json +5 -4
- package/docs/templates/frontend-implementation-contract.schema.json +31 -11
- package/package.json +6 -4
- package/skills/frontend-design-review/SKILL.md +8 -11
- package/skills/frontend-design-review/references/review-checklist.md +3 -4
- package/skills/frontend-review/SKILL.md +5 -1
- package/skills/loop-agent/references/command-reference.md +8 -0
- package/skills/loop-agent/references/hybrid-dag.md +1 -1
- package/dist/worker/console/static/assets/channel-ChE7y-cx.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-C01TCf2X.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-C01TCf2X.js +0 -1
- package/dist/worker/console/static/assets/index-BWkIfcrK.css +0 -1
- package/dist/worker/console/static/assets/index-Cdkvw_H6.js +0 -469
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DC7V5vcq.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-Bwy4QUTO.js +0 -8
|
@@ -198,6 +198,10 @@ export async function listExplicitlyAssociatedRuns(repoRoot, taskId) {
|
|
|
198
198
|
const recorded = new Map((record?.runAssociations ?? []).map((entry) => [entry.runId, entry.source]));
|
|
199
199
|
const entries = await listAllDagRunEntries(repoRoot);
|
|
200
200
|
const related = [];
|
|
201
|
+
// Recency key per run. `runId` is NOT chronological (a later recovery run can
|
|
202
|
+
// sort before an earlier one lexicographically, e.g. `dag-…` vs `2026…`), so
|
|
203
|
+
// ordering by runId alone reports a superseded run as the latest.
|
|
204
|
+
const startedAtByRunId = new Map();
|
|
201
205
|
for (const entry of entries) {
|
|
202
206
|
let source = recorded.get(entry.runId) ?? null;
|
|
203
207
|
let stateStatus;
|
|
@@ -207,6 +211,9 @@ export async function listExplicitlyAssociatedRuns(repoRoot, taskId) {
|
|
|
207
211
|
try {
|
|
208
212
|
const state = await readDagRunState(entry.runDir);
|
|
209
213
|
stateStatus = state.status;
|
|
214
|
+
if (typeof state.startedAt === "string") {
|
|
215
|
+
startedAtByRunId.set(entry.runId, state.startedAt);
|
|
216
|
+
}
|
|
210
217
|
const liveness = assessDagRunLiveness({ state });
|
|
211
218
|
const activeNode = Object.values(state.nodes ?? {}).find((node) => node.status === "RUNNING");
|
|
212
219
|
lastMeaningfulProgressAt =
|
|
@@ -266,7 +273,25 @@ export async function listExplicitlyAssociatedRuns(repoRoot, taskId) {
|
|
|
266
273
|
contractBinding,
|
|
267
274
|
});
|
|
268
275
|
}
|
|
269
|
-
|
|
276
|
+
// Chronological order by run start time; runs without a readable start time
|
|
277
|
+
// sort first so a run with real timing is never displaced by an unknown.
|
|
278
|
+
// runId breaks ties deterministically.
|
|
279
|
+
return related.sort((a, b) => {
|
|
280
|
+
const aStarted = startedAtByRunId.get(a.runId);
|
|
281
|
+
const bStarted = startedAtByRunId.get(b.runId);
|
|
282
|
+
if (aStarted !== bStarted) {
|
|
283
|
+
if (aStarted === undefined)
|
|
284
|
+
return -1;
|
|
285
|
+
if (bStarted === undefined)
|
|
286
|
+
return 1;
|
|
287
|
+
const aTime = Date.parse(aStarted);
|
|
288
|
+
const bTime = Date.parse(bStarted);
|
|
289
|
+
if (Number.isFinite(aTime) && Number.isFinite(bTime) && aTime !== bTime) {
|
|
290
|
+
return aTime - bTime;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
return a.runId.localeCompare(b.runId);
|
|
294
|
+
});
|
|
270
295
|
}
|
|
271
296
|
function deriveLifecycleState(input) {
|
|
272
297
|
if (!input.exists)
|
package/dist/build-stamp.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
|
-
"version": "0.44.0-next.
|
|
4
|
-
"gitSha": "
|
|
5
|
-
"builtAt": "2026-09-
|
|
3
|
+
"version": "0.44.0-next.11",
|
|
4
|
+
"gitSha": "332db6a894b640eef5c495e3a3ba92b383fcf259",
|
|
5
|
+
"builtAt": "2026-09-17T05:51:06.283Z"
|
|
6
6
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { runDoctor } from "../commands/doctor.js";
|
|
2
|
+
import { runPromoteRun } from "../commands/promote-run.js";
|
|
2
3
|
import { runDocsArchive } from "../commands/docs-archive.js";
|
|
3
4
|
import { runDocsAudit } from "../commands/docs-audit.js";
|
|
4
5
|
import { runEval } from "../commands/eval.js";
|
|
@@ -197,6 +198,16 @@ export const COMMAND_DEFINITIONS = [
|
|
|
197
198
|
await runDoctor(adapter, repoRoot, rest.filter((arg) => Boolean(arg)));
|
|
198
199
|
},
|
|
199
200
|
},
|
|
201
|
+
{
|
|
202
|
+
name: "promote-run",
|
|
203
|
+
adapter: "required",
|
|
204
|
+
tier: "operator",
|
|
205
|
+
intent: "Promote deterministic evidence from a completed DAG or one-shot run into task artifacts (修改记录.md, 验证结果.md).",
|
|
206
|
+
usage: "promote-run <task-id> --run-id <run-id>",
|
|
207
|
+
handler: async ({ adapter, repoRoot, rest }) => {
|
|
208
|
+
await runPromoteRun(repoRoot, rest.filter((arg) => Boolean(arg)));
|
|
209
|
+
},
|
|
210
|
+
},
|
|
200
211
|
{
|
|
201
212
|
name: "docs",
|
|
202
213
|
adapter: "required",
|
package/dist/cli/program.js
CHANGED
|
@@ -29,6 +29,7 @@ import { runDocsArchive } from "../commands/docs-archive.js";
|
|
|
29
29
|
import { runDocsAudit } from "../commands/docs-audit.js";
|
|
30
30
|
import { runEval } from "../commands/eval.js";
|
|
31
31
|
import { runDoctor } from "../commands/doctor.js";
|
|
32
|
+
import { runPromoteRun } from "../commands/promote-run.js";
|
|
32
33
|
import { runExamples } from "../commands/examples.js";
|
|
33
34
|
import { runGoal } from "../commands/goal.js";
|
|
34
35
|
import { runHandoffCheck } from "../commands/handoff-check.js";
|
|
@@ -149,6 +150,9 @@ async function runCommanderAction(ctx, command, subcommand, rest) {
|
|
|
149
150
|
case "doctor":
|
|
150
151
|
await runDoctor(ctx.adapter, ctx.repoRoot, compactArgs([subcommand, ...rest]));
|
|
151
152
|
return;
|
|
153
|
+
case "promote-run":
|
|
154
|
+
await runPromoteRun(ctx.repoRoot, compactArgs([subcommand, ...rest]));
|
|
155
|
+
return;
|
|
152
156
|
case "docs":
|
|
153
157
|
await runDocsCommand(ctx.repoRoot, subcommand, rest);
|
|
154
158
|
return;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { capabilityBoundarySchema } from "../task/config-types.js";
|
|
1
2
|
import { advanceTaskLifecycle, } from "../application/task-lifecycle/index.js";
|
|
2
3
|
import { backendTestConfigSchema, } from "../task/config-types.js";
|
|
3
4
|
import { buildOperatorResult, operatorFailed, processExitCodeForOutcome, writeOperatorJson, } from "../shared/operator/index.js";
|
|
@@ -145,13 +146,10 @@ export function parseTaskAdvanceArgs(args) {
|
|
|
145
146
|
if (token === "--capability-boundary") {
|
|
146
147
|
try {
|
|
147
148
|
const parsed = JSON.parse(next());
|
|
148
|
-
|
|
149
|
-
throw new Error("not an object");
|
|
150
|
-
}
|
|
151
|
-
options.capabilityBoundary = parsed;
|
|
149
|
+
options.capabilityBoundary = capabilityBoundarySchema.parse(parsed);
|
|
152
150
|
}
|
|
153
151
|
catch (error) {
|
|
154
|
-
throw new Error(`--capability-boundary must be a JSON object {allowed?, forbidden?}: ${error.message}`);
|
|
152
|
+
throw new Error(`--capability-boundary must be a JSON object {allowed?, forbidden?, requiredClauses?}: ${error.message}`);
|
|
155
153
|
}
|
|
156
154
|
continue;
|
|
157
155
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { readFile } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { stdin as stdinStream } from "node:process";
|
|
4
|
-
import { parseAcceptanceFlag, parseVerifyFlag, parseVerifyTimeoutFlag, prepareTaskSource, PREPARE_MAX_FILE_BYTES, } from "../task/source-prepare/index.js";
|
|
4
|
+
import { parseAcceptanceFlag, parseVerifyFlag, parseVerifyLaneFlag, parseVerifyTimeoutFlag, prepareTaskSource, PREPARE_MAX_FILE_BYTES, } from "../task/source-prepare/index.js";
|
|
5
5
|
import { parseTaskContractDraft } from "../task/contract/schema.js";
|
|
6
6
|
import { buildOperatorResult, operatorFailed, processExitCodeForOutcome, writeOperatorJson, } from "../shared/operator/index.js";
|
|
7
7
|
const COMMAND = "task source prepare";
|
|
@@ -28,6 +28,7 @@ const USAGE = `usage:
|
|
|
28
28
|
[--open-question <text>]...
|
|
29
29
|
[--verify <label:command>]...
|
|
30
30
|
[--verify-timeout <label:timeoutMs>]...
|
|
31
|
+
[--verify-lane <label:static|behavior>]...
|
|
31
32
|
[--request-id <id>]
|
|
32
33
|
[--dry-run | --apply]
|
|
33
34
|
[--force-overwrite-source]
|
|
@@ -157,6 +158,10 @@ export function parseTaskSourcePrepareArgs(args) {
|
|
|
157
158
|
options.verifyTimeout = pushList(options.verifyTimeout, next());
|
|
158
159
|
continue;
|
|
159
160
|
}
|
|
161
|
+
if (token === "--verify-lane") {
|
|
162
|
+
options.verifyLane = pushList(options.verifyLane, next());
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
160
165
|
if (token === "--request-id") {
|
|
161
166
|
options.requestId = next();
|
|
162
167
|
continue;
|
|
@@ -287,13 +292,20 @@ function buildFlags(options) {
|
|
|
287
292
|
const parsed = parseVerifyTimeoutFlag(raw);
|
|
288
293
|
timeouts.set(parsed.label, parsed.timeoutMs);
|
|
289
294
|
}
|
|
295
|
+
const lanes = new Map();
|
|
296
|
+
for (const raw of options.verifyLane ?? []) {
|
|
297
|
+
const parsed = parseVerifyLaneFlag(raw);
|
|
298
|
+
lanes.set(parsed.label, parsed.mode);
|
|
299
|
+
}
|
|
290
300
|
const commands = (options.verify ?? []).map((raw) => {
|
|
291
301
|
const parsed = parseVerifyFlag(raw);
|
|
292
302
|
const timeoutMs = timeouts.get(parsed.label);
|
|
303
|
+
const mode = lanes.get(parsed.label);
|
|
293
304
|
return {
|
|
294
305
|
label: parsed.label,
|
|
295
306
|
command: parsed.command,
|
|
296
307
|
...(timeoutMs !== undefined ? { timeoutMs } : {}),
|
|
308
|
+
...(mode !== undefined ? { mode } : {}),
|
|
297
309
|
};
|
|
298
310
|
});
|
|
299
311
|
// If only timeouts provided without verify, treat as empty explicit list? No — only set when --verify appears.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/** DAG Pi 模型引用解析:纯配置叶子模块,零依赖,禁止反向依赖执行器入口。 */
|
|
2
|
+
export const DEFAULT_DAG_PI_PROVIDER = "wizard-local";
|
|
3
|
+
export const DAG_PI_MODEL_PROVIDERS = {
|
|
4
|
+
"gpt-5.3-codex-spark": "wizard-local",
|
|
5
|
+
"gpt-5.5": "wizard-local",
|
|
6
|
+
"glm-5.2": "wizard-local",
|
|
7
|
+
"deepseek-v4-flash": "deepseek",
|
|
8
|
+
"deepseek-v4-pro": "deepseek",
|
|
9
|
+
};
|
|
10
|
+
export function resolveDagPiModelConfig(modelReference, options) {
|
|
11
|
+
const separatorIndex = modelReference.indexOf("/");
|
|
12
|
+
const qualified = separatorIndex >= 0;
|
|
13
|
+
const provider = qualified
|
|
14
|
+
? modelReference.slice(0, separatorIndex)
|
|
15
|
+
: (DAG_PI_MODEL_PROVIDERS[modelReference] ?? DEFAULT_DAG_PI_PROVIDER);
|
|
16
|
+
const model = qualified
|
|
17
|
+
? modelReference.slice(separatorIndex + 1)
|
|
18
|
+
: modelReference;
|
|
19
|
+
if (!provider || !model) {
|
|
20
|
+
throw new Error(`invalid DAG Pi model reference "${modelReference}": expected non-empty provider/model`);
|
|
21
|
+
}
|
|
22
|
+
const explicitThinking = options?.thinking?.trim();
|
|
23
|
+
const thinking = explicitThinking ??
|
|
24
|
+
(provider === "wizard-local" && model === "gpt-5.5" ? "low" : undefined);
|
|
25
|
+
return {
|
|
26
|
+
provider,
|
|
27
|
+
model,
|
|
28
|
+
...(thinking ? { thinking } : {}),
|
|
29
|
+
};
|
|
30
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { routeFrontendProviderCapability } from "../../../workflows/dag/frontend-provider-capability-matrix.js";
|
|
2
|
+
/**
|
|
3
|
+
* 构建 plan ledger 的 fact adopter。`deps.session` 必须传会话对象本身:adopter 会在**调用时**
|
|
4
|
+
* 读取 `session.store`(createDurableFrontendTools 每次域工具调用前后会换入 structuredClone 草稿,
|
|
5
|
+
* 构建时快照会写到过期 store)。
|
|
6
|
+
*/
|
|
7
|
+
export function createPlanFactAdopter(deps) {
|
|
8
|
+
return async function adoptPlanFact(kind, requestId, fact) {
|
|
9
|
+
// A+B (AC-005): a provider-capability fact kind reaching the plan ledger
|
|
10
|
+
// is out of route — it belongs to the provider capability channel,
|
|
11
|
+
// not the plan decision ledger. Route it through the frozen seven-kind
|
|
12
|
+
// matrix and fail closed to `unsupported-provider-capability` instead of
|
|
13
|
+
// silently widening the plan catalog.
|
|
14
|
+
const capabilityRoute = routeFrontendProviderCapability({ factKind: kind });
|
|
15
|
+
if (capabilityRoute.ok) {
|
|
16
|
+
return {
|
|
17
|
+
ok: false,
|
|
18
|
+
kind,
|
|
19
|
+
code: "unsupported-provider-capability",
|
|
20
|
+
error: `plan ledger cannot adopt provider capability fact kind: ${kind}`,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
try {
|
|
24
|
+
const staged = deps.stageTypedEventFact({
|
|
25
|
+
store: deps.session.store,
|
|
26
|
+
requestId,
|
|
27
|
+
attemptId: deps.session.attemptId,
|
|
28
|
+
fact: fact,
|
|
29
|
+
});
|
|
30
|
+
const committed = await deps.adoptTypedEventFact({
|
|
31
|
+
store: deps.session.store,
|
|
32
|
+
requestId,
|
|
33
|
+
attemptId: deps.session.attemptId,
|
|
34
|
+
fact: fact,
|
|
35
|
+
eventId: staged.eventId,
|
|
36
|
+
expectedRevision: deps.session.store.revision,
|
|
37
|
+
});
|
|
38
|
+
return {
|
|
39
|
+
ok: true,
|
|
40
|
+
kind,
|
|
41
|
+
eventId: committed.eventId,
|
|
42
|
+
revision: committed.revision,
|
|
43
|
+
error: "",
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
return {
|
|
48
|
+
ok: false,
|
|
49
|
+
kind,
|
|
50
|
+
code: error?.code,
|
|
51
|
+
error: error instanceof Error ? error.message : String(error),
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
/** DAG Pi plan ledger 完整性事实计算:纯类型与纯函数,禁止反向依赖执行器入口。 */
|
|
2
|
+
import { collectCanonicalStateFlowNames } from "../../../workflows/dag/frontend-contract-facts.js";
|
|
2
3
|
export function committedFactFromPlanRecord(value) {
|
|
3
4
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
4
5
|
return undefined;
|
|
@@ -66,9 +67,16 @@ export function collectFrontendPlanMissingFacts(input) {
|
|
|
66
67
|
});
|
|
67
68
|
continue;
|
|
68
69
|
}
|
|
69
|
-
|
|
70
|
+
// Verification targets are the single authoritative direction. The
|
|
71
|
+
// legacy requirement-side list is accepted only as a fallback while
|
|
72
|
+
// resuming older ledgers; new plans derive it from target.requirementIds.
|
|
73
|
+
const derivedTargetIds = [...verificationTargetRequirements.entries()]
|
|
74
|
+
.filter(([, requirementIds]) => requirementIds.has(id))
|
|
75
|
+
.map(([targetId]) => targetId);
|
|
76
|
+
const legacyTargetIds = Array.isArray(entry.verificationTargetIds)
|
|
70
77
|
? entry.verificationTargetIds.filter((value) => typeof value === "string" && value.trim().length > 0)
|
|
71
78
|
: [];
|
|
79
|
+
const targetIds = derivedTargetIds.length > 0 ? derivedTargetIds : legacyTargetIds;
|
|
72
80
|
const gap = entry.evidenceGap && typeof entry.evidenceGap === "object"
|
|
73
81
|
? entry.evidenceGap
|
|
74
82
|
: undefined;
|
|
@@ -113,44 +121,31 @@ export function collectFrontendPlanPhaseMissingFacts(input) {
|
|
|
113
121
|
];
|
|
114
122
|
}
|
|
115
123
|
if (input.phase === "ux-local") {
|
|
116
|
-
// One snapshot per check: no cross-revision cache to invalidate.
|
|
117
|
-
const required = new Set(input.behaviorRequiredRequirementIds ?? []);
|
|
118
|
-
const requested = new Set(input.requirementIds.filter(id => required.has(id)));
|
|
119
|
-
if (!requested.size)
|
|
120
|
-
return [];
|
|
121
|
-
const byRequirement = new Map();
|
|
122
|
-
for (const fact of facts) {
|
|
123
|
-
for (const id of new Set(planFactStringList(fact.scopeRequirementIds))) {
|
|
124
|
-
if (!requested.has(id))
|
|
125
|
-
continue;
|
|
126
|
-
const entries = byRequirement.get(id) ?? [];
|
|
127
|
-
entries.push(fact);
|
|
128
|
-
byRequirement.set(id, entries);
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
const liveStateFlow = collectCanonicalStateFlowNames(facts);
|
|
132
124
|
const missing = [];
|
|
133
|
-
|
|
134
|
-
|
|
125
|
+
// Evaluate each behaviour requirement independently. A fact scoped to AC-1
|
|
126
|
+
// must not accidentally satisfy AC-2 merely because both ids share one
|
|
127
|
+
// UX session; shared facts remain valid when they explicitly list both ids.
|
|
128
|
+
for (const requirementId of input.requirementIds) {
|
|
129
|
+
if (!input.behaviorRequiredRequirementIds?.includes(requirementId))
|
|
135
130
|
continue;
|
|
136
|
-
const scopedFacts =
|
|
131
|
+
const scopedFacts = facts.filter((fact) => planFactScopeIntersects(fact, new Set([requirementId])));
|
|
137
132
|
const hasChoice = scopedFacts.some((fact) => fact.kind === "component-choice" &&
|
|
138
133
|
Array.isArray(fact.uiComponentChoices) &&
|
|
139
134
|
fact.uiComponentChoices.length > 0);
|
|
140
135
|
const canonicalStateFlow = collectCanonicalStateFlowNames(scopedFacts);
|
|
141
|
-
const hasStateFlow =
|
|
142
|
-
|
|
136
|
+
const hasStateFlow = canonicalStateFlow.uiStateNames.size > 0 ||
|
|
137
|
+
canonicalStateFlow.interactionNames.size > 0;
|
|
143
138
|
if (!hasChoice) {
|
|
144
139
|
missing.push({
|
|
145
140
|
kind: "component-choice",
|
|
146
|
-
requirementIds: [
|
|
141
|
+
requirementIds: [requirementId],
|
|
147
142
|
reason: "behaviour-required UX slice has no committed component-choice fact",
|
|
148
143
|
});
|
|
149
144
|
}
|
|
150
145
|
if (!hasStateFlow) {
|
|
151
146
|
missing.push({
|
|
152
147
|
kind: "state-flow",
|
|
153
|
-
requirementIds: [
|
|
148
|
+
requirementIds: [requirementId],
|
|
154
149
|
reason: "behaviour-required UX slice has no committed state-flow fact",
|
|
155
150
|
});
|
|
156
151
|
}
|
|
@@ -158,9 +153,8 @@ export function collectFrontendPlanPhaseMissingFacts(input) {
|
|
|
158
153
|
return missing;
|
|
159
154
|
}
|
|
160
155
|
const hasMockApi = facts.some((fact) => fact.kind === "mock-api");
|
|
161
|
-
const allInteractions = collectCanonicalStateFlowNames(
|
|
162
|
-
const
|
|
163
|
-
const liveInteractions = new Set([...collectCanonicalStateFlowNames(facts.filter(f => !planFactStringList(f.scopeRequirementIds).length || planFactScopeIntersects(f, requested))).interactionNames].filter(name => allInteractions.has(name)));
|
|
156
|
+
const allInteractions = collectCanonicalStateFlowNames(input.committedFacts).interactionNames;
|
|
157
|
+
const liveInteractions = new Set([...collectCanonicalStateFlowNames(facts.filter(f => !planFactStringList(f.scopeRequirementIds).length || planFactScopeIntersects(f, new Set(input.requirementIds)))).interactionNames].filter(name => allInteractions.has(name)));
|
|
164
158
|
const coveredInteractions = new Set(facts
|
|
165
159
|
.filter((fact) => fact.kind === "data-flow")
|
|
166
160
|
.flatMap((fact) => planFactStringList(fact.interactions)));
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
import { loadContractRequirementInheritance } from "./ledger-contract.js";
|
|
2
|
+
import { collectFrontendPlanPhaseMissingFacts } from "./facts.js";
|
|
3
|
+
import { planToolReceipt, stringList } from "./tool-helpers.js";
|
|
4
|
+
import { collectCanonicalStateFlowNames } from "../../../workflows/dag/frontend-contract-facts.js";
|
|
5
|
+
import { isRecordObject } from "../guards.js";
|
|
6
|
+
import { randomUUID } from "node:crypto";
|
|
7
|
+
export function buildPlanFinalizeTools(ctx) {
|
|
8
|
+
const { Type, defineTool, session, schemas, input, readCommittedEvents, assemblePlanPatchFromCommittedFacts, contractInheritance, adoptStagedFact, adoptPlanFact } = ctx;
|
|
9
|
+
const { optionalString, optionalStringArray } = ctx.schemas;
|
|
10
|
+
const finalizePlanTool = defineTool({
|
|
11
|
+
name: "finalize_plan",
|
|
12
|
+
label: "finalize_plan",
|
|
13
|
+
description: "Commit the finalize_plan terminal. Requirements, verification targets, and evidence gaps (optional) were already committed incrementally through record_plan_requirement / record_plan_verification_target / record_plan_evidence_gap; finalize_plan assembles them from the ledger together with these optional remaining fields, publishes the canonical editable patch on a target-surface fact, and commits the terminal. A successful terminal commit occurs exactly once. If validation fails, correct only the reported facts and retry finalize.",
|
|
14
|
+
promptSnippet: "Commit the finalize_plan terminal (ledger fields + optional residualRisks / realIntegrationGap).",
|
|
15
|
+
parameters: Type.Object({
|
|
16
|
+
residualRisks: optionalStringArray,
|
|
17
|
+
realIntegrationGap: optionalString,
|
|
18
|
+
}, { additionalProperties: false }),
|
|
19
|
+
async execute(_toolCallId, params) {
|
|
20
|
+
try {
|
|
21
|
+
const committed = readCommittedEvents(session.store, session.attemptId);
|
|
22
|
+
// Source fidelity ledger (AC-005/AC-006): inherit the contract
|
|
23
|
+
// node's declared requirement→fragment provenance so the compiled
|
|
24
|
+
// canonical contract carries sourceFragmentIds/sourceRefs even
|
|
25
|
+
// when the plan did not re-declare them. The contract node is the
|
|
26
|
+
// sole synthesis point; the plan inherits by requirement id.
|
|
27
|
+
const contractInheritance = await loadContractRequirementInheritance(input.runDir);
|
|
28
|
+
const missingData = collectFrontendPlanPhaseMissingFacts({ phase: "global-mock-data", requirementIds: input.requirementIds ?? [...contractInheritance.keys()], committedFacts: committed }).filter(f => f.kind === "data-flow");
|
|
29
|
+
if (missingData.length)
|
|
30
|
+
return planToolReceipt({ ok: false, kind: "finalize_plan", code: "PLAN_DATA_FLOW_INCOMPLETE", error: missingData.map(f => f.reason).join("; ") });
|
|
31
|
+
const fragment = assemblePlanPatchFromCommittedFacts(committed, contractInheritance) ?? {};
|
|
32
|
+
const patch = {
|
|
33
|
+
...fragment,
|
|
34
|
+
...(params?.residualRisks
|
|
35
|
+
? { residualRisks: params.residualRisks }
|
|
36
|
+
: {}),
|
|
37
|
+
...(params?.realIntegrationGap
|
|
38
|
+
? { realIntegrationGap: params.realIntegrationGap }
|
|
39
|
+
: {}),
|
|
40
|
+
};
|
|
41
|
+
const latestRegistry = [...committed]
|
|
42
|
+
.reverse()
|
|
43
|
+
.map((record) => record.fact)
|
|
44
|
+
.find((fact) => isRecordObject(fact) && fact.kind === "state-registry");
|
|
45
|
+
if (latestRegistry) {
|
|
46
|
+
const registryUiStates = new Set(stringList(latestRegistry.uiStateNames));
|
|
47
|
+
const registryInteractions = new Set(stringList(latestRegistry.interactionNames));
|
|
48
|
+
const live = collectCanonicalStateFlowNames(committed);
|
|
49
|
+
const missingUiStates = [...registryUiStates].filter((name) => !live.uiStateNames.has(name));
|
|
50
|
+
const missingInteractions = [...registryInteractions].filter((name) => !live.interactionNames.has(name));
|
|
51
|
+
const undeclaredUiStates = [...live.uiStateNames].filter((name) => !registryUiStates.has(name));
|
|
52
|
+
const undeclaredInteractions = [...live.interactionNames].filter((name) => !registryInteractions.has(name));
|
|
53
|
+
if (missingUiStates.length > 0 ||
|
|
54
|
+
missingInteractions.length > 0 ||
|
|
55
|
+
undeclaredUiStates.length > 0 ||
|
|
56
|
+
undeclaredInteractions.length > 0) {
|
|
57
|
+
return planToolReceipt({
|
|
58
|
+
ok: false,
|
|
59
|
+
kind: "finalize_plan",
|
|
60
|
+
error: `finalize_plan UX registry mismatch: every registered name must have one live state-flow entry and every live entry must be registered (missing uiStates: ${missingUiStates.join(", ") || "none"}; missing interactions: ${missingInteractions.join(", ") || "none"}; undeclared uiStates: ${undeclaredUiStates.join(", ") || "none"}; undeclared interactions: ${undeclaredInteractions.join(", ") || "none"})`,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
// Front-load the node's compile + policy gates into the finalize
|
|
65
|
+
// receipt (same pipeline the design-policy shell and the node
|
|
66
|
+
// self-check run: merge the patch onto the runtime skeleton,
|
|
67
|
+
// then the full analyze). A failing gate used to burn an entire
|
|
68
|
+
// attempt per finding (r8/r9: ui-design-coverage, verification
|
|
69
|
+
// targets, UI-state shape, one attempt each); surfaced here the
|
|
70
|
+
// model fixes the facts and re-calls finalize_plan in-node.
|
|
71
|
+
if (input.skeleton && input.sourceBinding) {
|
|
72
|
+
// Front-load the exact pipeline the design-policy shell and
|
|
73
|
+
// the node self-check run (patch ⊕ skeleton -> analyze ->
|
|
74
|
+
// policy pre-checks) into the finalize receipt. Findings
|
|
75
|
+
// come back as fixable receipt errors instead of burning
|
|
76
|
+
// an attempt per gate (r8/r9: coverage, verification
|
|
77
|
+
// targets, UI-state shape each cost a full attempt).
|
|
78
|
+
const { analyzeFrontendPlanPatchCandidate, applyFrontendContractMergePatch, FrontendContractFailure, PlanPolicyPrecheckFailure, serializeDeterministicJson, } = await import("../../../workflows/dag/frontend-implementation-contract.js");
|
|
79
|
+
try {
|
|
80
|
+
const merged = applyFrontendContractMergePatch(input.skeleton, patch);
|
|
81
|
+
await analyzeFrontendPlanPatchCandidate({
|
|
82
|
+
runDir: input.runDir,
|
|
83
|
+
rawContractText: serializeDeterministicJson(merged),
|
|
84
|
+
sourceBinding: input.sourceBinding,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
if (error instanceof PlanPolicyPrecheckFailure) {
|
|
89
|
+
// Template the fix: every uncovered interaction / state
|
|
90
|
+
// maps to a record_component_choice skeleton. Reuse is
|
|
91
|
+
// never implied: the operator/model must fill an existing
|
|
92
|
+
// evidencePath or switch the choice to decision=new with
|
|
93
|
+
// the required frozen source citation.
|
|
94
|
+
const suggestions = error.findings
|
|
95
|
+
.filter((finding) => finding.code === "ui-design-coverage-missing" &&
|
|
96
|
+
finding.path)
|
|
97
|
+
.map((finding) => ({
|
|
98
|
+
tool: "record_component_choice",
|
|
99
|
+
args: {
|
|
100
|
+
choice: {
|
|
101
|
+
purpose: finding.path,
|
|
102
|
+
component: "<name the existing or new component>",
|
|
103
|
+
decision: "reuse-existing",
|
|
104
|
+
evidencePath: "<existing repo file that proves this reuse>",
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
}));
|
|
108
|
+
const suggestionBlock = suggestions.length > 0
|
|
109
|
+
? ` Suggested record_* calls (copy, fill component, submit): ${JSON.stringify(suggestions)}`
|
|
110
|
+
: "";
|
|
111
|
+
return planToolReceipt({
|
|
112
|
+
ok: false,
|
|
113
|
+
kind: "finalize_plan",
|
|
114
|
+
error: `finalize_plan pre-validation failed (fix the listed plan facts with record_* tools, then call finalize_plan again): ${error.message}${suggestionBlock}`,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
if (!(error instanceof FrontendContractFailure))
|
|
118
|
+
throw error;
|
|
119
|
+
return planToolReceipt({
|
|
120
|
+
ok: false,
|
|
121
|
+
kind: "finalize_plan",
|
|
122
|
+
error: `finalize_plan pre-validation failed (fix the listed plan facts with record_* tools, then call finalize_plan again): ${error.message}`,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
const patchResult = await adoptPlanFact("target-surface", `${session.attemptId}:finalize_plan:patch:${randomUUID()}`, { kind: "target-surface", origin: "plan", patch });
|
|
127
|
+
if (!patchResult.ok) {
|
|
128
|
+
return planToolReceipt({
|
|
129
|
+
ok: false,
|
|
130
|
+
kind: "finalize_plan",
|
|
131
|
+
error: patchResult.error,
|
|
132
|
+
code: patchResult.code,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
const terminal = await adoptPlanFact("finalize_plan", `${session.attemptId}:finalize_plan:terminal:${randomUUID()}`, { kind: "finalize_plan", origin: "plan", patch });
|
|
136
|
+
return planToolReceipt(terminal);
|
|
137
|
+
}
|
|
138
|
+
catch (error) {
|
|
139
|
+
return planToolReceipt({
|
|
140
|
+
ok: false,
|
|
141
|
+
kind: "finalize_plan",
|
|
142
|
+
error: error instanceof Error ? error.message : String(error),
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
},
|
|
146
|
+
});
|
|
147
|
+
const adoptStagedFactTool = defineTool({
|
|
148
|
+
name: "adopt_staged_fact",
|
|
149
|
+
label: "adopt_staged_fact",
|
|
150
|
+
description: "Explicitly adopt a quarantined fact from a prior attempt into the committed ledger (idempotent by requestId).",
|
|
151
|
+
promptSnippet: "Adopt a quarantined fact into the committed ledger.",
|
|
152
|
+
parameters: Type.Object({
|
|
153
|
+
requestId: Type.String({}),
|
|
154
|
+
eventId: Type.String({}),
|
|
155
|
+
expectedRevision: Type.Number({}),
|
|
156
|
+
}, { additionalProperties: false }),
|
|
157
|
+
async execute(_toolCallId, params) {
|
|
158
|
+
try {
|
|
159
|
+
const adopted = await adoptStagedFact({
|
|
160
|
+
store: session.store,
|
|
161
|
+
requestId: typeof params?.requestId === "string" ? params.requestId : "",
|
|
162
|
+
attemptId: session.attemptId,
|
|
163
|
+
eventId: typeof params?.eventId === "string" ? params.eventId : "",
|
|
164
|
+
expectedRevision: typeof params?.expectedRevision === "number"
|
|
165
|
+
? params.expectedRevision
|
|
166
|
+
: session.store.revision,
|
|
167
|
+
});
|
|
168
|
+
return planToolReceipt({
|
|
169
|
+
ok: true,
|
|
170
|
+
kind: "adopt_staged_fact",
|
|
171
|
+
eventId: adopted.eventId,
|
|
172
|
+
revision: adopted.revision,
|
|
173
|
+
error: "",
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
catch (error) {
|
|
177
|
+
return planToolReceipt({
|
|
178
|
+
ok: false,
|
|
179
|
+
kind: "adopt_staged_fact",
|
|
180
|
+
error: error instanceof Error ? error.message : String(error),
|
|
181
|
+
code: error?.code,
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
},
|
|
185
|
+
});
|
|
186
|
+
const readPlanFactsTool = defineTool({
|
|
187
|
+
name: "read_plan_facts",
|
|
188
|
+
label: "read_plan_facts",
|
|
189
|
+
description: "Read the LIVE committed plan ledger without repository access. Filter by kind and optional entryId or eventId. Use field (dot path, e.g. entry.requirementIds or uiStates) to page large arrays/strings. offset/limit paginate results; follow nextOffset until null before replacing any full-array fact. expectedRevision pins all pages to one ledger revision; restart if it changes. No writes.",
|
|
190
|
+
parameters: Type.Object({
|
|
191
|
+
kind: Type.String(), entryId: Type.Optional(Type.String()), eventId: Type.Optional(Type.String()),
|
|
192
|
+
field: Type.Optional(Type.String()), offset: Type.Optional(Type.Integer({ minimum: 0 })),
|
|
193
|
+
limit: Type.Optional(Type.Integer({ minimum: 1, maximum: 50 })),
|
|
194
|
+
stringOffset: Type.Optional(Type.Integer({ minimum: 0 })),
|
|
195
|
+
expectedRevision: Type.Optional(Type.Integer({ minimum: 0 })),
|
|
196
|
+
}, { additionalProperties: false }),
|
|
197
|
+
async execute(_id, params) {
|
|
198
|
+
const reply = (details) => ({ content: [{ type: "text", text: JSON.stringify(details) }], details });
|
|
199
|
+
if (params.expectedRevision !== undefined && params.expectedRevision !== session.store.revision) {
|
|
200
|
+
return reply({ ok: false, revision: session.store.revision, error: "ledger revision changed; restart pagination" });
|
|
201
|
+
}
|
|
202
|
+
let records = readCommittedEvents(session.store, session.attemptId).filter(record => {
|
|
203
|
+
const fact = record.fact;
|
|
204
|
+
return fact.kind === params.kind && (!params.eventId || record.eventId === params.eventId) &&
|
|
205
|
+
(!params.entryId || fact.entry?.id === params.entryId);
|
|
206
|
+
});
|
|
207
|
+
// Full-entry corrections and registries are last-wins. Other kinds
|
|
208
|
+
// remain an ordered event log (state removals must remain visible).
|
|
209
|
+
if (params.entryId || params.kind === "state-registry")
|
|
210
|
+
records = records.slice(-1);
|
|
211
|
+
let values = records.map(record => ({ eventId: record.eventId, ...record.fact }));
|
|
212
|
+
if (params.field) {
|
|
213
|
+
values = records.flatMap(record => {
|
|
214
|
+
let value = record.fact;
|
|
215
|
+
for (const key of params.field.split(".")) {
|
|
216
|
+
if (!value || typeof value !== "object" || !Object.hasOwn(value, key))
|
|
217
|
+
return [];
|
|
218
|
+
value = value[key];
|
|
219
|
+
}
|
|
220
|
+
return Array.isArray(value) ? value : [value];
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
const offset = params.offset ?? 0;
|
|
224
|
+
if (params.stringOffset !== undefined) {
|
|
225
|
+
const value = values[offset];
|
|
226
|
+
if (typeof value !== "string")
|
|
227
|
+
return reply({ ok: false, error: "stringOffset requires a string field item" });
|
|
228
|
+
// Unicode code points prevent a page boundary splitting a surrogate pair.
|
|
229
|
+
const characters = Array.from(value);
|
|
230
|
+
const chunk = characters.slice(params.stringOffset, params.stringOffset + 1000).join("");
|
|
231
|
+
const next = params.stringOffset + Array.from(chunk).length;
|
|
232
|
+
return reply({ ok: true, revision: session.store.revision, offset, stringOffset: params.stringOffset,
|
|
233
|
+
chunk, totalCharacters: characters.length, nextStringOffset: next < characters.length ? next : null });
|
|
234
|
+
}
|
|
235
|
+
const items = [];
|
|
236
|
+
let bytes = 0;
|
|
237
|
+
for (const value of values.slice(offset, offset + (params.limit ?? 10))) {
|
|
238
|
+
const size = Buffer.byteLength(JSON.stringify(value));
|
|
239
|
+
if (bytes + size > 12_000)
|
|
240
|
+
break;
|
|
241
|
+
bytes += size;
|
|
242
|
+
items.push(value);
|
|
243
|
+
}
|
|
244
|
+
if (!items.length && offset < values.length)
|
|
245
|
+
return reply({
|
|
246
|
+
ok: false, revision: session.store.revision, total: values.length, offset,
|
|
247
|
+
error: "item exceeds the page budget; select an eventId/entryId and a narrower field path; for a string item set stringOffset: 0 and follow nextStringOffset",
|
|
248
|
+
fields: typeof values[offset] === "object" && values[offset] !== null ? Object.keys(values[offset]) : [],
|
|
249
|
+
});
|
|
250
|
+
return reply({ ok: true, revision: session.store.revision, total: values.length, offset, items,
|
|
251
|
+
nextOffset: offset + items.length < values.length ? offset + items.length : null });
|
|
252
|
+
},
|
|
253
|
+
});
|
|
254
|
+
return {
|
|
255
|
+
finalizePlanTool,
|
|
256
|
+
adoptStagedFactTool,
|
|
257
|
+
readPlanFactsTool,
|
|
258
|
+
};
|
|
259
|
+
}
|