@tea-agent/loop-agent 0.44.0-next.7 → 0.44.0-next.9
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 +100 -0
- 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/{workflows/dag/frontend-plan-completeness.js → executors/dag-pi/plan/facts.js} +25 -41
- package/dist/executors/dag-pi/plan/ledger-contract.js +55 -0
- package/dist/executors/dag-pi/tools/contract-tools.js +515 -0
- package/dist/executors/dag-pi/tools/design-terminal-tools.js +166 -0
- package/dist/executors/dag-pi/tools/plan-decision-tools.js +519 -0
- package/dist/executors/dag-pi/tools/review-terminal-tools.js +166 -0
- package/dist/executors/dag-pi/tools/scout-evidence-tools.js +270 -0
- package/dist/executors/dag-pi-executor.js +198 -118
- package/dist/executors/pi-executor.js +6 -1
- package/dist/executors/pi-sdk-executor.js +76 -11
- package/dist/executors/shell-executor.js +88 -12
- package/dist/infrastructure/console/operation-store.js +2 -0
- package/dist/infrastructure/evaluation/corpus-store.js +37 -12
- package/dist/shared/operator/capabilities.js +10 -5
- package/dist/task/config-types.js +22 -9
- package/dist/task/contract/project.js +13 -0
- package/dist/task/contract/schema.js +2 -8
- 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/operator-actions.js +0 -1
- package/dist/worker/console/operator-user-error.js +1 -1
- package/dist/worker/console/prd-intake-bridge.js +5 -15
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-DTuku_-7.js → abnfDiagram-N423BO3Z-r1NAPU_5.js} +1 -1
- package/dist/worker/console/static/assets/{arc-CQbCsSqb.js → arc-dT5T4sVL.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-C-xUntTR.js → architectureDiagram-T3A2C74G-CP_cOq46.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-DhNlwGAl.js → blockDiagram-VBNYF7ZC-sToEZxEf.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-B7j3nx8o.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-BCrcxEh2.js → chunk-2GRJ4B5K-B9fh0IkF.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-CsaePDb3.js → chunk-2Q5K7J3B-CAUNl-A6.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H--SRQc1rN.js → chunk-5RXB4S5H-BomeqbNX.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-C5BFY2Po.js → chunk-5VM5RSS4-BNAuTOLE.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-Caso6uN7.js → chunk-6Q2QTUOP-DfzjRD5y.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-AY2ZSAx2.js → chunk-GF5L2VYU-C3uE-yc2.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-DiMqZQ_8.js → chunk-JWPE2WC7-C3sQMMYr.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-gpvvriOp.js → chunk-KBJHAD2P-D_AtEYQL.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-CUdyoJhI.js → chunk-RYQCIY6F-DW4TwhKv.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-Bp1xW3T4.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-D-JIcOIT.js → cose-bilkent-JH36ORCC-iMqPuBsL.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-C-NKFflx.js → cynefin-VYW2F7L2-DktIuDWs.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-CSOcvOAA.js → cynefinDiagram-MW4NZA55-BgVnjyEs.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-BQSBltMy.js → dagre-VZM6K2ZE-CR25E4bu.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-B0LVC45G.js → diagram-7IWD3JNH-CPM2h5HY.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-Cypg3nqy.js → diagram-B4RE2ZJO-DqF0x2g1.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-BBDU88XJ.js → diagram-LBJQPF4R-G8BYp2eb.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-BZusLqSb.js → diagram-Q27KOJAE-BbC4kT_D.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-Cfolotcr.js → diagram-UB23O5K3-CKZwZ-G5.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-CjfyUy0m.js → ebnfDiagram-BXEA7PRR-kJDmheia.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-DJdz7txE.js → erDiagram-JOGREHBK-DFTVHyxB.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-CQqgPO6y.js → flowDiagram-UKHOOZJN-BqdN5_os.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-BOpGfS2m.js → ganttDiagram-PKOTCBZU-SYDjVakh.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-C6UCu0qx.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-DWKejNVJ.js → index-CTMZsDmh.js} +98 -98
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-BWeaVEUB.js → infoDiagram-6WML65LV-DIeAmJ45.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-C8gmlBbk.js → ishikawaDiagram-WSZJBQD7-oikqUAlo.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-DF3bm8Bp.js → journeyDiagram-NVQOT4AX-mKEF_xYT.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-D2fG2Vxi.js → kanban-definition-27J2QSJJ-BdWJHPnN.js} +1 -1
- package/dist/worker/console/static/assets/{linear-BGKgMc2T.js → linear-DGRlieMH.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-DF6mMPJ9.js → mermaid.core-4P9yPC9c.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-AMhePheC.js → mindmap-definition-FAOFIHXS-5PTSkos6.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-DN1Cl0W0.js → pegDiagram-VL7TDLO6-DONIFpG5.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-Cx9ngUeN.js → pieDiagram-7S7Q4E2Y-D_Z4S70M.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-BcYPTQJb.js → quadrantDiagram-CIZ2JOQS-CAsjh8iY.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-BqHUZWnH.js → railroadDiagram-AXF67PYL-Cx4Dd6ac.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-CmqWZ0-B.js → requirementDiagram-LRYGKXZP-cQb7mwyH.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P--4YetH1y.js → sankeyDiagram-W5VNT64P-Cd-DhUdf.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-DGD4VMfD.js → sequenceDiagram-SI44F4Z6-AlKYRgIk.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-QVw9q7rh.js → sizeCapture-X5ZJPWSS-CUy_ET4e.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-CuemV9pe.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-C7oKzZKs.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-C2Of60nT.js → timeline-definition-Z64GVDOM-DZZeiAPr.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-DHtpQHyL.js → vennDiagram-T6HMQDX7-t4O91cZU.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-COi9OhSy.js → wardleyDiagram-T6FBY63Y-CV5qGHZu.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-DPHQfRrt.js → xychartDiagram-ELKLHX3M-DyXbytUI.js} +1 -1
- package/dist/worker/console/static/index.html +2 -2
- package/dist/workflows/dag/budget-enforcement.js +31 -2
- 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 -59
- 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 +11 -10
- 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 -6
- package/dist/workflows/dag/frontend-verification-trace.js +6 -0
- package/dist/workflows/dag/frontend-writer-admission.js +2 -1
- package/dist/workflows/dag/init-hybrid.js +108 -37
- package/dist/workflows/dag/node-execution.js +11 -11
- package/dist/workflows/dag/rerun-feedback.js +35 -4
- package/dist/workflows/dag/rerun-task.js +62 -67
- package/dist/workflows/dag/runner.js +58 -12
- package/dist/workflows/dag/types.js +15 -8
- package/docs/architecture/runtime-boundaries.md +1 -1
- package/docs/templates/backend-test-dag.json +4 -4
- package/docs/templates/frontend-implementation-contract.schema.json +31 -11
- package/package.json +1 -1
- 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/dist/worker/console/static/assets/channel-5_os7t_x.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-loz0n18W.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-loz0n18W.js +0 -1
- package/dist/worker/console/static/assets/index-CUT440A8.css +0 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-BdlGdIJw.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-DjjbHbde.js +0 -8
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { reviewFindingSchema } from "../workflows/dag/frontend-typed-event-store.js";
|
|
2
|
+
import { authorizeRepairAssertions } from "../workflows/dag/frontend-repair-assertions.js";
|
|
3
|
+
import { createFrontendPlanProgressGuard } from "../workflows/dag/frontend-plan-progress.js";
|
|
1
4
|
import { classifyFrontendPlanRecovery } from "../workflows/dag/frontend-plan-recovery-policy.js";
|
|
2
5
|
import { collectFrontendExecutionGroups, frontendExecutionSchema } from "../workflows/dag/frontend-execution-groups.js";
|
|
3
6
|
import { FRONTEND_SCOPE_TARGET_BYTES, packFrontendInputUnits, parseFrontendInputBlock, projectFrontendContractPrompt, projectFrontendInputScope } from "../workflows/dag/frontend-input-projection.js";
|
|
@@ -5,7 +8,7 @@ import { createDurableFrontendTools } from "../workflows/dag/frontend-durable-to
|
|
|
5
8
|
import { sha256OfCanonicalJson } from "../task/contract/hash.js";
|
|
6
9
|
import { z } from "zod";
|
|
7
10
|
import { createFrontendReviewScopeProtocol, loadFrontendReviewScopes } from "../workflows/dag/frontend-review-scopes.js";
|
|
8
|
-
import { observeFrontendSession } from "../workflows/dag/frontend-session-budget.js";
|
|
11
|
+
import { observeFrontendFinalization, observeFrontendSession } from "../workflows/dag/frontend-session-budget.js";
|
|
9
12
|
import path from "node:path";
|
|
10
13
|
import { createHash, randomUUID } from "node:crypto";
|
|
11
14
|
import { readFile, stat } from "node:fs/promises";
|
|
@@ -852,6 +855,7 @@ export async function createFrontendReviewTerminalTools(input) {
|
|
|
852
855
|
line: Type.Optional(Type.Integer({ minimum: 1 })),
|
|
853
856
|
issue: Type.String({ minLength: 1 }),
|
|
854
857
|
requiredChange: Type.Optional(Type.String({ minLength: 1 })),
|
|
858
|
+
repairAssertions: Type.Optional(Type.Unknown({ description: "Optional mechanical checks: file-absent/state-present {check,token,source}; vt-changed also requires expectedRequirementIds. Invalid items retain diagnostics and do not remove this finding." })),
|
|
855
859
|
}, { additionalProperties: false });
|
|
856
860
|
const scopeProtocol = createFrontendReviewScopeProtocol({ phase: "review", inventory: input.inventory, getStore: () => store, attemptId });
|
|
857
861
|
const savedFindings = () => [...new Map(readCommittedEvents(store, attemptId).filter(r => r.fact.kind === "review-finding").map(r => [r.fact.id, r.fact.finding])).values()];
|
|
@@ -859,9 +863,11 @@ export async function createFrontendReviewTerminalTools(input) {
|
|
|
859
863
|
const recordFindingTool = defineTool({
|
|
860
864
|
name: "record_review_finding", label: "record_review_finding",
|
|
861
865
|
description: "Save one finding with a stable id. Submit findings incrementally, then finalize without repeating the findings array. Saved blocking findings cannot be omitted from approval; correct a finding explicitly with replace:true.",
|
|
862
|
-
parameters: Type.Object({ id: Type.String({ minLength: 1 }), finding: findingSchema }, { additionalProperties: false }),
|
|
866
|
+
parameters: Type.Object({ id: Type.String({ minLength: 1 }), finding: findingSchema, replace: Type.Optional(Type.Boolean()) }, { additionalProperties: false }),
|
|
863
867
|
async execute(_callId, params) {
|
|
864
|
-
const
|
|
868
|
+
const previous = readCommittedEvents(store, attemptId).filter(r => r.fact.kind === "review-finding" && r.fact.id === params.id).at(-1);
|
|
869
|
+
const scopeIds = scopeProtocol.findingScopeIds(previous?.fact.scopeIds);
|
|
870
|
+
const fact = { kind: "review-finding", id: params.id, finding: reviewFindingSchema.parse(params.finding), ...(scopeIds ? { scopeIds } : {}) };
|
|
865
871
|
const requestId = `${attemptId}:finding:${randomUUID()}`;
|
|
866
872
|
const staged = stageTypedEventFact({ store, requestId, attemptId, fact });
|
|
867
873
|
const adopted = await adoptTypedEventFact({ store, requestId, attemptId, fact, eventId: staged.eventId, expectedRevision: store.revision });
|
|
@@ -1003,6 +1009,7 @@ export async function createFrontendDesignTerminalTools(input) {
|
|
|
1003
1009
|
line: Type.Optional(Type.Integer({ minimum: 1 })),
|
|
1004
1010
|
issue: Type.String({ minLength: 1 }),
|
|
1005
1011
|
requiredChange: Type.Optional(Type.String({ minLength: 1 })),
|
|
1012
|
+
repairAssertions: Type.Optional(Type.Unknown({ description: "Optional mechanical checks: file-absent/state-present {check,token,source}; vt-changed also requires expectedRequirementIds. Invalid items retain diagnostics and do not remove this finding." })),
|
|
1006
1013
|
}, { additionalProperties: false });
|
|
1007
1014
|
const scopeProtocol = createFrontendReviewScopeProtocol({ phase: "design", inventory: input.inventory, getStore: () => store, attemptId });
|
|
1008
1015
|
const savedFindings = () => [...new Map(readCommittedEvents(store, attemptId).filter(r => r.fact.kind === "design-finding").map(r => [r.fact.id, r.fact.finding])).values()];
|
|
@@ -1010,9 +1017,11 @@ export async function createFrontendDesignTerminalTools(input) {
|
|
|
1010
1017
|
const recordFindingTool = defineTool({
|
|
1011
1018
|
name: "record_design_finding", label: "record_design_finding",
|
|
1012
1019
|
description: "Save one finding with a stable id. Submit findings incrementally, then finalize without repeating the findings array. Saved blocking findings cannot be omitted from approval; correct a finding explicitly with replace:true.",
|
|
1013
|
-
parameters: Type.Object({ id: Type.String({ minLength: 1 }), finding: findingSchema }, { additionalProperties: false }),
|
|
1020
|
+
parameters: Type.Object({ id: Type.String({ minLength: 1 }), finding: findingSchema, replace: Type.Optional(Type.Boolean()) }, { additionalProperties: false }),
|
|
1014
1021
|
async execute(_callId, params) {
|
|
1015
|
-
const
|
|
1022
|
+
const previous = readCommittedEvents(store, attemptId).filter(r => r.fact.kind === "design-finding" && r.fact.id === params.id).at(-1);
|
|
1023
|
+
const scopeIds = scopeProtocol.findingScopeIds(previous?.fact.scopeIds);
|
|
1024
|
+
const fact = { kind: "design-finding", id: params.id, finding: reviewFindingSchema.parse(params.finding), ...(scopeIds ? { scopeIds } : {}) };
|
|
1016
1025
|
const requestId = `${attemptId}:finding:${randomUUID()}`;
|
|
1017
1026
|
const staged = stageTypedEventFact({ store, requestId, attemptId, fact });
|
|
1018
1027
|
const adopted = await adoptTypedEventFact({ store, requestId, attemptId, fact, eventId: staged.eventId, expectedRevision: store.revision });
|
|
@@ -2700,7 +2709,9 @@ export async function createFrontendPlanLedgerTools(input) {
|
|
|
2700
2709
|
nextOffset: offset + items.length < values.length ? offset + items.length : null });
|
|
2701
2710
|
},
|
|
2702
2711
|
});
|
|
2712
|
+
const planControl = createFrontendPlanProgressGuard();
|
|
2703
2713
|
const durable = await createDurableFrontendTools({
|
|
2714
|
+
planControl,
|
|
2704
2715
|
file: path.join(input.runDir, input.nodeId, "plan-typed-facts.jsonl"), attemptId, store: input.store,
|
|
2705
2716
|
setWorkingStore: next => { store = next; },
|
|
2706
2717
|
binding: { sourceBinding: input.sourceBinding, skeleton: input.skeleton, requirementIds: input.requirementIds, writeSet: input.writeSetPatterns, declaredUiStateIds: input.declaredUiStateIds, citations: input.componentNewSourceReferences, contractInheritance },
|
|
@@ -2726,6 +2737,7 @@ export async function createFrontendPlanLedgerTools(input) {
|
|
|
2726
2737
|
if (!durableFinalizePlanTool)
|
|
2727
2738
|
throw new Error("frontend plan durable finalize tool unavailable");
|
|
2728
2739
|
return {
|
|
2740
|
+
planControl,
|
|
2729
2741
|
customTools: [...durable.customTools, { ...readPlanFactsTool, execute: async (...args) => { await durable.flush(); return readPlanFactsTool.execute(...args); } }],
|
|
2730
2742
|
adoptCommittedFacts: async (records) => durable.commitExternal(async () => {
|
|
2731
2743
|
for (const record of records) {
|
|
@@ -2836,8 +2848,7 @@ export function translateDecisionPatchFindings(message, decision) {
|
|
|
2836
2848
|
const base = row
|
|
2837
2849
|
? `record_reuse_decision purpose "${row.purpose}" (component ${row.symbol}, decision ${row.decision}): ${issue}`
|
|
2838
2850
|
: `record_reuse_decision row ${choice[1]}: ${issue}`;
|
|
2839
|
-
|
|
2840
|
-
return field?.startsWith("specReference")
|
|
2851
|
+
return choice[2]?.startsWith("specReference") === true
|
|
2841
2852
|
? `${base} — pass specSection (the AC id / task-source section it implements) on that entry, or use decision "reuse-existing"`
|
|
2842
2853
|
: base;
|
|
2843
2854
|
}
|
|
@@ -2872,7 +2883,7 @@ export function translateDecisionPatchFindings(message, decision) {
|
|
|
2872
2883
|
}
|
|
2873
2884
|
/** Read the recovery child's read-only parent decision snapshot (written by
|
|
2874
2885
|
* the recovery continuation). Absent file → undefined (fresh plan). */
|
|
2875
|
-
export async function readParentDecisionSnapshot(runDir, nodeId) {
|
|
2886
|
+
export async function readParentDecisionSnapshot(runDir, nodeId, context) {
|
|
2876
2887
|
const snapshotPath = path.join(runDir, nodeId, "parent-decision-snapshot.json");
|
|
2877
2888
|
let raw;
|
|
2878
2889
|
try {
|
|
@@ -2884,8 +2895,45 @@ export async function readParentDecisionSnapshot(runDir, nodeId) {
|
|
|
2884
2895
|
throw error;
|
|
2885
2896
|
}
|
|
2886
2897
|
const parsed = JSON.parse(raw);
|
|
2887
|
-
if (!Array.isArray(parsed.facts))
|
|
2898
|
+
if (parsed.schemaVersion !== 1 || parsed.schemaId !== "frontend-plan-parent-decision-snapshot-v1" || !/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(parsed.parentRunId) || !/^[a-f0-9]{64}$/.test(parsed.parentLedgerSha256) || !Array.isArray(parsed.facts) || parsed.facts.some(fact => !fact || typeof fact.kind !== "string" || !fact.entry || typeof fact.entry !== "object" || Array.isArray(fact.entry)))
|
|
2888
2899
|
throw new Error(`parent decision snapshot malformed: ${snapshotPath}`);
|
|
2900
|
+
delete parsed.resolvedParentRunDir;
|
|
2901
|
+
if (context) {
|
|
2902
|
+
const { locateDagRun, readDagRunState, readDagRunSpec } = await import("../workflows/dag/lifecycle.js");
|
|
2903
|
+
const state = await readDagRunState(runDir);
|
|
2904
|
+
if (state.frontendRecoveryState?.parentRunId !== parsed.parentRunId || state.frontendRecoveryState.resetRootNodeId !== "frontend-plan-pi")
|
|
2905
|
+
throw new Error("parent decision snapshot recovery lineage mismatch");
|
|
2906
|
+
const located = await locateDagRun(context.workspaceRoot, parsed.parentRunId);
|
|
2907
|
+
if (!located)
|
|
2908
|
+
throw new Error("parent decision snapshot source run unavailable");
|
|
2909
|
+
const parentSpec = await readDagRunSpec(located.runDir);
|
|
2910
|
+
const sourceHash = sha256OfCanonicalJson(context.sourceBinding ?? null);
|
|
2911
|
+
if (parsed.sourceBindingSha256 !== sourceHash || sha256OfCanonicalJson(parentSpec.sourceBinding ?? null) !== sourceHash)
|
|
2912
|
+
throw new Error("parent decision snapshot source binding mismatch");
|
|
2913
|
+
const ledgerPath = path.join(located.runDir, "frontend-plan-pi", "plan-decision-facts.jsonl");
|
|
2914
|
+
const rawLedger = await readFile(ledgerPath, "utf8");
|
|
2915
|
+
if (createHash("sha256").update(rawLedger).digest("hex") !== parsed.parentLedgerSha256)
|
|
2916
|
+
throw new Error("parent decision snapshot ledger hash mismatch");
|
|
2917
|
+
const { readTypedEventStoreFromJsonl } = await import("../workflows/dag/frontend-typed-event-store.js");
|
|
2918
|
+
const { assembleDecisionContractFromFacts } = await import("../workflows/dag/frontend-plan-decision-contract.js");
|
|
2919
|
+
const records = (await readTypedEventStoreFromJsonl(ledgerPath)).filter(record => record.phase === "committed");
|
|
2920
|
+
if (records.some(record => record.payloadSha256 !== sha256OfCanonicalJson(record.fact)))
|
|
2921
|
+
throw new Error("parent decision snapshot ledger payload hash mismatch");
|
|
2922
|
+
const actual = assembleDecisionContractFromFacts(records.map(record => record.fact));
|
|
2923
|
+
if (sha256OfCanonicalJson(assembleDecisionContractFromFacts(parsed.facts)) !== sha256OfCanonicalJson(actual))
|
|
2924
|
+
throw new Error("parent decision snapshot facts differ from bound ledger");
|
|
2925
|
+
const { readCurrentFrontendRepairFindings } = await import("../workflows/dag/rerun-feedback.js");
|
|
2926
|
+
const feedback = await readCurrentFrontendRepairFindings(located.runDir);
|
|
2927
|
+
const feedbackBinding = feedback ? { sourceNodeId: feedback.sourceNodeId, ledgerSha256: feedback.ledgerSha256, terminalEventId: feedback.terminalEventId } : undefined;
|
|
2928
|
+
if (sha256OfCanonicalJson(parsed.feedbackBinding ?? null) !== sha256OfCanonicalJson(feedbackBinding ?? null) || sha256OfCanonicalJson(parsed.designFindings ?? []) !== sha256OfCanonicalJson(feedback?.verdict === "request" ? feedback.findings : []))
|
|
2929
|
+
throw new Error("parent decision snapshot finding source mismatch");
|
|
2930
|
+
if (parsed.parentCanonicalSha256) {
|
|
2931
|
+
const canonical = await readFile(path.join(located.runDir, "contracts/frontend-implementation-contract.json"), "utf8");
|
|
2932
|
+
if (createHash("sha256").update(canonical).digest("hex") !== parsed.parentCanonicalSha256)
|
|
2933
|
+
throw new Error("parent canonical hash mismatch");
|
|
2934
|
+
}
|
|
2935
|
+
parsed.resolvedParentRunDir = located.runDir;
|
|
2936
|
+
}
|
|
2889
2937
|
return parsed;
|
|
2890
2938
|
}
|
|
2891
2939
|
const decisionKindToToolName = (kind) => `record_${kind.replaceAll("-", "_")}`;
|
|
@@ -3287,7 +3335,7 @@ export async function createFrontendPlanDecisionTools(input) {
|
|
|
3287
3335
|
return planToolReceipt({
|
|
3288
3336
|
ok: false,
|
|
3289
3337
|
kind: "finalize_decision",
|
|
3290
|
-
code: "DECISION_SAFETY_FINDINGS",
|
|
3338
|
+
code: "DECISION_SAFETY_FINDINGS", validationStage: "safety", completedValidationStages: [],
|
|
3291
3339
|
error: `decision safety projection failed: ${derived.findings.join("; ")}`,
|
|
3292
3340
|
});
|
|
3293
3341
|
}
|
|
@@ -3296,7 +3344,7 @@ export async function createFrontendPlanDecisionTools(input) {
|
|
|
3296
3344
|
return planToolReceipt({
|
|
3297
3345
|
ok: false,
|
|
3298
3346
|
kind: "finalize_decision",
|
|
3299
|
-
code: "DECISION_PREVALIDATION_FAILED",
|
|
3347
|
+
code: "DECISION_PREVALIDATION_FAILED", validationStage: "canonical", completedValidationStages: ["safety"],
|
|
3300
3348
|
error: prevalidation.error,
|
|
3301
3349
|
});
|
|
3302
3350
|
}
|
|
@@ -3310,7 +3358,7 @@ export async function createFrontendPlanDecisionTools(input) {
|
|
|
3310
3358
|
if (input.parentDecisionSnapshot) {
|
|
3311
3359
|
const { readParentCanonical, parentCanonicalContentSha256 } = await import("../workflows/dag/frontend-implementation-contract.js");
|
|
3312
3360
|
const { extractRepairAssertions, evaluateRepairClosure, decisionIdentitySha256 } = await import("../workflows/dag/frontend-plan-decision-contract.js");
|
|
3313
|
-
const parentRunDir = path.join(input.runDir, "..", input.parentDecisionSnapshot.parentRunId);
|
|
3361
|
+
const parentRunDir = input.parentDecisionSnapshot.resolvedParentRunDir ?? path.join(input.runDir, "..", input.parentDecisionSnapshot.parentRunId);
|
|
3314
3362
|
const parentCanonical = await readParentCanonical(parentRunDir);
|
|
3315
3363
|
// 1. identical-plan guard. Decisions, not prose: a rationale-only
|
|
3316
3364
|
// rewrite is not a repair (smoke r27 cleared a raw byte-hash guard
|
|
@@ -3328,21 +3376,26 @@ export async function createFrontendPlanDecisionTools(input) {
|
|
|
3328
3376
|
return planToolReceipt({
|
|
3329
3377
|
ok: false,
|
|
3330
3378
|
kind: "finalize_decision",
|
|
3331
|
-
code: "DECISION_REPAIR_NO_CHANGE",
|
|
3379
|
+
code: "DECISION_REPAIR_NO_CHANGE", validationStage: "recovery", completedValidationStages: input.skeleton && input.sourceBinding ? ["safety", "canonical"] : ["safety"],
|
|
3332
3380
|
error: "recovery finalize blocked: every structured decision row is identical to the parent run's (rationale-only edits do not count as a repair), but the review findings require changes. Correct the flagged rows (same identity, replace:true), then call finalize_decision again.",
|
|
3333
3381
|
});
|
|
3334
3382
|
}
|
|
3335
3383
|
// 2. per-finding closure: every deterministic assertion extracted
|
|
3336
3384
|
// from the parent findings must hold on the child canonical.
|
|
3337
3385
|
const findings = input.parentDecisionSnapshot.designFindings ?? [];
|
|
3386
|
+
if (!parentCanonical && findings.some(finding => Array.isArray(finding.repairAssertions) && finding.repairAssertions.length > 0))
|
|
3387
|
+
return planToolReceipt({ ok: false, kind: "finalize_decision", code: "DECISION_REPAIR_PARENT_UNAVAILABLE", error: "Repair assertions require the bound parent canonical contract", validationStage: "recovery", completedValidationStages: input.skeleton && input.sourceBinding ? ["safety", "canonical"] : ["safety"] });
|
|
3338
3388
|
if (parentCanonical && findings.length > 0) {
|
|
3339
3389
|
const assertions = extractRepairAssertions(findings, parentCanonical);
|
|
3390
|
+
const unauthorized = authorizeRepairAssertions(assertions, { parent: parentCanonical, requirementIds: authority.requirements.map(r => r.id), stateIds: authority.behaviorGroups.flatMap(group => group.requiredStates ?? []), requiredDeliverables: (prevalidation.canonical.targets?.requiredDeliverables ?? []) });
|
|
3391
|
+
if (unauthorized.length)
|
|
3392
|
+
return planToolReceipt({ ok: false, kind: "finalize_decision", code: "DECISION_REPAIR_ASSERTION_UNAUTHORIZED", error: unauthorized.join("; "), validationStage: "recovery", completedValidationStages: input.skeleton && input.sourceBinding ? ["safety", "canonical"] : ["safety"] });
|
|
3340
3393
|
const closure = evaluateRepairClosure(assertions, prevalidation.canonical, parentCanonical);
|
|
3341
3394
|
if (!closure.closed) {
|
|
3342
3395
|
return planToolReceipt({
|
|
3343
3396
|
ok: false,
|
|
3344
3397
|
kind: "finalize_decision",
|
|
3345
|
-
code: "DECISION_REPAIR_NOT_CLOSED",
|
|
3398
|
+
code: "DECISION_REPAIR_NOT_CLOSED", validationStage: "recovery", completedValidationStages: input.skeleton && input.sourceBinding ? ["safety", "canonical"] : ["safety"],
|
|
3346
3399
|
error: `recovery finalize blocked: ${closure.unmet.length} finding(s) are still not closed — ${closure.unmet.join("; ")}. Correct the flagged rows (same identity, replace:true), then call finalize_decision again.`,
|
|
3347
3400
|
});
|
|
3348
3401
|
}
|
|
@@ -3352,7 +3405,9 @@ export async function createFrontendPlanDecisionTools(input) {
|
|
|
3352
3405
|
return planToolReceipt(result);
|
|
3353
3406
|
},
|
|
3354
3407
|
});
|
|
3408
|
+
const planControl = createFrontendPlanProgressGuard();
|
|
3355
3409
|
const durable = await createDurableFrontendTools({
|
|
3410
|
+
planControl,
|
|
3356
3411
|
file: path.join(input.runDir, input.nodeId, "plan-decision-facts.jsonl"),
|
|
3357
3412
|
attemptId,
|
|
3358
3413
|
store: input.store,
|
|
@@ -3374,6 +3429,7 @@ export async function createFrontendPlanDecisionTools(input) {
|
|
|
3374
3429
|
if (!durableFinalizeDecisionTool)
|
|
3375
3430
|
throw new Error("frontend plan decision durable finalize tool unavailable");
|
|
3376
3431
|
return {
|
|
3432
|
+
planControl,
|
|
3377
3433
|
customTools: durable.customTools,
|
|
3378
3434
|
flush: durable.flush,
|
|
3379
3435
|
committedFacts: () => readCommittedEvents(store, attemptId),
|
|
@@ -5610,27 +5666,6 @@ function buildFrontendPlanWorkload(input) {
|
|
|
5610
5666
|
countFrontendPlanTargetSurfaces(input.basePrompt) === 1,
|
|
5611
5667
|
};
|
|
5612
5668
|
}
|
|
5613
|
-
/**
|
|
5614
|
-
* Requirement ids the plan coverage layout must own, in first-commit order.
|
|
5615
|
-
*
|
|
5616
|
-
* The contract ledger is append-only and `record_requirement` is incremental, so
|
|
5617
|
-
* the same id can legitimately be committed more than once. Deduplicating here is
|
|
5618
|
-
* load-bearing: `buildFrontendPlanWorkload` seeds one `unclassified` work group per
|
|
5619
|
-
* list entry without re-checking membership, so a repeated id produced two groups,
|
|
5620
|
-
* `buildWorkBatches` emitted the id twice, and the coverage-layout validator then
|
|
5621
|
-
* rejected the layout the runtime had derived itself (members 8 !== owners 7) as
|
|
5622
|
-
* FRONTEND_PLAN_LAYOUT_INVALID with failureCategory tool-policy - killing the run
|
|
5623
|
-
* at the plan node over a completely ordinary ledger.
|
|
5624
|
-
*/
|
|
5625
|
-
export function collectFrontendPlanRequirementIds(contractFacts) {
|
|
5626
|
-
return [
|
|
5627
|
-
...new Set(contractFacts
|
|
5628
|
-
.filter((record) => record.fact?.kind ===
|
|
5629
|
-
"requirement")
|
|
5630
|
-
.map((record) => record.fact?.id)
|
|
5631
|
-
.filter((id) => typeof id === "string")),
|
|
5632
|
-
];
|
|
5633
|
-
}
|
|
5634
5669
|
const frontendPlanCoverageLayoutSchema = z.object({
|
|
5635
5670
|
schemaVersion: z.literal(1),
|
|
5636
5671
|
bindingSha256: z.string(),
|
|
@@ -5914,9 +5949,9 @@ export async function runFrontendPlanSegmentedSessions(input) {
|
|
|
5914
5949
|
}
|
|
5915
5950
|
}
|
|
5916
5951
|
const work = [...domains.values()];
|
|
5917
|
-
|
|
5952
|
+
// Work-unit packing and full provider envelope capacity have separate budgets.
|
|
5918
5953
|
return packFrontendInputUnits(work, {
|
|
5919
|
-
targetBytes:
|
|
5954
|
+
targetBytes: input.sessionOptions.frontendExecutionPolicy?.scopeTargetBytes ?? FRONTEND_SCOPE_TARGET_BYTES,
|
|
5920
5955
|
maxUnits: input.sessionOptions.frontendExecutionPolicy?.maxScopeUnits ?? 4,
|
|
5921
5956
|
maxCost: FRONTEND_PLAN_COVERAGE_MAX_RECORD_CALLS,
|
|
5922
5957
|
cost: (group) => group.estimatedCalls,
|
|
@@ -6170,7 +6205,7 @@ export async function runFrontendPlanSegmentedSessions(input) {
|
|
|
6170
6205
|
};
|
|
6171
6206
|
const customTools = input.segmentCustomTools(atomicFocus ? new Set([atomicTools[atomicFocus.kind]]) : session.toolNames);
|
|
6172
6207
|
const result = await observeFrontendSession({
|
|
6173
|
-
...input.observation, phase: `plan/${session.id}`, scopeIds: session.requirementSlice ?? session.coverageSlice ?? allRequirementIds,
|
|
6208
|
+
...input.observation, phase: `plan/${session.id}`, dispatchReason: (session.retryCount ?? 0) > 0 || !!session.missingFacts?.length || /capacity|recovery|split/.test(session.id) ? "correction" : "initial", scopeIds: session.requirementSlice ?? session.coverageSlice ?? allRequirementIds,
|
|
6174
6209
|
prompt, userMessage: input.sessionOptions.userMessage, customTools, committedCount: input.committedFactCount, durableCommittedCount: () => lastDurableCount,
|
|
6175
6210
|
artifactPath: input.observation?.artifactPath ? `${input.observation.artifactPath}-${invocationCount}.json` : undefined,
|
|
6176
6211
|
}, async (observer) => {
|
|
@@ -6274,7 +6309,7 @@ export async function runFrontendPlanSegmentedSessions(input) {
|
|
|
6274
6309
|
});
|
|
6275
6310
|
}
|
|
6276
6311
|
}
|
|
6277
|
-
const promptForMissingPhase = (missing) =>
|
|
6312
|
+
const promptForMissingPhase = (missing) => session.coverageOnly
|
|
6278
6313
|
? buildCoveragePrompt(session.coverageSlice ?? [], missing)
|
|
6279
6314
|
: isCompactLocalSession
|
|
6280
6315
|
? buildCompactLocalPrompt(missing)
|
|
@@ -6282,7 +6317,7 @@ export async function runFrontendPlanSegmentedSessions(input) {
|
|
|
6282
6317
|
? buildUxRegistryPrompt(missing)
|
|
6283
6318
|
: isUxLocalSession
|
|
6284
6319
|
? buildUxPrompt(session.requirementSlice ?? [], missing)
|
|
6285
|
-
: buildPhasePrompt(globalMockDataSegment, missing, session.requirementSlice ?? allRequirementIds)
|
|
6320
|
+
: buildPhasePrompt(globalMockDataSegment, missing, session.requirementSlice ?? allRequirementIds);
|
|
6286
6321
|
const recovery = classifyFrontendPlanRecovery({ ...result, stopReason: readWriterThinkingExhaustionEvidence(result).stopReason });
|
|
6287
6322
|
if (recovery === "stop")
|
|
6288
6323
|
return { ...result, ok: false };
|
|
@@ -6530,7 +6565,12 @@ export async function runFrontendPlanSegmentedSessions(input) {
|
|
|
6530
6565
|
optionalPlanFields.realIntegrationGap = fact.realIntegrationGap;
|
|
6531
6566
|
}
|
|
6532
6567
|
}
|
|
6533
|
-
let
|
|
6568
|
+
let finalizationCount = 0;
|
|
6569
|
+
const finalize = () => observeFrontendFinalization({
|
|
6570
|
+
...input.observation,
|
|
6571
|
+
artifactPath: input.observation?.artifactPath ? `${input.observation.artifactPath}-runtime-finalize-${++finalizationCount}.json` : undefined,
|
|
6572
|
+
}, () => input.finalizePlan(optionalPlanFields));
|
|
6573
|
+
let finalizeResult = await finalize();
|
|
6534
6574
|
const finalizeDetails = finalizeResult && typeof finalizeResult === "object"
|
|
6535
6575
|
? (finalizeResult.details ?? finalizeResult)
|
|
6536
6576
|
: finalizeResult;
|
|
@@ -6551,20 +6591,30 @@ export async function runFrontendPlanSegmentedSessions(input) {
|
|
|
6551
6591
|
].filter(Boolean).join("\n\n");
|
|
6552
6592
|
input.setActiveRequirementScope?.([]);
|
|
6553
6593
|
const correctionTools = input.segmentCustomTools(null);
|
|
6554
|
-
const correction = await
|
|
6555
|
-
...input.
|
|
6556
|
-
prompt: correctionPrompt,
|
|
6557
|
-
|
|
6594
|
+
const correction = await observeFrontendSession({
|
|
6595
|
+
...input.observation, phase: "plan/finalize-correction", dispatchReason: "correction",
|
|
6596
|
+
scopeIds: input.requirementIds, prompt: correctionPrompt, userMessage: input.sessionOptions.userMessage,
|
|
6597
|
+
customTools: correctionTools, committedCount: input.committedFactCount, durableCommittedCount: () => lastDurableCount,
|
|
6598
|
+
artifactPath: input.observation?.artifactPath ? `${input.observation.artifactPath}-finalize-correction.json` : undefined,
|
|
6599
|
+
}, async (observer) => {
|
|
6600
|
+
const result = await input.piStepFn({
|
|
6601
|
+
...input.sessionOptions, onAttemptObservation: observer, prompt: correctionPrompt,
|
|
6602
|
+
writerToolPolicy: { requireSdk: true, customTools: correctionTools },
|
|
6603
|
+
});
|
|
6604
|
+
try {
|
|
6605
|
+
await input.flushLedger();
|
|
6606
|
+
lastDurableCount = input.committedFactCount();
|
|
6607
|
+
}
|
|
6608
|
+
catch { /* node-level flush retries below */ }
|
|
6609
|
+
return result;
|
|
6558
6610
|
});
|
|
6559
|
-
try {
|
|
6560
|
-
await input.flushLedger();
|
|
6561
|
-
}
|
|
6562
|
-
catch { /* node-level flush retries below */ }
|
|
6563
6611
|
accumulated = accumulated
|
|
6564
6612
|
? combineSequentialPiResults(accumulated, correction)
|
|
6565
6613
|
: correction;
|
|
6614
|
+
if (!correction.ok && !["invalid-output", "empty-output", "success"].includes(correction.failureCategory))
|
|
6615
|
+
return { ...accumulated, ok: false, failureCategory: correction.failureCategory };
|
|
6566
6616
|
if (correction.ok) {
|
|
6567
|
-
finalizeResult = await
|
|
6617
|
+
finalizeResult = await finalize();
|
|
6568
6618
|
}
|
|
6569
6619
|
const retriedDetails = finalizeResult && typeof finalizeResult === "object"
|
|
6570
6620
|
? (finalizeResult.details ?? finalizeResult)
|
|
@@ -6657,7 +6707,7 @@ async function runFrontendScoutParallelSessions(input) {
|
|
|
6657
6707
|
toolName: "record_target_surface",
|
|
6658
6708
|
instruction: [
|
|
6659
6709
|
"PARALLEL SCOUT SHARD — target surface only.",
|
|
6660
|
-
"Inspect routes, entrypoints, implementation ownership, data source, and
|
|
6710
|
+
"Inspect routes, entrypoints, implementation ownership, data source, and existing test entrypoints. Inspect project source/config first; do not recursively explore dependency internals merely to reconfirm standard test-runner behavior. If a concrete required capability cannot be established from project evidence, report the precise gap.",
|
|
6661
6711
|
"Call record_target_surface exactly once with the complete runtime-evidenced surface. Do not call record_design_evidence.",
|
|
6662
6712
|
].join(" "),
|
|
6663
6713
|
},
|
|
@@ -6666,12 +6716,16 @@ async function runFrontendScoutParallelSessions(input) {
|
|
|
6666
6716
|
toolName: "record_design_evidence",
|
|
6667
6717
|
instruction: [
|
|
6668
6718
|
"PARALLEL SCOUT SHARD — design evidence only.",
|
|
6669
|
-
"Inspect
|
|
6719
|
+
"Inspect project-owned frontend framework, styling/theme conventions, reusable components, and relevant design/spec files. The surface shard owns test-runner/environment discovery; do not duplicate its Vitest/happy-dom investigation. Stop discovery once the design evidence is sufficient and commit it.",
|
|
6670
6720
|
"Call record_design_evidence for the evidence you actually read. Do not call record_target_surface.",
|
|
6671
6721
|
].join(" "),
|
|
6672
6722
|
},
|
|
6673
6723
|
];
|
|
6674
6724
|
const outcomes = await Promise.all(shards.map(async (shard) => {
|
|
6725
|
+
const cacheKey = JSON.stringify([input.runDir, input.nodeId, shard.id, input.sessionOptions.modelConfig, input.observation?.model, input.observation?.sourceDigest, input.sourceDeclaredPaths]);
|
|
6726
|
+
const cached = input.completedShards?.get(cacheKey);
|
|
6727
|
+
if (cached)
|
|
6728
|
+
return { shard, facts: cached.facts, result: { ...cached.result, durationMs: 0, tokensUsed: 0, parsedEvents: 0, attemptedModels: [], fallbackUsed: false } };
|
|
6675
6729
|
let shardTools;
|
|
6676
6730
|
let shardResult;
|
|
6677
6731
|
try {
|
|
@@ -6714,7 +6768,12 @@ async function runFrontendScoutParallelSessions(input) {
|
|
|
6714
6768
|
},
|
|
6715
6769
|
}));
|
|
6716
6770
|
await shardTools.flush();
|
|
6717
|
-
|
|
6771
|
+
const facts = shardTools.committedFacts();
|
|
6772
|
+
const expectedKind = shard.id === "design" ? "design-evidence" : "target-surface";
|
|
6773
|
+
if (shardResult.ok && facts.some(record => { const fact = record.fact; return fact.kind === expectedKind && (shard.id !== "surface" || fact.completeness === "complete"); })) {
|
|
6774
|
+
input.completedShards?.set(cacheKey, { result: structuredClone(shardResult), facts: structuredClone(facts) });
|
|
6775
|
+
}
|
|
6776
|
+
return { shard, result: shardResult, tools: shardTools, facts };
|
|
6718
6777
|
}
|
|
6719
6778
|
catch (error) {
|
|
6720
6779
|
const crashMessage = `frontend scout parallel shard ${shard.id} crashed: ${error instanceof Error ? error.message : String(error)}`;
|
|
@@ -6754,8 +6813,8 @@ async function runFrontendScoutParallelSessions(input) {
|
|
|
6754
6813
|
const ordered = [...outcomes].sort((left, right) => left.shard.id.localeCompare(right.shard.id));
|
|
6755
6814
|
try {
|
|
6756
6815
|
for (const outcome of ordered) {
|
|
6757
|
-
if (outcome.result.ok
|
|
6758
|
-
await input.mainTools.adoptCommittedFacts(outcome.tools
|
|
6816
|
+
if (outcome.result.ok) {
|
|
6817
|
+
await input.mainTools.adoptCommittedFacts(outcome.facts ?? outcome.tools?.committedFacts() ?? []);
|
|
6759
6818
|
}
|
|
6760
6819
|
}
|
|
6761
6820
|
}
|
|
@@ -6782,7 +6841,7 @@ async function runFrontendScoutParallelSessions(input) {
|
|
|
6782
6841
|
stderr: `${aggregate.stderr}\nfrontend scout parallel shard failed: ${failed.shard.id}`.trim(),
|
|
6783
6842
|
};
|
|
6784
6843
|
}
|
|
6785
|
-
const committedKinds = new Set(ordered.flatMap((outcome) => (outcome.tools?.committedFacts() ?? [])
|
|
6844
|
+
const committedKinds = new Set(ordered.flatMap((outcome) => (outcome.facts ?? outcome.tools?.committedFacts() ?? [])
|
|
6786
6845
|
.map((record) => record.fact?.kind)
|
|
6787
6846
|
.filter((kind) => typeof kind === "string")));
|
|
6788
6847
|
const missing = ["target-surface", "design-evidence"].filter((kind) => !committedKinds.has(kind));
|
|
@@ -6987,7 +7046,7 @@ export async function executeDagPiNode(input, meta, piStepFn = executePiStep, wr
|
|
|
6987
7046
|
const { createTypedEventStore } = await import("../workflows/dag/frontend-typed-event-store.js");
|
|
6988
7047
|
const store = createTypedEventStore();
|
|
6989
7048
|
reviewTerminalTools = await createFrontendReviewTerminalTools({
|
|
6990
|
-
inventory: reviewInventory = await loadFrontendReviewScopes(meta.runDir, "review", meta.spec?.frontendExecutionPolicy?.scopeTargetBytes ?? FRONTEND_SCOPE_TARGET_BYTES),
|
|
7049
|
+
inventory: reviewInventory = await loadFrontendReviewScopes(meta.runDir, "review", meta.spec?.frontendExecutionPolicy?.scopeTargetBytes ?? FRONTEND_SCOPE_TARGET_BYTES, input.cwd),
|
|
6991
7050
|
inputDigest: reviewInventory?.digest ?? createHash("sha256").update(input.prompt).digest("hex"),
|
|
6992
7051
|
attemptId: `${meta.runId}:${input.task.id}`,
|
|
6993
7052
|
store,
|
|
@@ -7144,7 +7203,7 @@ export async function executeDagPiNode(input, meta, piStepFn = executePiStep, wr
|
|
|
7144
7203
|
if (useDecisionPlan) {
|
|
7145
7204
|
// Recovery child: read the parent snapshot before the tools so
|
|
7146
7205
|
// the replay and the identical-plan finalize guard share it.
|
|
7147
|
-
parentDecisionSnapshot = await readParentDecisionSnapshot(meta.runDir, input.task.id);
|
|
7206
|
+
parentDecisionSnapshot = await readParentDecisionSnapshot(meta.runDir, input.task.id, { workspaceRoot: input.cwd, sourceBinding: meta.spec.sourceBinding });
|
|
7148
7207
|
}
|
|
7149
7208
|
decisionPlanTools = await createFrontendPlanDecisionTools({
|
|
7150
7209
|
attemptId: `${meta.runId}:${input.task.id}`,
|
|
@@ -7334,6 +7393,7 @@ export async function executeDagPiNode(input, meta, piStepFn = executePiStep, wr
|
|
|
7334
7393
|
scoutEvidenceTools &&
|
|
7335
7394
|
input.task.complexity !== "LOW") {
|
|
7336
7395
|
result = await runFrontendScoutParallelSessions({
|
|
7396
|
+
completedShards: input.scoutCompletedShards,
|
|
7337
7397
|
piStepFn,
|
|
7338
7398
|
sessionOptions: piSessionOptions,
|
|
7339
7399
|
basePrompt: input.prompt,
|
|
@@ -7412,46 +7472,86 @@ export async function executeDagPiNode(input, meta, piStepFn = executePiStep, wr
|
|
|
7412
7472
|
return base;
|
|
7413
7473
|
})()
|
|
7414
7474
|
: input.prompt;
|
|
7415
|
-
|
|
7416
|
-
|
|
7417
|
-
|
|
7418
|
-
|
|
7475
|
+
let decisionDurableCount = decisionPlanTools.committedFacts().length;
|
|
7476
|
+
const decisionResult = await observeFrontendSession({
|
|
7477
|
+
artifactPath: path.join(meta.runDir, input.task.id, "session-budget", `attempt-${input.attempt ?? 1}-decision.json`),
|
|
7478
|
+
phase: "plan/decision", dispatchReason: "initial",
|
|
7479
|
+
runId: meta.runId, nodeId: input.task.id, taskId: meta.spec?.sourceBinding?.taskId,
|
|
7480
|
+
attempt: input.attempt ?? 1, model: input.model,
|
|
7481
|
+
sourceDigest: meta.spec?.sourceBinding?.schemaVersion === 2 ? meta.spec.sourceBinding.inputDigest : undefined,
|
|
7482
|
+
prompt: decisionPrompt, userMessage: piSessionOptions.userMessage, customTools: decisionPlanTools.customTools,
|
|
7483
|
+
committedCount: () => decisionPlanTools.committedFacts().length,
|
|
7484
|
+
durableCommittedCount: () => decisionDurableCount,
|
|
7485
|
+
}, async (observer) => {
|
|
7486
|
+
const result = await piStepFn({
|
|
7487
|
+
...piSessionOptions, frontendPlanControl: decisionPlanTools.planControl, onAttemptObservation: observer, prompt: decisionPrompt,
|
|
7488
|
+
writerToolPolicy: { requireSdk: true, customTools: decisionPlanTools.customTools },
|
|
7489
|
+
});
|
|
7490
|
+
try {
|
|
7491
|
+
await decisionPlanTools.flush();
|
|
7492
|
+
decisionDurableCount = decisionPlanTools.committedFacts().length;
|
|
7493
|
+
}
|
|
7494
|
+
catch { /* node-level flush retries below */ }
|
|
7495
|
+
return result;
|
|
7419
7496
|
});
|
|
7420
|
-
|
|
7421
|
-
|
|
7497
|
+
if ((!decisionResult.ok && !["empty-output", "invalid-output", "unknown"].includes(decisionResult.failureCategory)) || decisionPlanTools.planControl.readStop()) {
|
|
7498
|
+
const stopped = decisionPlanTools.planControl.readStop();
|
|
7499
|
+
result = decisionResult.ok && stopped ? { ...decisionResult, ok: false, failureCategory: "invalid-output", stderr: `${stopped.code}: ${stopped.error}` } : decisionResult;
|
|
7422
7500
|
}
|
|
7423
|
-
|
|
7424
|
-
|
|
7425
|
-
|
|
7426
|
-
|
|
7427
|
-
|
|
7428
|
-
|
|
7429
|
-
|
|
7430
|
-
|
|
7431
|
-
|
|
7432
|
-
|
|
7433
|
-
|
|
7434
|
-
|
|
7435
|
-
|
|
7436
|
-
|
|
7437
|
-
|
|
7438
|
-
|
|
7439
|
-
|
|
7440
|
-
|
|
7441
|
-
|
|
7442
|
-
|
|
7443
|
-
|
|
7444
|
-
|
|
7445
|
-
|
|
7446
|
-
|
|
7501
|
+
else {
|
|
7502
|
+
const finalizeResult = await observeFrontendFinalization({
|
|
7503
|
+
artifactPath: path.join(meta.runDir, input.task.id, "session-budget", `attempt-${input.attempt ?? 1}-runtime-finalize.json`),
|
|
7504
|
+
runId: meta.runId, nodeId: input.task.id, taskId: meta.spec?.sourceBinding?.taskId, attempt: input.attempt ?? 1,
|
|
7505
|
+
}, () => decisionPlanTools.finalizeDecision());
|
|
7506
|
+
const finalizeDetails = finalizeResult && typeof finalizeResult === "object"
|
|
7507
|
+
? (finalizeResult.details ?? finalizeResult)
|
|
7508
|
+
: finalizeResult;
|
|
7509
|
+
if (finalizeDetails && typeof finalizeDetails === "object" && finalizeDetails.ok === true) {
|
|
7510
|
+
// Bridge the finalized decisions into the relationship-shaped plan
|
|
7511
|
+
// ledger. The node-level R1 self-check and all downstream shells
|
|
7512
|
+
// compile ONLY plan-typed-facts.jsonl; without this write the whole
|
|
7513
|
+
// decision run fails "frontend plan ledger missing" at R1 on every
|
|
7514
|
+
// attempt. Validation failures surface as invalid-output so the
|
|
7515
|
+
// retry ladder restarts the session with the diagnostics.
|
|
7516
|
+
try {
|
|
7517
|
+
const bridge = await bridgeFrontendPlanDecisionToRelationshipLedger({
|
|
7518
|
+
runDir: meta.runDir,
|
|
7519
|
+
nodeId: input.task.id,
|
|
7520
|
+
attemptId: `${meta.runId}:${input.task.id}`,
|
|
7521
|
+
facts: decisionPlanTools.committedFacts(),
|
|
7522
|
+
authority: decisionPlanAuthority,
|
|
7523
|
+
skeleton: input.task.structuredContractOutput?.skeleton,
|
|
7524
|
+
sourceBinding: meta.spec.sourceBinding,
|
|
7525
|
+
});
|
|
7526
|
+
if (!bridge.ok) {
|
|
7527
|
+
throw new Error(bridge.error);
|
|
7528
|
+
}
|
|
7529
|
+
result = decisionResult;
|
|
7530
|
+
}
|
|
7531
|
+
catch (error) {
|
|
7532
|
+
result = {
|
|
7533
|
+
ok: false,
|
|
7534
|
+
stdout: decisionResult.stdout ?? "",
|
|
7535
|
+
stderr: `${decisionResult.stderr ?? ""}\n${error instanceof Error ? error.message : String(error)}`.trim(),
|
|
7536
|
+
failureCategory: "invalid-output",
|
|
7537
|
+
durationMs: Date.now() - started,
|
|
7538
|
+
modelDisplay: decisionResult.modelDisplay,
|
|
7539
|
+
parsedEvents: decisionResult.parsedEvents,
|
|
7540
|
+
timedOut: decisionResult.timedOut,
|
|
7541
|
+
attemptedModels: decisionResult.attemptedModels,
|
|
7542
|
+
fallbackUsed: decisionResult.fallbackUsed,
|
|
7543
|
+
tokensUsed: decisionResult.tokensUsed,
|
|
7544
|
+
assistantText: decisionResult.assistantText ?? "",
|
|
7545
|
+
command: decisionResult.command ?? [],
|
|
7546
|
+
exitCode: decisionResult.exitCode,
|
|
7547
|
+
};
|
|
7447
7548
|
}
|
|
7448
|
-
result = decisionResult;
|
|
7449
7549
|
}
|
|
7450
|
-
|
|
7550
|
+
else {
|
|
7451
7551
|
result = {
|
|
7452
7552
|
ok: false,
|
|
7453
7553
|
stdout: decisionResult.stdout ?? "",
|
|
7454
|
-
stderr: `${decisionResult.stderr ?? ""}\
|
|
7554
|
+
stderr: `${decisionResult.stderr ?? ""}\nfrontend decision finalize failed: ${String(finalizeDetails?.error ?? "decision finalize rejected")}`.trim(),
|
|
7455
7555
|
failureCategory: "invalid-output",
|
|
7456
7556
|
durationMs: Date.now() - started,
|
|
7457
7557
|
modelDisplay: decisionResult.modelDisplay,
|
|
@@ -7466,24 +7566,6 @@ export async function executeDagPiNode(input, meta, piStepFn = executePiStep, wr
|
|
|
7466
7566
|
};
|
|
7467
7567
|
}
|
|
7468
7568
|
}
|
|
7469
|
-
else {
|
|
7470
|
-
result = {
|
|
7471
|
-
ok: false,
|
|
7472
|
-
stdout: decisionResult.stdout ?? "",
|
|
7473
|
-
stderr: `${decisionResult.stderr ?? ""}\nfrontend decision finalize failed: ${String(finalizeDetails?.error ?? "decision finalize rejected")}`.trim(),
|
|
7474
|
-
failureCategory: "invalid-output",
|
|
7475
|
-
durationMs: Date.now() - started,
|
|
7476
|
-
modelDisplay: decisionResult.modelDisplay,
|
|
7477
|
-
parsedEvents: decisionResult.parsedEvents,
|
|
7478
|
-
timedOut: decisionResult.timedOut,
|
|
7479
|
-
attemptedModels: decisionResult.attemptedModels,
|
|
7480
|
-
fallbackUsed: decisionResult.fallbackUsed,
|
|
7481
|
-
tokensUsed: decisionResult.tokensUsed,
|
|
7482
|
-
assistantText: decisionResult.assistantText ?? "",
|
|
7483
|
-
command: decisionResult.command ?? [],
|
|
7484
|
-
exitCode: decisionResult.exitCode,
|
|
7485
|
-
};
|
|
7486
|
-
}
|
|
7487
7569
|
}
|
|
7488
7570
|
else if (isFrontendPlanLedgerNode(input.task) && planLedgerTools) {
|
|
7489
7571
|
// Frontend-only split: independent coverage map sessions feed a single
|
|
@@ -7496,14 +7578,12 @@ export async function executeDagPiNode(input, meta, piStepFn = executePiStep, wr
|
|
|
7496
7578
|
try {
|
|
7497
7579
|
const { readCommittedOriginFacts } = await import("../workflows/dag/frontend-committed-facts.js");
|
|
7498
7580
|
const contractFacts = await readCommittedOriginFacts(meta.runDir, "frontend-contract-pi", "contract-typed-facts.jsonl");
|
|
7499
|
-
//
|
|
7500
|
-
//
|
|
7501
|
-
//
|
|
7502
|
-
|
|
7503
|
-
|
|
7504
|
-
|
|
7505
|
-
planRequirementIds = collectFrontendPlanRequirementIds(contractFacts);
|
|
7506
|
-
for (const fact of resolveFrontendContractRequirements(contractFacts.map((record) => record.fact))) {
|
|
7581
|
+
// Read current requirements, not append-only revisions introduced when
|
|
7582
|
+
// Contract attaches execution metadata. Coverage and costs must share
|
|
7583
|
+
// the same identity projection as the rendered Plan input.
|
|
7584
|
+
const requirements = resolveFrontendContractRequirements(contractFacts.map(record => record.fact));
|
|
7585
|
+
planRequirementIds = requirements.map(fact => fact.id);
|
|
7586
|
+
for (const fact of requirements) {
|
|
7507
7587
|
if (fact.evidence.behavior === "required")
|
|
7508
7588
|
behaviorRequiredRequirementIds.push(fact.id);
|
|
7509
7589
|
planRequirementCosts.set(fact.id, estimateFrontendPlanRequirementRecordCalls(fact));
|
|
@@ -7529,7 +7609,7 @@ export async function executeDagPiNode(input, meta, piStepFn = executePiStep, wr
|
|
|
7529
7609
|
contractDigest: meta.spec?.taskContractBinding?.canonicalHash,
|
|
7530
7610
|
},
|
|
7531
7611
|
piStepFn,
|
|
7532
|
-
sessionOptions: options.sessionOptions,
|
|
7612
|
+
sessionOptions: { ...options.sessionOptions, frontendPlanControl: options.ledgerTools.planControl },
|
|
7533
7613
|
basePrompt: options.basePrompt ?? input.prompt,
|
|
7534
7614
|
attempt: input.attempt ?? 1,
|
|
7535
7615
|
committedFactCount: () => options.ledgerTools.committedFactCount(),
|
|
@@ -790,7 +790,7 @@ export async function executePiStep(options) {
|
|
|
790
790
|
reuseRuntimeActive,
|
|
791
791
|
});
|
|
792
792
|
}
|
|
793
|
-
if (options.reserveProviderRequest || !shouldFallbackSdkToCli(sdkResult, validationIssues, options)) {
|
|
793
|
+
if (options.frontendPlanControl || options.reserveProviderRequest || !shouldFallbackSdkToCli(sdkResult, validationIssues, options)) {
|
|
794
794
|
const failureCategory = validationIssues.length > 0
|
|
795
795
|
? "invalid-output"
|
|
796
796
|
: sdkResult.failureCategory;
|
|
@@ -1104,6 +1104,11 @@ export function classifyPiFailure(input) {
|
|
|
1104
1104
|
// Provider/transport verdicts read error-shaped channels only; the raw stdout
|
|
1105
1105
|
// stream embeds assistant text, tool arguments and findings verbatim.
|
|
1106
1106
|
const combined = `${input.stderr}\n${extractProviderFailureText(input.stdout)}`.toLowerCase();
|
|
1107
|
+
// An explicit spend-budget stop is terminal even if a transport wrapper also
|
|
1108
|
+
// reports a closed connection. Read only the error channels above, never
|
|
1109
|
+
// assistant/tool prose; generic quota and context limits keep their categories.
|
|
1110
|
+
if (/(?<![\w-])(?:frontend_provider_budget_exhausted|budget_exhausted|budget_breach|budget exhausted)(?![\w-])/.test(combined))
|
|
1111
|
+
return "budget_breach";
|
|
1107
1112
|
// Context overflow (400 request-too-large / context window exceeded).
|
|
1108
1113
|
// Must be detected BEFORE the generic rate-limit/quota phrases: an overflow
|
|
1109
1114
|
// error often contains "too many tokens" / "maximum context" which would
|