@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
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 前端 plan ledger 的 13 个 typebox schema。
|
|
3
|
+
*
|
|
4
|
+
* `Type` 由调用方注入,而不是模块级 `import { Type } from "typebox"`:executor 工厂内以
|
|
5
|
+
* `await import("typebox")` 延迟加载 typebox,改成静态导入会改变模块加载时机。
|
|
6
|
+
*/
|
|
7
|
+
export function buildPlanSchemas(Type) {
|
|
8
|
+
const stringArray = Type.Array(Type.String({}));
|
|
9
|
+
const optionalString = Type.Optional(Type.String({}));
|
|
10
|
+
const optionalStringArray = Type.Optional(stringArray);
|
|
11
|
+
const requirementSchema = Type.Object({
|
|
12
|
+
id: Type.String({}),
|
|
13
|
+
expectedOutcome: Type.Optional(Type.String({
|
|
14
|
+
description: "Optional. When omitted, the runtime derives it from the contract requirement. Prefer omitting it to keep this tool call small.",
|
|
15
|
+
})),
|
|
16
|
+
implementationTargets: stringArray,
|
|
17
|
+
verificationTargetIds: Type.Optional(stringArray),
|
|
18
|
+
evidenceGap: Type.Optional(Type.Object({
|
|
19
|
+
requirementId: optionalString,
|
|
20
|
+
description: Type.String({}),
|
|
21
|
+
blocking: Type.Boolean(),
|
|
22
|
+
}, { additionalProperties: false })),
|
|
23
|
+
}, { additionalProperties: false });
|
|
24
|
+
const uiStateSchema = Type.Object({
|
|
25
|
+
name: Type.String({}),
|
|
26
|
+
applicable: Type.Boolean(),
|
|
27
|
+
expectedBehavior: optionalString,
|
|
28
|
+
implementationTargets: optionalStringArray,
|
|
29
|
+
verificationTargetIds: optionalStringArray,
|
|
30
|
+
notApplicableReason: optionalString,
|
|
31
|
+
reason: Type.Optional(Type.String({})),
|
|
32
|
+
}, { additionalProperties: false });
|
|
33
|
+
const interactionSchema = Type.Object({
|
|
34
|
+
name: optionalString,
|
|
35
|
+
id: Type.Optional(Type.String({})),
|
|
36
|
+
trigger: Type.String({}),
|
|
37
|
+
expectedBehavior: Type.String({}),
|
|
38
|
+
implementationTargets: stringArray,
|
|
39
|
+
verificationTargetIds: stringArray,
|
|
40
|
+
}, { additionalProperties: false });
|
|
41
|
+
const mockEndpointSchema = Type.Object({
|
|
42
|
+
method: Type.Enum({ GET: "GET", POST: "POST", PUT: "PUT", PATCH: "PATCH", DELETE: "DELETE", HEAD: "HEAD", OPTIONS: "OPTIONS" }),
|
|
43
|
+
path: Type.String({}),
|
|
44
|
+
fixture: optionalString,
|
|
45
|
+
consumer: optionalString,
|
|
46
|
+
}, { additionalProperties: false });
|
|
47
|
+
const mockApiSchema = Type.Object({
|
|
48
|
+
strategy: Type.Union([
|
|
49
|
+
Type.Literal("native"),
|
|
50
|
+
Type.Literal("browser-intercept"),
|
|
51
|
+
Type.Literal("request-adapter"),
|
|
52
|
+
Type.Literal("not-needed"),
|
|
53
|
+
], {
|
|
54
|
+
description: "native | browser-intercept | request-adapter | not-needed",
|
|
55
|
+
}),
|
|
56
|
+
activation: Type.String({}),
|
|
57
|
+
endpoints: Type.Array(mockEndpointSchema),
|
|
58
|
+
}, { additionalProperties: false });
|
|
59
|
+
const designEvidenceSchema = Type.Object({
|
|
60
|
+
source: Type.String({}),
|
|
61
|
+
paths: stringArray,
|
|
62
|
+
conflicts: stringArray,
|
|
63
|
+
}, { additionalProperties: false });
|
|
64
|
+
// Verification mode is runtime-owned (contract v2): the plan submits a
|
|
65
|
+
// commandId referencing the frozen command directory, never a type. A
|
|
66
|
+
// soft Type.String would let the model commit values that only fail at
|
|
67
|
+
// compile time — keep the reference a required string and validate it
|
|
68
|
+
// against the directory at the tool boundary below.
|
|
69
|
+
const verificationTargetScopeSchema = Type.Union([
|
|
70
|
+
Type.Literal("unit"),
|
|
71
|
+
Type.Literal("component"),
|
|
72
|
+
Type.Literal("integration"),
|
|
73
|
+
]);
|
|
74
|
+
const verificationTargetSchema = Type.Object({
|
|
75
|
+
id: Type.String({}),
|
|
76
|
+
commandId: Type.String({
|
|
77
|
+
description: "Frozen command directory key (e.g. verify-npm-run-build); the runtime resolves mode and label from it.",
|
|
78
|
+
}),
|
|
79
|
+
file: Type.String({}),
|
|
80
|
+
requirementIds: stringArray,
|
|
81
|
+
scope: Type.Optional(verificationTargetScopeSchema),
|
|
82
|
+
uiStates: Type.Optional(Type.Array(Type.String({}), {
|
|
83
|
+
description: "Optional only at this tool boundary. An omitted value is deterministically recorded as []. Pass an explicit array for new calls.",
|
|
84
|
+
})),
|
|
85
|
+
}, { additionalProperties: false });
|
|
86
|
+
const evidenceGapSchema = Type.Object({
|
|
87
|
+
requirementId: optionalString,
|
|
88
|
+
description: Type.String({}),
|
|
89
|
+
blocking: Type.Boolean(),
|
|
90
|
+
}, { additionalProperties: false });
|
|
91
|
+
const uiComponentChoiceSchema = Type.Object({
|
|
92
|
+
purpose: Type.String({}),
|
|
93
|
+
component: Type.String({}),
|
|
94
|
+
decision: Type.Union([
|
|
95
|
+
Type.Literal("specified"),
|
|
96
|
+
Type.Literal("reuse-existing"),
|
|
97
|
+
Type.Literal("new"),
|
|
98
|
+
], {
|
|
99
|
+
description: "specified | reuse-existing | new",
|
|
100
|
+
}),
|
|
101
|
+
specReference: Type.Optional(Type.Object({
|
|
102
|
+
path: Type.String({}),
|
|
103
|
+
section: Type.String({}),
|
|
104
|
+
line: Type.Optional(Type.Integer({ minimum: 1 })),
|
|
105
|
+
}, { additionalProperties: false })),
|
|
106
|
+
rationale: Type.Optional(Type.String({ minLength: 1 })),
|
|
107
|
+
covers: Type.Optional(Type.Array(Type.String({}), {
|
|
108
|
+
description: "UI state and/or interaction names this single component choice covers (one choice may cover many ids).",
|
|
109
|
+
})),
|
|
110
|
+
evidencePath: Type.Optional(Type.String({
|
|
111
|
+
description: "REQUIRED for decision=reuse-existing: repo-relative path whose existing file is the reuse evidence. Greenfield paths must use decision=new.",
|
|
112
|
+
})),
|
|
113
|
+
}, { additionalProperties: false });
|
|
114
|
+
return {
|
|
115
|
+
stringArray,
|
|
116
|
+
optionalString,
|
|
117
|
+
optionalStringArray,
|
|
118
|
+
requirementSchema,
|
|
119
|
+
uiStateSchema,
|
|
120
|
+
interactionSchema,
|
|
121
|
+
mockEndpointSchema,
|
|
122
|
+
mockApiSchema,
|
|
123
|
+
designEvidenceSchema,
|
|
124
|
+
verificationTargetScopeSchema,
|
|
125
|
+
verificationTargetSchema,
|
|
126
|
+
evidenceGapSchema,
|
|
127
|
+
uiComponentChoiceSchema,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** plan ledger 工具共用的纯 helper:零状态、零 I/O。 */
|
|
2
|
+
export const stringList = (value) => Array.isArray(value)
|
|
3
|
+
? value.filter((item) => typeof item === "string")
|
|
4
|
+
: [];
|
|
5
|
+
export const nonEmptyString = (value) => typeof value === "string" && value.trim().length > 0
|
|
6
|
+
? value.trim()
|
|
7
|
+
: undefined;
|
|
8
|
+
export const planToolReceipt = (details) => ({
|
|
9
|
+
content: [
|
|
10
|
+
{
|
|
11
|
+
type: "text",
|
|
12
|
+
text: JSON.stringify(details),
|
|
13
|
+
},
|
|
14
|
+
],
|
|
15
|
+
details,
|
|
16
|
+
});
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** DAG Pi 会话族兼容转发:既有测试从 entry 导入的符号统一经此转发,逐步迁移后移除。 */
|
|
2
|
+
export { estimateFrontendPlanRequirementRecordCalls, batchFrontendPlanRequirements, FRONTEND_PLAN_COVERAGE_MAX_CONCURRENCY, FRONTEND_PLAN_BATCH_MAX_SESSIONS, mapWithConcurrency, buildFrontendPlanWorkload, collectFrontendPlanRequirementIds, loadOrCreateFrontendPlanCoverageLayout, collectFrontendVerificationCommandFiles } from "./plan-batches.js";
|
|
3
|
+
export { compactFrontendPlanLedgerContext, compactFrontendPlanPromptForRequirementSlice } from "./plan-prompts.js";
|
|
4
|
+
export { isPlannerThinkingExhausted, isWriterThinkingExhausted, readWriterThinkingExhaustionEvidence, WRITER_BUDGET_EXHAUSTED_CATEGORY, WRITER_TOKEN_BUDGET, WRITER_THINKING_EXHAUSTED_CATEGORY, PLANNER_THINKING_EXHAUSTED_CATEGORY } from "./writer-evidence.js";
|
|
5
|
+
export { aggregateParallelPiResults, combineSequentialPiResults, runFrontendScoutParallelSessions } from "./scout-parallel.js";
|
|
6
|
+
export { runFrontendContractSegmentedSessions, runFrontendPlanSegmentedSessions, runFrontendReviewSegmentedSessions, runFrontendScoutSegmentedSessions } from "./segmented-plan.js";
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
/** DAG Pi 分段会话的批次规划:work 装配、批次上限、覆盖布局与并发映射。覆盖布局会读写 coverage-layout.json;禁止反向依赖执行器入口。 */
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { readFile, stat } from "node:fs/promises";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { FRONTEND_SCOPE_TARGET_BYTES, packFrontendInputUnits, parseFrontendInputBlock } from "../../../workflows/dag/frontend-input-projection.js";
|
|
6
|
+
import { sha256OfCanonicalJson } from "../../../task/contract/hash.js";
|
|
7
|
+
import { writeDagNodeJsonArtifact } from "../../../infrastructure/harness/artifact-store.js";
|
|
8
|
+
import { isRecordObject } from "../guards.js";
|
|
9
|
+
export const FRONTEND_PLAN_SEGMENTS = [
|
|
10
|
+
{
|
|
11
|
+
id: "coverage",
|
|
12
|
+
toolNames: new Set([
|
|
13
|
+
"record_plan_requirement",
|
|
14
|
+
"record_plan_group_coverage",
|
|
15
|
+
"record_plan_verification_target",
|
|
16
|
+
"record_plan_evidence_gap",
|
|
17
|
+
"adopt_staged_fact",
|
|
18
|
+
]),
|
|
19
|
+
instruction: [
|
|
20
|
+
"PLAN PHASE — requirement coverage only.",
|
|
21
|
+
"Your ONLY job: for every frozen requirement, emit record_plan_requirement (requirement → implementation files) and record_plan_verification_target facts (verification target bound to requirement ids and files). Group related requirements under one non-static behavior target when one observable test behavior proves them together; do not mechanically create one target per requirement. Target ids identify contract entries, not test-title markers. Reuse affected existing test files and their names; do not add tests or rename titles just to carry generated ids. Never submit prose as a symbol. Do NOT record components, UI states, mock, dependency, or routes — a follow-up session owns those.",
|
|
22
|
+
"A coverage session is complete only when EVERY requirement assigned to this session (the full inventory, or the exact COVERAGE BATCH / shard list when present) has committed coverage facts: a record_plan_requirement entry plus verification targets, or a committed evidence gap. Keep committing in batches of up to 4 record_* calls per assistant message until then; do not write a concluding summary while any assigned requirement is still uncommitted — an early stop strands the remainder into a MISSING-FACT repair session and doubles the sessions needed.",
|
|
23
|
+
"If a requirement genuinely cannot have a verification target, record a non-empty record_plan_evidence_gap. Do not call finalize_plan; it is not available in this phase.",
|
|
24
|
+
].join(" "),
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
id: "ux-registry",
|
|
28
|
+
toolNames: new Set(["record_state_registry", "adopt_staged_fact"]),
|
|
29
|
+
instruction: [
|
|
30
|
+
"PLAN PHASE — global UX vocabulary.",
|
|
31
|
+
"Bootstrap the global UX vocabulary from the execution-group index and authoritative declared states. This is navigation, not permission to decide unseen behavior. Detailed complete scopes may extend the registry with replace:true while preserving live names. UI states use declaredUiStates ids when present. Interaction names are stable kebab-case behavior domains; merge requirements that describe the same behavior instead of renaming it per AC slice. Empty arrays explicitly declare that no UX vocabulary applies. Do not record component choices or state-flow details in this phase.",
|
|
32
|
+
"Do not call finalize_plan; it is not available in this phase.",
|
|
33
|
+
].join(" "),
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
id: "ux-local",
|
|
37
|
+
toolNames: new Set([
|
|
38
|
+
"record_state_registry",
|
|
39
|
+
"record_component_choice",
|
|
40
|
+
"record_state_flow",
|
|
41
|
+
"record_plan_verification_target",
|
|
42
|
+
"adopt_staged_fact",
|
|
43
|
+
]),
|
|
44
|
+
instruction: [
|
|
45
|
+
"PLAN PHASE — global UX decisions.",
|
|
46
|
+
"Requirements and verification targets are already committed in the ledger; do not re-record unchanged facts. Review the current complete execution-group scope and the committed global UX registry together, then record each component choice, UI state and interaction exactly once. Bind each applicable state to its verificationTargetIds; the runtime derives the reverse VT.uiStates relation. If a VT requires correction, record_plan_verification_target with replace:true is available after declaring its states; preserve its requirement coverage. Multiple requirements describing one behavior share one registry name and state-flow entry; never repeat or rename it per AC. Cross-cutting data flow belongs to the global Mock/data phase. Do not record routes, Mock/API policy, dependencies, or design deviations here.",
|
|
47
|
+
"Do not call finalize_plan; it is not available in this phase.",
|
|
48
|
+
].join(" "),
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
id: "global-route",
|
|
52
|
+
toolNames: new Set(["record_route_selection", "adopt_staged_fact"]),
|
|
53
|
+
instruction: [
|
|
54
|
+
"PLAN PHASE — global route decision.",
|
|
55
|
+
"Use the Scout target surface and record only the selected route(s). Do not record requirement-local UX, Mock/data, dependency, or deviation facts. Do not call finalize_plan.",
|
|
56
|
+
].join(" "),
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
id: "global-mock-data",
|
|
60
|
+
toolNames: new Set(["record_data_flow", "record_mock_api", "record_mock_endpoint", "adopt_staged_fact"]),
|
|
61
|
+
instruction: [
|
|
62
|
+
"PLAN PHASE — global Mock/API and data policy.",
|
|
63
|
+
"Record the cross-cutting interaction-to-endpoint data flow and Mock/API strategy only. Keep this decision set separate from route, component, state, dependency, and deviation facts. Do not call finalize_plan.",
|
|
64
|
+
].join(" "),
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
id: "global-dependency-deviation",
|
|
68
|
+
toolNames: new Set(["record_dependency", "record_design_deviation", "adopt_staged_fact"]),
|
|
69
|
+
instruction: [
|
|
70
|
+
"PLAN PHASE — global dependency and design-deviation policy.",
|
|
71
|
+
"Record only dependency policy and design-evidence conflicts. Do not record route, Mock/data, or requirement-local UX facts. Do not call finalize_plan.",
|
|
72
|
+
].join(" "),
|
|
73
|
+
},
|
|
74
|
+
];
|
|
75
|
+
export function estimateFrontendPlanRequirementRecordCalls(fact) {
|
|
76
|
+
const record = fact && typeof fact === "object" && !Array.isArray(fact)
|
|
77
|
+
? fact
|
|
78
|
+
: {};
|
|
79
|
+
const declaredTargetCount = Array.isArray(record.verificationTargetIds)
|
|
80
|
+
? record.verificationTargetIds.filter((value) => typeof value === "string" && value.trim()).length
|
|
81
|
+
: Array.isArray(record.verificationTargets)
|
|
82
|
+
? record.verificationTargets.length
|
|
83
|
+
: 0;
|
|
84
|
+
const evidence = record.evidence && typeof record.evidence === "object"
|
|
85
|
+
? record.evidence
|
|
86
|
+
: undefined;
|
|
87
|
+
const targetCount = Math.max(declaredTargetCount, evidence?.behavior === "required" ? 2 : 1);
|
|
88
|
+
return 1 + targetCount;
|
|
89
|
+
}
|
|
90
|
+
export function batchFrontendPlanRequirements(input) {
|
|
91
|
+
const batches = [];
|
|
92
|
+
let current = [];
|
|
93
|
+
let currentCost = 0;
|
|
94
|
+
for (const id of input.requirementIds) {
|
|
95
|
+
const cost = Math.max(1, input.requirementCosts?.get(id) ?? 2);
|
|
96
|
+
if (current.length > 0 &&
|
|
97
|
+
(currentCost + cost > input.maxEstimatedRecordCalls ||
|
|
98
|
+
(input.maxRequirements !== undefined &&
|
|
99
|
+
current.length >= input.maxRequirements))) {
|
|
100
|
+
batches.push(current);
|
|
101
|
+
current = [];
|
|
102
|
+
currentCost = 0;
|
|
103
|
+
}
|
|
104
|
+
current.push(id);
|
|
105
|
+
currentCost += cost;
|
|
106
|
+
}
|
|
107
|
+
if (current.length > 0)
|
|
108
|
+
batches.push(current);
|
|
109
|
+
return batches;
|
|
110
|
+
}
|
|
111
|
+
export const FRONTEND_PLAN_COVERAGE_MAX_RECORD_CALLS = 10;
|
|
112
|
+
export const FRONTEND_PLAN_COVERAGE_MAX_CONCURRENCY = 4;
|
|
113
|
+
export const FRONTEND_PLAN_BATCH_MAX_SESSIONS = 128;
|
|
114
|
+
export const FRONTEND_PLAN_SMALL_MAX_REQUIREMENTS = 8;
|
|
115
|
+
export const FRONTEND_PLAN_SMALL_MAX_ESTIMATED_CALLS = 24;
|
|
116
|
+
export async function mapWithConcurrency(items, limit, worker, shouldReduceConcurrency) {
|
|
117
|
+
const results = new Array(items.length);
|
|
118
|
+
let nextIndex = 0;
|
|
119
|
+
let concurrency = Math.max(1, limit);
|
|
120
|
+
const running = new Map();
|
|
121
|
+
try {
|
|
122
|
+
while (nextIndex < items.length || running.size > 0) {
|
|
123
|
+
while (nextIndex < items.length && running.size < concurrency) {
|
|
124
|
+
const index = nextIndex++;
|
|
125
|
+
running.set(index, worker(items[index], index).then(result => ({ index, result })));
|
|
126
|
+
}
|
|
127
|
+
const { index, result } = await Promise.race(running.values());
|
|
128
|
+
running.delete(index);
|
|
129
|
+
results[index] = result;
|
|
130
|
+
// Drain existing work; only pending shards use the reduced cap.
|
|
131
|
+
// Failed shards remain failed and receive no extra retry allowance.
|
|
132
|
+
if (shouldReduceConcurrency(result))
|
|
133
|
+
concurrency = Math.max(1, Math.floor(concurrency / 2));
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
catch (error) {
|
|
137
|
+
await Promise.allSettled(running.values());
|
|
138
|
+
throw error;
|
|
139
|
+
}
|
|
140
|
+
return results;
|
|
141
|
+
}
|
|
142
|
+
export function collectFrontendVerificationCommandFiles(basePrompt) {
|
|
143
|
+
const files = new Set();
|
|
144
|
+
const configRe = /--config\s+([\w@./-]+\.(?:js|mjs|cjs|ts|json))/g;
|
|
145
|
+
const checkRe = /node\s+--check\s+([\w@./-]+\.(?:js|mjs|cjs))/g;
|
|
146
|
+
for (const re of [configRe, checkRe]) {
|
|
147
|
+
for (const match of basePrompt.matchAll(re)) {
|
|
148
|
+
const file = match[1];
|
|
149
|
+
if (file && file.includes("/"))
|
|
150
|
+
files.add(file);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return [...files].sort();
|
|
154
|
+
}
|
|
155
|
+
export function countFrontendPlanTargetSurfaces(basePrompt) {
|
|
156
|
+
const match = /<frontend_plan_input>[\s\S]*?<\/frontend_plan_input>/.exec(basePrompt);
|
|
157
|
+
if (!match)
|
|
158
|
+
return undefined;
|
|
159
|
+
for (const line of match[0].split(/\r?\n/)) {
|
|
160
|
+
try {
|
|
161
|
+
const payload = JSON.parse(line);
|
|
162
|
+
if (Array.isArray(payload.targetSurface))
|
|
163
|
+
return payload.targetSurface.length;
|
|
164
|
+
}
|
|
165
|
+
catch {
|
|
166
|
+
// surrounding lines are prose
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return undefined;
|
|
170
|
+
}
|
|
171
|
+
export function buildFrontendPlanWorkload(input) {
|
|
172
|
+
const allRequirementIds = input.requirementIds;
|
|
173
|
+
const compiledInput = parseFrontendInputBlock(input.basePrompt, "plan")?.payload;
|
|
174
|
+
const fullUnits = new Map(compiledInput?.requirements.map(r => [r.id, r]) ?? []);
|
|
175
|
+
const declaredGroups = Array.isArray(compiledInput?.executionGroups)
|
|
176
|
+
? compiledInput.executionGroups.filter(isRecordObject) : [];
|
|
177
|
+
const workGroups = declaredGroups.map(g => ({
|
|
178
|
+
id: String(g.id), kind: String(g.kind),
|
|
179
|
+
requirementIds: Array.isArray(g.requirementIds)
|
|
180
|
+
? g.requirementIds.filter((id) => typeof id === "string" && allRequirementIds.includes(id)) : [],
|
|
181
|
+
})).filter(g => g.requirementIds.length);
|
|
182
|
+
const groupedIds = new Set(workGroups.flatMap(g => g.requirementIds));
|
|
183
|
+
for (const id of allRequirementIds) {
|
|
184
|
+
if (!groupedIds.has(id))
|
|
185
|
+
workGroups.push({ id, kind: "unclassified", requirementIds: [id] });
|
|
186
|
+
}
|
|
187
|
+
const workCost = (ids) => 1 + ids.reduce((total, id) => total + Math.max(1, (input.requirementCosts?.get(id) ?? 2) - 1), 0);
|
|
188
|
+
const policy = input.sessionOptions.frontendExecutionPolicy;
|
|
189
|
+
const targetBytes = policy?.scopeTargetBytes ?? FRONTEND_SCOPE_TARGET_BYTES;
|
|
190
|
+
const buildWorkBatches = (ids) => {
|
|
191
|
+
const work = workGroups.flatMap((g, index) => {
|
|
192
|
+
const members = g.requirementIds.filter(id => ids.includes(id));
|
|
193
|
+
return members.length ? [{ id: `${index}:${g.id}`, requirementIds: members,
|
|
194
|
+
requirements: members.map(id => fullUnits.get(id) ?? { id }), estimatedCalls: workCost(members) }] : [];
|
|
195
|
+
});
|
|
196
|
+
// Pack complete work units, not the repeated request scaffold. Deducting
|
|
197
|
+
// fixed context/tools can leave a one-byte budget and force one AC per
|
|
198
|
+
// session without reducing that overhead. The SDK checks the actual
|
|
199
|
+
// request against model capacity; capacity recovery splits unfinished work.
|
|
200
|
+
return packFrontendInputUnits(work, {
|
|
201
|
+
targetBytes,
|
|
202
|
+
maxUnits: policy?.maxScopeUnits ?? 4,
|
|
203
|
+
maxCost: FRONTEND_PLAN_COVERAGE_MAX_RECORD_CALLS, cost: g => g.estimatedCalls,
|
|
204
|
+
}).map(batch => batch.flatMap(g => g.requirementIds));
|
|
205
|
+
};
|
|
206
|
+
// This renderer also carries the Plan's evolving UX checkpoint on resume.
|
|
207
|
+
// Only the Contract/Scout-derived input participates in ownership binding.
|
|
208
|
+
const { committedUx: _committedUx, ...frozenInput } = compiledInput ?? {};
|
|
209
|
+
return {
|
|
210
|
+
frozenInput, workGroups, buildWorkBatches,
|
|
211
|
+
compactEligible: input.requirementCosts !== undefined &&
|
|
212
|
+
workGroups.length <= FRONTEND_PLAN_SMALL_MAX_REQUIREMENTS &&
|
|
213
|
+
workGroups.reduce((total, group) => total + workCost(group.requirementIds), 0) <= FRONTEND_PLAN_SMALL_MAX_ESTIMATED_CALLS &&
|
|
214
|
+
countFrontendPlanTargetSurfaces(input.basePrompt) === 1,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
export function collectFrontendPlanRequirementIds(contractFacts) {
|
|
218
|
+
return [
|
|
219
|
+
...new Set(contractFacts
|
|
220
|
+
.filter((record) => record.fact?.kind ===
|
|
221
|
+
"requirement")
|
|
222
|
+
.map((record) => record.fact?.id)
|
|
223
|
+
.filter((id) => typeof id === "string")),
|
|
224
|
+
];
|
|
225
|
+
}
|
|
226
|
+
export const frontendPlanCoverageLayoutSchema = z.object({
|
|
227
|
+
schemaVersion: z.literal(1),
|
|
228
|
+
bindingSha256: z.string(),
|
|
229
|
+
coverageBatches: z.array(z.array(z.string().min(1)).min(1)).min(1),
|
|
230
|
+
layoutSha256: z.string(),
|
|
231
|
+
}).strict();
|
|
232
|
+
export async function loadOrCreateFrontendPlanCoverageLayout(input) {
|
|
233
|
+
const fileName = "coverage-layout.json";
|
|
234
|
+
const file = path.join(input.runDir, input.nodeId, fileName);
|
|
235
|
+
const bindingSha256 = sha256OfCanonicalJson(input.binding);
|
|
236
|
+
let raw;
|
|
237
|
+
try {
|
|
238
|
+
raw = await readFile(file, "utf8");
|
|
239
|
+
}
|
|
240
|
+
catch (error) {
|
|
241
|
+
if (error.code !== "ENOENT")
|
|
242
|
+
throw error;
|
|
243
|
+
}
|
|
244
|
+
let layout;
|
|
245
|
+
if (raw !== undefined) {
|
|
246
|
+
layout = frontendPlanCoverageLayoutSchema.parse(JSON.parse(raw));
|
|
247
|
+
}
|
|
248
|
+
else {
|
|
249
|
+
// Losing the ownership receipt must never repartition acknowledged facts.
|
|
250
|
+
for (const relative of ["plan-typed-facts.jsonl", "parallel"]) {
|
|
251
|
+
const existing = await stat(path.join(input.runDir, input.nodeId, relative)).catch(error => {
|
|
252
|
+
if (error.code !== "ENOENT")
|
|
253
|
+
throw error;
|
|
254
|
+
return undefined;
|
|
255
|
+
});
|
|
256
|
+
if (existing && (existing.isDirectory() || existing.size > 0)) {
|
|
257
|
+
throw Error("FRONTEND_PLAN_LAYOUT_MISSING: preserve the existing ledger and restart its owning phase");
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
const descriptor = {
|
|
261
|
+
schemaVersion: 1, bindingSha256,
|
|
262
|
+
coverageBatches: input.workload.compactEligible ? [input.requirementIds] : input.workload.buildWorkBatches(input.requirementIds),
|
|
263
|
+
};
|
|
264
|
+
layout = { ...descriptor, layoutSha256: sha256OfCanonicalJson(descriptor) };
|
|
265
|
+
}
|
|
266
|
+
const { layoutSha256, ...descriptor } = layout;
|
|
267
|
+
if (layout.bindingSha256 !== bindingSha256 || layoutSha256 !== sha256OfCanonicalJson(descriptor)) {
|
|
268
|
+
throw Error("FRONTEND_PLAN_LAYOUT_MISMATCH: frozen coverage ownership or input binding changed");
|
|
269
|
+
}
|
|
270
|
+
const members = layout.coverageBatches.flat();
|
|
271
|
+
const owners = new Map(layout.coverageBatches.flatMap((batch, index) => batch.map(id => [id, index])));
|
|
272
|
+
if (members.length !== owners.size || members.length !== input.requirementIds.length ||
|
|
273
|
+
input.requirementIds.some(id => !owners.has(id)) ||
|
|
274
|
+
input.workload.workGroups.some(group => new Set(group.requirementIds.map(id => owners.get(id))).size !== 1)) {
|
|
275
|
+
throw Error("FRONTEND_PLAN_LAYOUT_INVALID: coverage must partition complete execution groups exactly once");
|
|
276
|
+
}
|
|
277
|
+
if (raw === undefined)
|
|
278
|
+
await writeDagNodeJsonArtifact(input.runDir, input.nodeId, fileName, layout);
|
|
279
|
+
return layout.coverageBatches;
|
|
280
|
+
}
|