@tea-agent/loop-agent 0.42.0-next.9 → 0.42.1
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 +111 -45
- package/dist/application/dag/run-dag.js +8 -2
- package/dist/application/evaluation/budget.js +19 -1
- package/dist/application/task-lifecycle/advance.js +26 -8
- package/dist/application/task-lifecycle/observe.js +43 -29
- package/dist/application/task-lifecycle/plan-transitions.js +5 -4
- package/dist/build-stamp.json +3 -3
- package/dist/cli/program.js +1 -1
- package/dist/commands/dag-rerun-task.js +2 -0
- package/dist/commands/task-source-prepare.js +3 -1
- package/dist/executors/dag-pi-executor.js +2584 -600
- package/dist/executors/pi-executor.js +22 -1
- package/dist/executors/pi-extension-resolver.js +14 -2
- package/dist/executors/pi-sdk-executor.js +140 -39
- package/dist/executors/shell-executor.js +135 -55
- package/dist/shared/dag-failure-category.js +6 -0
- package/dist/shared/frontend-execution-policy.js +23 -0
- package/dist/task/config-types.js +4 -0
- package/dist/task/contract/apply.js +36 -2
- package/dist/task/source-prepare/ledger-reconciliation.js +2 -2
- package/dist/task/source-prepare/ledger-review.js +6 -9
- package/dist/task/source-prepare/parse-intent.js +7 -0
- package/dist/task/source-prepare/semantic-intake.js +16 -26
- package/dist/task/source-prepare/source-fidelity-pi.js +28 -9
- package/dist/task/source-references.js +48 -23
- package/dist/worker/console/chat/assistant-content.js +23 -2
- package/dist/worker/console/chat/browser-policy.js +143 -0
- package/dist/worker/console/chat/browser-routes.js +148 -0
- package/dist/worker/console/chat/chat-event-store.js +4 -2
- package/dist/worker/console/chat/explore-tools.js +13 -0
- package/dist/worker/console/chat/pi-runtime.js +173 -94
- package/dist/worker/console/chat/resource-loader.js +4 -1
- package/dist/worker/console/chat/routes.js +208 -66
- package/dist/worker/console/chat/scm-routes.js +217 -0
- package/dist/worker/console/chat/scm-service.js +283 -0
- package/dist/worker/console/chat/scm-tools.js +111 -0
- package/dist/worker/console/chat/sdd-data-alignment.js +78 -11
- package/dist/worker/console/chat/session-catalog.js +32 -0
- package/dist/worker/console/chat/session-mode-view.js +48 -0
- package/dist/worker/console/chat/session-mode.js +218 -0
- package/dist/worker/console/chat/session-store.js +27 -7
- package/dist/worker/console/chat/shortcuts.js +6 -0
- package/dist/worker/console/chat/subagents/agent-tool.js +62 -0
- package/dist/worker/console/chat/subagents/explore-agent.js +169 -0
- package/dist/worker/console/chat/subagents/index.js +5 -0
- package/dist/worker/console/chat/subagents/orchestrator.js +273 -0
- package/dist/worker/console/chat/subagents/tool-policy.js +53 -0
- package/dist/worker/console/chat/subagents/types.js +13 -0
- package/dist/worker/console/chat/terminal-routes.js +216 -0
- package/dist/worker/console/chat/terminal-sessions.js +284 -0
- package/dist/worker/console/chat/terminal-tools.js +199 -0
- package/dist/worker/console/chat/tool-preview.js +21 -0
- package/dist/worker/console/chat/tools.js +13 -1
- package/dist/worker/console/chat/turn-process.js +1 -0
- package/dist/worker/console/interview/tools.js +1 -0
- package/dist/worker/console/server.js +2 -28
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-C6n9_m0P.js → abnfDiagram-N423BO3Z-C9eI7nEo.js} +1 -1
- package/dist/worker/console/static/assets/{arc-DQh-IfZ1.js → arc-VJWsxWhB.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-54NnrwUC.js → architectureDiagram-T3A2C74G-BGcyiMSo.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-pivRALGK.js → blockDiagram-VBNYF7ZC-BScnFwyi.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-BR7OV2NJ.js → c4Diagram-5PPSVZJV-Bl9_BsLi.js} +1 -1
- package/dist/worker/console/static/assets/channel-B6sQYuOE.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-B1Aq6BcK.js → chunk-2GRJ4B5K-F1uKxiUt.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-CbdU0rjo.js → chunk-2Q5K7J3B-rSRMynVu.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-Dqi2GJeD.js → chunk-5RXB4S5H-DAsJm1WD.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-BYn1Hu4R.js → chunk-5VM5RSS4-DQIdVI0a.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-DRLjDL0k.js → chunk-6Q2QTUOP-4A_8rJ-Q.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-CY9Xx2jV.js → chunk-GF5L2VYU-BHnT-vjh.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-BNCZs7_z.js → chunk-JWPE2WC7-Rq7QKBkn.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-DZ8AStLL.js → chunk-KBJHAD2P-BXh-AI2u.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-DsxjYrzz.js → chunk-RYQCIY6F-BblcLy8m.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-Ddx1KC1l.js → chunk-XXDRQBXY-zHZmVnB6.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-Z6s5QoeI.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-Z6s5QoeI.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-W9TveCnK.js → cose-bilkent-JH36ORCC-Cmwz0Wi4.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-l9j_ztZH.js → cynefin-VYW2F7L2-Dq76MHQU.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-BMJMKi4G.js → cynefinDiagram-MW4NZA55-zPahV1fM.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-Bb4mG9pH.js → dagre-VZM6K2ZE-DQJ3xPc_.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-BMgL_Qv0.js → diagram-7IWD3JNH-BwX7u6fv.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-Bvo2T4OQ.js → diagram-B4RE2ZJO-CodnDCco.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-_5kWRN9c.js → diagram-LBJQPF4R-gAjtWkxG.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-DqdMrltM.js → diagram-Q27KOJAE-CGCqz0cS.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-CDDYsNkp.js → diagram-UB23O5K3-RHwYEywd.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-c4nfnZUV.js → ebnfDiagram-BXEA7PRR-B6seb_p_.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-DnkUcNMs.js → erDiagram-JOGREHBK-CmwCKYe8.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-Dz0KGLZE.js → flowDiagram-UKHOOZJN-BfNsaZ0V.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-Cj1t1uka.js → ganttDiagram-PKOTCBZU-zmYgi_Z3.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-tp2FrBHd.js → gitGraphDiagram-DS77QQ5N-BDEzzSKF.js} +1 -1
- package/dist/worker/console/static/assets/index-DgenUAfc.js +468 -0
- package/dist/worker/console/static/assets/index-aF4u-Y__.css +1 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-DQwJS8DH.js → infoDiagram-6WML65LV-DeSz8l42.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-eimCQWD4.js → ishikawaDiagram-WSZJBQD7-B1LV29aH.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-Dd9Gbwgv.js → journeyDiagram-NVQOT4AX-CCw4W2oU.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-Qve3cyft.js → kanban-definition-27J2QSJJ-BozaMoRJ.js} +1 -1
- package/dist/worker/console/static/assets/{linear-UXKSe36Z.js → linear-G1JR40SX.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-CWhj4JXN.js → mermaid.core-CCgPF0oA.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-yTcwf4SJ.js → mindmap-definition-FAOFIHXS-CEqnCHnD.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-_7qToaZP.js → pegDiagram-VL7TDLO6-DpmQPKuI.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-CXmrKmDm.js → pieDiagram-7S7Q4E2Y-BP501AxR.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-hMmrJyaS.js → quadrantDiagram-CIZ2JOQS-D1dBhl_A.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-BXw8tCBe.js → railroadDiagram-AXF67PYL-DgZDS5pY.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-BmjgnLZl.js → requirementDiagram-LRYGKXZP-BzB3pyIq.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-Bj77SPpN.js → sankeyDiagram-W5VNT64P-B9ZprFFP.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-B2FDVysL.js → sequenceDiagram-SI44F4Z6-CC2-_XrA.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-UChNY9ZZ.js → sizeCapture-X5ZJPWSS-UVtAUqgB.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-wbAEkbd7.js → stateDiagram-OKZ733FA-CWcS5JYG.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-BooX8u1Q.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-DCEkU8HR.js → swimlanes-SLNWSIFB-C4p-dn06.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-D64_eqqE.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-BGp7H06U.js → timeline-definition-Z64GVDOM-Dnz52OHX.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-DsCIQuWR.js → vennDiagram-T6HMQDX7-CzTE0Nfu.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-cRZLP4Xe.js → wardleyDiagram-T6FBY63Y-NAR9cjQQ.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-CP0xLR9Y.js → xychartDiagram-ELKLHX3M-DVMk8413.js} +1 -1
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/chat-view-types.js +1 -1
- package/dist/worker/console/static-src/operator-chat/chat-link.js +94 -0
- package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +142 -7
- package/dist/worker/console/static-src/operator-chat/pending-user-message.js +44 -0
- package/dist/worker/console/static-src/operator-chat/process-label.js +41 -0
- package/dist/worker/console/static-src/operator-chat/tools-catalog.js +21 -2
- package/dist/worker/console/static-src/operator-chat/turn-group-equality.js +13 -0
- package/dist/worker/console/static-src/operator-chat/turn-stream-controller.js +2 -0
- package/dist/worker/console/static-src/operator-chat/use-searchable-hidden.js +51 -0
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +33 -18
- package/dist/worker/console/static-src/operator-chat/useChatStream.js +48 -16
- package/dist/worker/console/static-src/operator-chat/useChatThread.js +170 -7
- package/dist/worker/console/static-src/operator-chat/useComposer.js +13 -7
- package/dist/worker/console/static-src/operator-chat/user-turn-anchor-equality.js +21 -0
- package/dist/worker/console/static-src/shell/workspace-route.js +4 -0
- package/dist/worker/console/workspace-context.js +15 -1
- package/dist/worker/observe/node-transparency.js +81 -72
- package/dist/worker/observe/routes.js +20 -1
- package/dist/worker/observe/static/constants.js +22 -22
- package/dist/worker/observe/static/dag-context-reason-labels.js +19 -0
- package/dist/worker/observe/static/dag-history-labels.js +1 -0
- package/dist/worker/observe/static/dag-inspector-humanize.d.ts +16 -0
- package/dist/worker/observe/static/dag-inspector-humanize.js +342 -0
- package/dist/worker/observe/static/dag-node-purpose.js +10 -10
- package/dist/worker/observe/static/dom.js +20 -1
- package/dist/worker/observe/static/format-pool.d.ts +2 -0
- package/dist/worker/observe/static/format-pool.js +6 -0
- package/dist/worker/observe/static/format.js +7 -0
- package/dist/worker/observe/static/index.html +4 -4
- package/dist/worker/observe/static/inspect-workspace.js +34 -7
- package/dist/worker/observe/static/inspector-submission.js +32 -0
- package/dist/worker/observe/static/kpi.js +1 -0
- package/dist/worker/observe/static/prompt-restart-candidates.js +4 -2
- package/dist/worker/observe/static/relations.js +7 -5
- package/dist/worker/observe/static/router.js +13 -0
- package/dist/worker/observe/static/run-processing.js +2 -0
- package/dist/worker/observe/static/shell-chrome.js +36 -3
- package/dist/worker/observe/static/state.js +35 -2
- package/dist/worker/observe/static/styles.css +431 -39
- package/dist/worker/observe/static/task-failure-labels.d.ts +4 -0
- package/dist/worker/observe/static/task-failure-labels.js +67 -0
- package/dist/worker/observe/static/task-history.js +12 -0
- package/dist/worker/observe/static/views/batch.js +6 -13
- package/dist/worker/observe/static/views/dag-graph.js +51 -4
- package/dist/worker/observe/static/views/dag-inspector.js +1069 -426
- package/dist/worker/observe/static/views/dag-trajectory.js +3 -0
- package/dist/worker/observe/static/views/dag.d.ts +6 -0
- package/dist/worker/observe/static/views/dag.js +64 -22
- package/dist/worker/observe/static/views/dags.js +2 -0
- package/dist/worker/observe/static/views/dashboard.js +21 -12
- package/dist/worker/observe/static/views/failures.js +21 -11
- package/dist/worker/observe/static/views/feature.js +11 -29
- package/dist/worker/observe/static/views/pool.js +37 -28
- package/dist/worker/observe/static/views/run.js +48 -5
- package/dist/worker/observe/static/views/session-timeline.js +194 -243
- package/dist/worker/observe/static/views/task.js +81 -62
- package/dist/workflows/dag/backend-test-plan-protocol.js +104 -0
- package/dist/workflows/dag/budget-enforcement.js +53 -3
- package/dist/workflows/dag/dag-retry-schema.js +3 -0
- package/dist/workflows/dag/frontend-capacity.js +9 -0
- package/dist/workflows/dag/frontend-contract-facts.js +130 -0
- package/dist/workflows/dag/frontend-design-policy.js +103 -17
- package/dist/workflows/dag/frontend-durable-tools.js +193 -0
- package/dist/workflows/dag/frontend-execution-groups.js +24 -0
- package/dist/workflows/dag/frontend-implementation-contract.js +423 -41
- package/dist/workflows/dag/frontend-input-projection.js +76 -0
- package/dist/workflows/dag/frontend-plan-completeness.js +186 -0
- package/dist/workflows/dag/frontend-plan-recovery-policy.js +18 -0
- package/dist/workflows/dag/frontend-plan-render.js +21 -3
- package/dist/workflows/dag/frontend-prewrite-gate.js +1 -1
- package/dist/workflows/dag/frontend-recovery-controller.js +32 -8
- package/dist/workflows/dag/frontend-recovery-lineage.js +13 -0
- package/dist/workflows/dag/frontend-recovery-plan.js +4 -1
- package/dist/workflows/dag/frontend-recovery-run.js +146 -16
- package/dist/workflows/dag/frontend-review-scopes.js +139 -0
- package/dist/workflows/dag/frontend-risk.js +2 -0
- package/dist/workflows/dag/frontend-session-budget.js +249 -0
- package/dist/workflows/dag/frontend-shadow-dual-write.js +37 -3
- package/dist/workflows/dag/frontend-shape-facts.js +12 -2
- package/dist/workflows/dag/frontend-shape.js +46 -7
- package/dist/workflows/dag/frontend-test-execution-evidence.js +49 -0
- package/dist/workflows/dag/frontend-typed-event-store.js +14 -0
- package/dist/workflows/dag/frontend-verification-trace.js +38 -4
- package/dist/workflows/dag/frontend-writer-admission.js +2 -2
- package/dist/workflows/dag/init-hybrid.js +70 -35
- package/dist/workflows/dag/node-execution.js +225 -226
- package/dist/workflows/dag/prompt.js +4 -0
- package/dist/workflows/dag/recovery-lease.js +106 -16
- package/dist/workflows/dag/rerun-feedback.js +1 -0
- package/dist/workflows/dag/rerun-plan.js +96 -4
- package/dist/workflows/dag/rerun-run.js +28 -6
- package/dist/workflows/dag/rerun-task.js +221 -18
- package/dist/workflows/dag/retry-policy.js +27 -10
- package/dist/workflows/dag/runner-exit-diagnostics.js +125 -0
- package/dist/workflows/dag/runner.js +246 -19
- package/dist/workflows/dag/structured-output-repair.js +4 -1
- package/dist/workflows/dag/types.js +10 -3
- package/dist/workflows/dag/validate.js +10 -8
- package/dist/workflows/dag/workspace-checkpoint.js +66 -0
- package/docs/operations/README.md +2 -0
- package/docs/templates/agent-dag.schema.json +2 -2
- package/docs/templates/backend-test-dag.json +6 -4
- package/docs/templates/frontend-design-contract.md +4 -4
- package/docs/templates/frontend-implementation-contract.schema.json +68 -4
- package/docs/templates/frontend-implementation-dag.json +5 -5
- package/harness.json +1 -1
- package/package.json +8 -6
- package/skills/frontend-contract/SKILL.md +2 -1
- package/skills/frontend-contract/references/contract-protocol.md +19 -3
- package/skills/frontend-design-review/SKILL.md +12 -11
- package/skills/frontend-plan/SKILL.md +22 -2
- package/skills/frontend-plan/references/decision-contract.md +114 -5
- package/skills/frontend-plan/references/design-decisions.md +32 -0
- package/skills/frontend-review/SKILL.md +10 -11
- package/skills/frontend-scout/references/scout-evidence.md +4 -0
- package/dist/worker/console/static/assets/channel-DsZxrgqe.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-CuToPeGV.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-CuToPeGV.js +0 -1
- package/dist/worker/console/static/assets/index-D9Sc0f0y.js +0 -449
- package/dist/worker/console/static/assets/index-DDQc5a50.css +0 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-yPoT19ft.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-VWdarchG.js +0 -8
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { sha256OfCanonicalJson } from "../../task/contract/hash.js";
|
|
2
|
+
/** An execution packing target, not a claim about any model's context limit. */
|
|
3
|
+
export const FRONTEND_SCOPE_TARGET_BYTES = 24_000;
|
|
4
|
+
export function packFrontendInputUnits(units, options = {}) {
|
|
5
|
+
const target = options.targetBytes ?? FRONTEND_SCOPE_TARGET_BYTES;
|
|
6
|
+
const maxUnits = options.maxUnits ?? 4;
|
|
7
|
+
if (!Number.isFinite(target) || target <= 0 || !Number.isInteger(maxUnits) || maxUnits < 1)
|
|
8
|
+
throw Error("FRONTEND_INPUT_POLICY_INVALID");
|
|
9
|
+
const batches = [];
|
|
10
|
+
let batch = [];
|
|
11
|
+
let bytes = 2;
|
|
12
|
+
let cost = 0;
|
|
13
|
+
const ids = new Set();
|
|
14
|
+
for (const unit of units) {
|
|
15
|
+
if (!unit.id || ids.has(unit.id))
|
|
16
|
+
throw Error(`FRONTEND_INPUT_ID_CONFLICT: ${unit.id}`);
|
|
17
|
+
ids.add(unit.id);
|
|
18
|
+
const size = Buffer.byteLength(JSON.stringify(unit), "utf8") + 1;
|
|
19
|
+
if (options.maxUnitBytes !== undefined && size > options.maxUnitBytes)
|
|
20
|
+
throw Error(`FRONTEND_INPUT_UNIT_TOO_LARGE: ${unit.id} requires ${size} bytes; split the source obligation without dropping conditions`);
|
|
21
|
+
const unitCost = options.cost?.(unit) ?? 1;
|
|
22
|
+
if (batch.length && (batch.length >= maxUnits || bytes + size > target || cost + unitCost > (options.maxCost ?? Infinity))) {
|
|
23
|
+
batches.push(batch);
|
|
24
|
+
batch = [];
|
|
25
|
+
bytes = 2;
|
|
26
|
+
cost = 0;
|
|
27
|
+
}
|
|
28
|
+
batch.push(unit);
|
|
29
|
+
bytes += size;
|
|
30
|
+
cost += unitCost;
|
|
31
|
+
}
|
|
32
|
+
if (batch.length)
|
|
33
|
+
batches.push(batch);
|
|
34
|
+
return batches;
|
|
35
|
+
}
|
|
36
|
+
/** Select whole obligations before serialization; never clip identities or text. */
|
|
37
|
+
export function projectFrontendInputScope(payload, scopeIds) {
|
|
38
|
+
const byId = new Map(payload.requirements.map(r => [r.id, r]));
|
|
39
|
+
const requirements = [...new Set(scopeIds)].map(id => {
|
|
40
|
+
const unit = byId.get(id);
|
|
41
|
+
if (!unit)
|
|
42
|
+
throw Error(`FRONTEND_INPUT_SCOPE_MISSING: ${id}`);
|
|
43
|
+
return unit;
|
|
44
|
+
});
|
|
45
|
+
const fragments = new Set(requirements.flatMap(r => Array.isArray(r.sourceFragmentIds) ? r.sourceFragmentIds : []));
|
|
46
|
+
const inScope = (fact) => {
|
|
47
|
+
const ids = typeof fact.requirementId === "string" ? [fact.requirementId] : Array.isArray(fact.requirementIds) ? fact.requirementIds : [];
|
|
48
|
+
return !ids.length || ids.some(id => scopeIds.includes(String(id)));
|
|
49
|
+
};
|
|
50
|
+
return {
|
|
51
|
+
...payload, requirements,
|
|
52
|
+
...(Array.isArray(payload.sharedFacts) ? { sharedFacts: payload.sharedFacts.filter(f => f && typeof f === "object" && inScope(f)).map(f => f.kind === "required-deliverables" && Array.isArray(f.items) ? { ...f, items: f.items.filter((item) => inScope(item)) } : f) } : {}),
|
|
53
|
+
...(Array.isArray(payload.fragments) ? { fragments: payload.fragments.filter(f => f && typeof f === "object" && fragments.has(f.id)) } : {}),
|
|
54
|
+
inputManifest: { schemaVersion: 1, semantics: "full", sourceSha256: sha256OfCanonicalJson(payload), scopeIds: requirements.map(r => r.id), excludedIds: payload.requirements.filter(r => !scopeIds.includes(r.id)).map(r => r.id) },
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
export function parseFrontendInputBlock(prompt, phase) {
|
|
58
|
+
const block = new RegExp(`<frontend_${phase}_input>[\\s\\S]*?<\\/frontend_${phase}_input>`).exec(prompt)?.[0];
|
|
59
|
+
if (!block)
|
|
60
|
+
return undefined;
|
|
61
|
+
for (const line of block.split(/\r?\n/)) {
|
|
62
|
+
try {
|
|
63
|
+
const payload = JSON.parse(line);
|
|
64
|
+
if (Array.isArray(payload.requirements))
|
|
65
|
+
return { block, payload };
|
|
66
|
+
}
|
|
67
|
+
catch { /* wrapper prose is not JSON */ }
|
|
68
|
+
}
|
|
69
|
+
throw Error(`FRONTEND_INPUT_INVALID: ${phase} compiled inventory is not parseable`);
|
|
70
|
+
}
|
|
71
|
+
export function projectFrontendContractPrompt(prompt, scopeIds) {
|
|
72
|
+
const input = parseFrontendInputBlock(prompt, "contract");
|
|
73
|
+
if (!input)
|
|
74
|
+
throw Error("FRONTEND_INPUT_MISSING: contract compiled inventory unavailable");
|
|
75
|
+
return prompt.replace(input.block, ["<frontend_contract_input>", "Complete canonical obligations for this session only. Confirm each id with record_requirement; preserve all independent conditions.", JSON.stringify(projectFrontendInputScope(input.payload, scopeIds)), "</frontend_contract_input>"].join("\n"));
|
|
76
|
+
}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { collectCanonicalStateFlowNames } from "./frontend-contract-facts.js";
|
|
2
|
+
export function committedFactFromPlanRecord(value) {
|
|
3
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
4
|
+
return undefined;
|
|
5
|
+
const record = value;
|
|
6
|
+
if (record.phase !== undefined && record.phase !== "committed")
|
|
7
|
+
return undefined;
|
|
8
|
+
const fact = record.fact;
|
|
9
|
+
return fact && typeof fact === "object" && !Array.isArray(fact)
|
|
10
|
+
? fact
|
|
11
|
+
: typeof record.kind === "string"
|
|
12
|
+
? record
|
|
13
|
+
: undefined;
|
|
14
|
+
}
|
|
15
|
+
export function planFactStringList(value) {
|
|
16
|
+
if (!Array.isArray(value))
|
|
17
|
+
return [];
|
|
18
|
+
return value.filter((item) => typeof item === "string" && item.trim().length > 0);
|
|
19
|
+
}
|
|
20
|
+
export function planFactScopeIntersects(fact, requirementIds) {
|
|
21
|
+
return planFactStringList(fact.scopeRequirementIds).some((id) => requirementIds.has(id));
|
|
22
|
+
}
|
|
23
|
+
/** Compute the authoritative coverage queue from the committed plan ledger. */
|
|
24
|
+
export function collectFrontendPlanMissingFacts(input) {
|
|
25
|
+
const requirements = new Map();
|
|
26
|
+
const standaloneEvidenceGaps = new Set();
|
|
27
|
+
const verificationTargetIds = new Set();
|
|
28
|
+
const verificationTargetRequirements = new Map();
|
|
29
|
+
for (const value of input.committedFacts) {
|
|
30
|
+
const fact = committedFactFromPlanRecord(value);
|
|
31
|
+
if (!fact || fact.origin !== "plan")
|
|
32
|
+
continue;
|
|
33
|
+
if (fact.kind === "plan-requirement" && fact.entry && typeof fact.entry === "object") {
|
|
34
|
+
const entry = fact.entry;
|
|
35
|
+
if (typeof entry.id === "string" && entry.id.trim())
|
|
36
|
+
requirements.set(entry.id, entry);
|
|
37
|
+
}
|
|
38
|
+
if (fact.kind === "plan-verification-target" && fact.entry && typeof fact.entry === "object") {
|
|
39
|
+
const entry = fact.entry;
|
|
40
|
+
const id = entry.id;
|
|
41
|
+
if (typeof id === "string" && id.trim()) {
|
|
42
|
+
verificationTargetIds.add(id);
|
|
43
|
+
verificationTargetRequirements.set(id, new Set(Array.isArray(entry.requirementIds)
|
|
44
|
+
? entry.requirementIds.filter((value) => typeof value === "string")
|
|
45
|
+
: []));
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
if (fact.kind === "plan-evidence-gap" && fact.entry && typeof fact.entry === "object") {
|
|
49
|
+
const entry = fact.entry;
|
|
50
|
+
const requirementId = entry.requirementId;
|
|
51
|
+
const description = entry.description;
|
|
52
|
+
if (typeof requirementId === "string" && requirementId.trim() && typeof description === "string" && description.trim()) {
|
|
53
|
+
standaloneEvidenceGaps.add(requirementId);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
const missing = [];
|
|
58
|
+
for (const id of input.requirementIds) {
|
|
59
|
+
const entry = requirements.get(id);
|
|
60
|
+
if (!entry) {
|
|
61
|
+
missing.push({
|
|
62
|
+
kind: "plan-requirement",
|
|
63
|
+
id,
|
|
64
|
+
requirementIds: [id],
|
|
65
|
+
reason: `requirement ${id} has no committed plan-requirement fact`,
|
|
66
|
+
});
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
const targetIds = Array.isArray(entry.verificationTargetIds)
|
|
70
|
+
? entry.verificationTargetIds.filter((value) => typeof value === "string" && value.trim().length > 0)
|
|
71
|
+
: [];
|
|
72
|
+
const gap = entry.evidenceGap && typeof entry.evidenceGap === "object"
|
|
73
|
+
? entry.evidenceGap
|
|
74
|
+
: undefined;
|
|
75
|
+
const hasEvidenceGap = (typeof gap?.description === "string" && gap.description.trim().length > 0) ||
|
|
76
|
+
standaloneEvidenceGaps.has(id);
|
|
77
|
+
if (targetIds.length === 0 && !hasEvidenceGap) {
|
|
78
|
+
missing.push({
|
|
79
|
+
kind: "plan-verification-target",
|
|
80
|
+
requirementIds: [id],
|
|
81
|
+
reason: `requirement ${id} declares neither a verification target nor a non-empty evidenceGap`,
|
|
82
|
+
});
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
for (const targetId of targetIds) {
|
|
86
|
+
if (!verificationTargetIds.has(targetId) ||
|
|
87
|
+
!verificationTargetRequirements.get(targetId)?.has(id)) {
|
|
88
|
+
missing.push({
|
|
89
|
+
kind: "plan-verification-target",
|
|
90
|
+
id: targetId,
|
|
91
|
+
requirementIds: [id],
|
|
92
|
+
reason: `requirement ${id} references verification target ${targetId}, but that target is not committed`,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return missing;
|
|
98
|
+
}
|
|
99
|
+
/** Completeness checks for phases whose facts are committed incrementally. */
|
|
100
|
+
export function collectFrontendPlanPhaseMissingFacts(input) {
|
|
101
|
+
const facts = input.committedFacts
|
|
102
|
+
.map(committedFactFromPlanRecord)
|
|
103
|
+
.filter((fact) => Boolean(fact && fact.origin === "plan"));
|
|
104
|
+
if (input.phase === "ux-registry") {
|
|
105
|
+
return facts.some((fact) => fact.kind === "state-registry")
|
|
106
|
+
? []
|
|
107
|
+
: [
|
|
108
|
+
{
|
|
109
|
+
kind: "state-registry",
|
|
110
|
+
requirementIds: [...input.requirementIds],
|
|
111
|
+
reason: "global UX vocabulary phase has no committed state-registry fact",
|
|
112
|
+
},
|
|
113
|
+
];
|
|
114
|
+
}
|
|
115
|
+
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
|
+
const missing = [];
|
|
133
|
+
for (const id of input.requirementIds) {
|
|
134
|
+
if (!required.has(id))
|
|
135
|
+
continue;
|
|
136
|
+
const scopedFacts = byRequirement.get(id) ?? [];
|
|
137
|
+
const hasChoice = scopedFacts.some((fact) => fact.kind === "component-choice" &&
|
|
138
|
+
Array.isArray(fact.uiComponentChoices) &&
|
|
139
|
+
fact.uiComponentChoices.length > 0);
|
|
140
|
+
const canonicalStateFlow = collectCanonicalStateFlowNames(scopedFacts);
|
|
141
|
+
const hasStateFlow = [...canonicalStateFlow.uiStateNames].some(name => liveStateFlow.uiStateNames.has(name)) ||
|
|
142
|
+
[...canonicalStateFlow.interactionNames].some(name => liveStateFlow.interactionNames.has(name));
|
|
143
|
+
if (!hasChoice) {
|
|
144
|
+
missing.push({
|
|
145
|
+
kind: "component-choice",
|
|
146
|
+
requirementIds: [id],
|
|
147
|
+
reason: "behaviour-required UX slice has no committed component-choice fact",
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
if (!hasStateFlow) {
|
|
151
|
+
missing.push({
|
|
152
|
+
kind: "state-flow",
|
|
153
|
+
requirementIds: [id],
|
|
154
|
+
reason: "behaviour-required UX slice has no committed state-flow fact",
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return missing;
|
|
159
|
+
}
|
|
160
|
+
const hasMockApi = facts.some((fact) => fact.kind === "mock-api");
|
|
161
|
+
const allInteractions = collectCanonicalStateFlowNames(facts).interactionNames;
|
|
162
|
+
const requested = new Set(input.requirementIds);
|
|
163
|
+
const liveInteractions = new Set([...collectCanonicalStateFlowNames(facts.filter(f => !planFactStringList(f.scopeRequirementIds).length || planFactScopeIntersects(f, requested))).interactionNames].filter(name => allInteractions.has(name)));
|
|
164
|
+
const coveredInteractions = new Set(facts
|
|
165
|
+
.filter((fact) => fact.kind === "data-flow")
|
|
166
|
+
.flatMap((fact) => planFactStringList(fact.interactions)));
|
|
167
|
+
const missing = [];
|
|
168
|
+
if (!hasMockApi) {
|
|
169
|
+
missing.push({
|
|
170
|
+
kind: "mock-api",
|
|
171
|
+
requirementIds: [...input.requirementIds],
|
|
172
|
+
reason: "global Mock/data phase has no committed mock-api fact",
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
for (const interaction of liveInteractions) {
|
|
176
|
+
if (coveredInteractions.has(interaction))
|
|
177
|
+
continue;
|
|
178
|
+
missing.push({
|
|
179
|
+
kind: "data-flow",
|
|
180
|
+
id: interaction,
|
|
181
|
+
requirementIds: [...input.requirementIds],
|
|
182
|
+
reason: `interaction ${interaction} has no committed data-flow fact`,
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
return missing;
|
|
186
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/** Precise failures own their recovery. A stale transport length marker must
|
|
2
|
+
* never turn cancellation, provider rejection or policy failure into progress. */
|
|
3
|
+
export function classifyFrontendPlanRecovery(result) {
|
|
4
|
+
const category = result.failureCategory ?? "";
|
|
5
|
+
if (result.timedOut || result.stopReason === "aborted" ||
|
|
6
|
+
/interrupt|termination-unconfirmed|budget_breach/.test(category) ||
|
|
7
|
+
["cancelled", "auth", "missing-api-key", "rate-limit", "quota", "timeout", "tool-policy", "write-guard", "path-violation", "governance-blocked", "human-rejected"].includes(category))
|
|
8
|
+
return "stop";
|
|
9
|
+
if (result.ok)
|
|
10
|
+
return "none";
|
|
11
|
+
if (category === "output-limit")
|
|
12
|
+
return "output";
|
|
13
|
+
if (["context-overflow", "context-budget-exhausted"].includes(category))
|
|
14
|
+
return "context";
|
|
15
|
+
if (result.stopReason === "length" && ["", "empty-output", "network", "nonzero-exit", "unknown"].includes(category))
|
|
16
|
+
return "output";
|
|
17
|
+
return "none";
|
|
18
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { collectFrontendExecutionGroups } from "./frontend-execution-groups.js";
|
|
1
2
|
/**
|
|
2
3
|
* Deterministic Markdown renderer for a canonical frontend implementation
|
|
3
4
|
* contract. The prewrite gate materializes this as `contracts/frontend-plan.md`
|
|
@@ -12,8 +13,25 @@ function bulletList(items) {
|
|
|
12
13
|
return "";
|
|
13
14
|
return items.map((item) => `- ${item}`).join("\n");
|
|
14
15
|
}
|
|
16
|
+
/** The runtime's deterministic fallback for an outcome the plan did not
|
|
17
|
+
* author. It is bookkeeping for the schema's non-empty constraint, not user
|
|
18
|
+
* prose — the renderer must never present it as requirement content. */
|
|
19
|
+
const IMPLIED_OUTCOME_PREFIX = "implied by contract:";
|
|
15
20
|
function requirementsCoverage(contract) {
|
|
16
|
-
const
|
|
21
|
+
const renderRequirement = (requirement, includeOutcome = true) => {
|
|
22
|
+
const targets = requirement.implementationTargets.join(", ");
|
|
23
|
+
const verificationTargets = requirement.verificationTargetIds.join(", ");
|
|
24
|
+
const authoredOutcome = !includeOutcome || requirement.expectedOutcome.startsWith(IMPLIED_OUTCOME_PREFIX)
|
|
25
|
+
? ""
|
|
26
|
+
: ` — ${requirement.expectedOutcome}`;
|
|
27
|
+
return `- ${requirement.id} → ${targets || "(no targets)"} [${verificationTargets || "no VT"}]${authoredOutcome}`;
|
|
28
|
+
};
|
|
29
|
+
const lines = collectFrontendExecutionGroups(contract.requirements).flatMap(group => {
|
|
30
|
+
const members = contract.requirements.filter(r => group.requirementIds.includes(r.id));
|
|
31
|
+
if (group.kind === "unclassified")
|
|
32
|
+
return members.map(r => renderRequirement(r));
|
|
33
|
+
return [`### ${group.id} (${group.kind})`, group.summary, ...members.map(r => renderRequirement(r, false)), "Full independent acceptance conditions remain in contracts/frontend-implementation-contract.json."];
|
|
34
|
+
});
|
|
17
35
|
return lines.length > 0 ? lines.join("\n") : "_(no requirements)_";
|
|
18
36
|
}
|
|
19
37
|
function uiStateHandling(contract) {
|
|
@@ -61,7 +79,7 @@ function sourceFidelityLedger(contract) {
|
|
|
61
79
|
`- ledger: ${binding.ledgerPath}`,
|
|
62
80
|
`- ledgerSha256: ${binding.ledgerSha256 ?? "(missing)"}`,
|
|
63
81
|
`- inputDigest: ${binding.inputDigest ?? "(missing)"}`,
|
|
64
|
-
"- 语义权威:原始 requirement/acceptance 文档与 requirement-ledger.json
|
|
82
|
+
"- 语义权威:原始 requirement/acceptance 文档与 requirement-ledger.json;本计划按执行组展示共享方案;独立验收全文保留在 canonical contract,执行输入按完整义务分批。",
|
|
65
83
|
];
|
|
66
84
|
const boundCount = Object.keys(binding.requirementToFragments ?? {}).length;
|
|
67
85
|
if (boundCount > 0) {
|
|
@@ -98,7 +116,7 @@ export function renderFrontendPlanMarkdown(contract) {
|
|
|
98
116
|
interactionNotes(contract),
|
|
99
117
|
"",
|
|
100
118
|
"## Mock & API Strategy",
|
|
101
|
-
contract.mockApi
|
|
119
|
+
contract.mockApi?.strategy || "_(not specified)_",
|
|
102
120
|
"",
|
|
103
121
|
"## Dependency Policy",
|
|
104
122
|
contract.dependencyPolicy || "_(not specified)_",
|
|
@@ -734,7 +734,7 @@ export async function runFrontendPrewriteGate(input) {
|
|
|
734
734
|
failureCode: "target-outside-write-set",
|
|
735
735
|
});
|
|
736
736
|
}
|
|
737
|
-
const nonStaticVTs = analysis.canonical.verificationTargets.filter((vt) => vt.
|
|
737
|
+
const nonStaticVTs = analysis.canonical.verificationTargets.filter((vt) => vt.mode !== "static");
|
|
738
738
|
const uncoveredVTs = nonStaticVTs.filter((vt) => ![...writeSet].some((pattern) => pathMatchesPattern(vt.file, pattern) || vt.file === pattern));
|
|
739
739
|
if (uncoveredVTs.length > 0) {
|
|
740
740
|
return finalizePrewrite(input, {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { setTimeout as delay } from "node:timers/promises";
|
|
1
2
|
import { isTerminalOperationState, } from "../../infrastructure/console/operation-store.js";
|
|
2
3
|
import { runOperation, } from "../../worker/console/operation-runner.js";
|
|
3
4
|
import { buildFailureCapsule, classifyFailureOwner, projectProtocolFailureReason, } from "./frontend-recovery-capsule.js";
|
|
@@ -78,7 +79,13 @@ export async function runRecoveryOperationController(input) {
|
|
|
78
79
|
const readChangedFiles = deps.readChangedFiles ?? (async () => []);
|
|
79
80
|
const fingerprints = [...(deps.previousFingerprints ?? [])];
|
|
80
81
|
let budgetAggregate;
|
|
81
|
-
|
|
82
|
+
const pollIntervalMs = Math.max(10, deps.operationPollIntervalMs ?? 1_000);
|
|
83
|
+
const waitTimeoutMs = Math.max(pollIntervalMs, deps.operationWaitTimeoutMs ??
|
|
84
|
+
deps.operationRunnerDeps.defaultTimeoutMs ??
|
|
85
|
+
30 * 60_000);
|
|
86
|
+
const waitStartedAt = Date.now();
|
|
87
|
+
let attempt = 1;
|
|
88
|
+
while (attempt <= maxAttempts) {
|
|
82
89
|
const operation = await deps.store.get(operationId);
|
|
83
90
|
if (!operation) {
|
|
84
91
|
return {
|
|
@@ -89,13 +96,13 @@ export async function runRecoveryOperationController(input) {
|
|
|
89
96
|
};
|
|
90
97
|
}
|
|
91
98
|
// Total budget fold + hard stop (resource protection, fail-closed).
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
ledgers,
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
}
|
|
99
|
+
try {
|
|
100
|
+
const ledgers = await readLedgers();
|
|
101
|
+
if (deps.budget)
|
|
102
|
+
budgetAggregate = foldOperationBudgets({ ledgers, budget: deps.budget, now });
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
return { terminalReason: "needs-attention", attempts: attempt, budgetAggregate, fingerprints, detail: `Recovery budget evidence unavailable: ${error instanceof Error ? error.message : String(error)}` };
|
|
99
106
|
}
|
|
100
107
|
if (budgetAggregate?.status === "breached") {
|
|
101
108
|
return {
|
|
@@ -190,7 +197,24 @@ export async function runRecoveryOperationController(input) {
|
|
|
190
197
|
detail: `interrupt projection: ${projected}`,
|
|
191
198
|
};
|
|
192
199
|
}
|
|
200
|
+
const operationAlreadyInFlight = operation.state === "starting" || operation.state === "running";
|
|
201
|
+
const operationOwnedElsewhere = input.observeOnly &&
|
|
202
|
+
(operation.state === "queued" || operation.state === "needs-reconcile");
|
|
203
|
+
if (operationAlreadyInFlight || operationOwnedElsewhere) {
|
|
204
|
+
if (Date.now() - waitStartedAt >= waitTimeoutMs) {
|
|
205
|
+
return {
|
|
206
|
+
terminalReason: "needs-attention",
|
|
207
|
+
attempts: attempt,
|
|
208
|
+
budgetAggregate,
|
|
209
|
+
fingerprints,
|
|
210
|
+
detail: `recovery operation did not settle within ${waitTimeoutMs}ms`,
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
await delay(pollIntervalMs);
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
193
216
|
await runOperation(operationId, deps.operationRunnerDeps);
|
|
217
|
+
attempt += 1;
|
|
194
218
|
}
|
|
195
219
|
return {
|
|
196
220
|
terminalReason: "max-attempts",
|
|
@@ -87,6 +87,7 @@ export function foldOperationBudgets(input) {
|
|
|
87
87
|
limits: { ...input.budget.limits },
|
|
88
88
|
consumed: {
|
|
89
89
|
executorCalls: 0,
|
|
90
|
+
...(input.budget.limits.maxProviderRequests !== undefined ? { providerRequests: 0 } : {}),
|
|
90
91
|
wallTimeMs: 0,
|
|
91
92
|
tokens: null,
|
|
92
93
|
repairPasses: 0,
|
|
@@ -100,6 +101,15 @@ export function foldOperationBudgets(input) {
|
|
|
100
101
|
let knownTokenSum = 0;
|
|
101
102
|
const missing = new Set();
|
|
102
103
|
for (const ledger of input.ledgers) {
|
|
104
|
+
if (input.budget.limits.maxProviderRequests !== undefined) {
|
|
105
|
+
if (ledger.consumed.providerRequests === undefined) {
|
|
106
|
+
if (input.budget.mode === "hard")
|
|
107
|
+
throw Error("FRONTEND_PROVIDER_BUDGET_INVALID: missing operation request consumption");
|
|
108
|
+
delete aggregate.consumed.providerRequests;
|
|
109
|
+
}
|
|
110
|
+
else if (aggregate.consumed.providerRequests !== undefined)
|
|
111
|
+
aggregate.consumed.providerRequests += ledger.consumed.providerRequests;
|
|
112
|
+
}
|
|
103
113
|
aggregate.consumed.executorCalls += ledger.consumed.executorCalls;
|
|
104
114
|
aggregate.consumed.wallTimeMs += ledger.consumed.wallTimeMs;
|
|
105
115
|
aggregate.consumed.repairPasses += ledger.consumed.repairPasses;
|
|
@@ -119,6 +129,9 @@ export function foldOperationBudgets(input) {
|
|
|
119
129
|
aggregate.consumed.missingTokenNodeIds = [...missing].sort((a, b) => a.localeCompare(b));
|
|
120
130
|
if (aggregate.mode === "hard") {
|
|
121
131
|
const limits = aggregate.limits;
|
|
132
|
+
pushAggregateBreach(aggregate, "providerRequests", aggregate.consumed.providerRequests ?? 0, limits.maxProviderRequests, now);
|
|
133
|
+
if (aggregate.breaches.length)
|
|
134
|
+
aggregate.status = "breached";
|
|
122
135
|
pushAggregateBreach(aggregate, "executorCalls", aggregate.consumed.executorCalls, limits.maxExecutorCalls, now);
|
|
123
136
|
pushAggregateBreach(aggregate, "wallTimeMs", aggregate.consumed.wallTimeMs, limits.maxWallTimeMs, now);
|
|
124
137
|
pushAggregateBreach(aggregate, "repairPasses", aggregate.consumed.repairPasses, limits.maxRepairPasses, now);
|
|
@@ -27,6 +27,7 @@ export function deriveFrontendRecoveryFailureSource(result) {
|
|
|
27
27
|
* - `frontend-plan-pi` failure → reset plan + design-review + policy/admission (+ descendants);
|
|
28
28
|
* - `frontend-design-policy-shell` failure → reset policy/admission (+ descendants);
|
|
29
29
|
* - design-review rejection → reset plan + design review + admission (+ descendants);
|
|
30
|
+
* - final-review rejection → reset implementation + verification/review (+ descendants);
|
|
30
31
|
* - other admission failures → reset writer admission (+ descendants);
|
|
31
32
|
*
|
|
32
33
|
* Everything upstream of the failure source is imported as verified parent facts.
|
|
@@ -62,7 +63,9 @@ function computeFrontendRecoveryPlanForSource(input) {
|
|
|
62
63
|
// reset from implement: the recovery re-implements and re-verifies.
|
|
63
64
|
const resetRoot = input.failureSource === "frontend-verify-shell"
|
|
64
65
|
? "frontend-implement-pi"
|
|
65
|
-
: input.failureSource
|
|
66
|
+
: input.failureSource === "frontend-review-pi"
|
|
67
|
+
? "frontend-implement-pi"
|
|
68
|
+
: input.failureSource;
|
|
66
69
|
const resetNodeIds = computeResetClosure(input.spec, resetRoot);
|
|
67
70
|
const resetSet = new Set(resetNodeIds);
|
|
68
71
|
const importedNodeIds = input.spec.tasks
|