@tea-agent/loop-agent 0.43.0-next.17 → 0.43.0-next.3
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 +9 -140
- package/dist/application/task-lifecycle/advance.js +4 -17
- package/dist/application/task-lifecycle/observe.js +29 -43
- package/dist/application/task-lifecycle/plan-transitions.js +4 -5
- package/dist/build-stamp.json +3 -3
- package/dist/commands/client-recovery.js +55 -20
- package/dist/commands/new-task.js +5 -0
- package/dist/commands/status.js +57 -0
- package/dist/commands/task-contract.js +269 -0
- package/dist/executors/dag-pi-executor.js +230 -269
- package/dist/executors/pi-executor.js +0 -2
- package/dist/executors/pi-sdk-executor.js +39 -77
- package/dist/executors/shell-executor.js +2 -3
- package/dist/executors/shell-write-guard.js +32 -27
- package/dist/shared/pi-context-pressure/extension.js +16 -26
- package/dist/shared/pi-retry-settings.js +0 -40
- package/dist/shared/prompts.js +26 -0
- package/dist/task/config.js +2 -0
- package/dist/task/contract/validate-draft.js +106 -0
- package/dist/task/goal.js +3 -0
- package/dist/task/index.js +12 -0
- package/dist/task/lifecycle.js +1 -0
- package/dist/task/operator/capabilities.js +6 -0
- package/dist/task/operator/envelope.js +2 -0
- package/dist/task/operator/index.js +5 -0
- package/dist/task/operator/registry.js +2 -0
- package/dist/task/operator/types.js +1 -0
- package/dist/task/paths.js +1 -0
- package/dist/task/source-prepare/source-fidelity-pi.js +2 -2
- package/dist/task/source-references.js +23 -48
- package/dist/task/source-state.js +1 -0
- package/dist/task/subagent-guidance.js +1 -0
- package/dist/worker/console/chat/assistant-content.js +2 -23
- package/dist/worker/console/chat/explore-tools.js +0 -13
- package/dist/worker/console/chat/pi-runtime.js +1182 -221
- package/dist/worker/console/chat/routes.js +71 -213
- package/dist/worker/console/chat/sdd-data-alignment.js +9 -65
- package/dist/worker/console/chat/session-catalog.js +0 -32
- package/dist/worker/console/chat/session-store.js +6 -22
- package/dist/worker/console/chat/shortcuts.js +0 -6
- package/dist/worker/console/chat/sift-bridge.js +1 -0
- package/dist/worker/console/chat/tool-preview.js +0 -11
- package/dist/worker/console/chat/tools.js +0 -6
- package/dist/worker/console/console-update-and-init.js +19 -1
- package/dist/worker/console/console-update-runtime.js +20 -5
- package/dist/worker/console/index.js +20 -0
- package/dist/worker/console/operator-actions.js +1 -13
- package/dist/worker/console/routes.js +0 -8
- package/dist/worker/console/server.js +29 -11
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-C4NXyedy.js → abnfDiagram-N423BO3Z-hUmM_GRf.js} +1 -1
- package/dist/worker/console/static/assets/{arc-NZ3qXvBI.js → arc-Bt-xuieW.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-COZSRvml.js → architectureDiagram-T3A2C74G-DTqkL4DJ.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-Ck0Gxi9C.js → blockDiagram-VBNYF7ZC-Cy0MNnzV.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-BX5HQ4Ox.js → c4Diagram-5PPSVZJV-DDdlT0Pd.js} +1 -1
- package/dist/worker/console/static/assets/channel-BpzRnZmU.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-CoviemSL.js → chunk-2GRJ4B5K-3JawwNZ6.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-DbOdOkbZ.js → chunk-2Q5K7J3B-DoTp7Cri.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-BsG2pmdQ.js → chunk-5RXB4S5H-CcirhZaS.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-DGdvrNm8.js → chunk-5VM5RSS4-VrG21gnP.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-BAy7Yd6y.js → chunk-6Q2QTUOP-DDOgIS70.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-D9UA7hUb.js → chunk-GF5L2VYU-CBjmgvJ_.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-DPz7SZ8V.js → chunk-JWPE2WC7-CCQSht8M.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-BmuEOIh7.js → chunk-KBJHAD2P-BpGMkUUV.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-CIhBWnd8.js → chunk-RYQCIY6F-DNDT41QL.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-CScR6mDD.js → chunk-XXDRQBXY-euXnDFbn.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-BhlMVqCc.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-BhlMVqCc.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-BjpfWaUr.js → cose-bilkent-JH36ORCC-BJ6NAeql.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-BFQohJgT.js → cynefin-VYW2F7L2-F5JKIvIq.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-D3gzRJnS.js → cynefinDiagram-MW4NZA55-Cwmkl_Gi.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-5eojkhoA.js → dagre-VZM6K2ZE-BmCZ8815.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-CpD8LWu_.js → diagram-7IWD3JNH-zWA3hUnX.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-B8_4qJ2a.js → diagram-B4RE2ZJO-BzA-888F.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-HOsXf0eu.js → diagram-LBJQPF4R-tPNheoch.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-BqwRwiWP.js → diagram-Q27KOJAE-Cdi7Fyqp.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-BRVCNZe1.js → diagram-UB23O5K3-DFK9VBf1.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-Cvb1TgGh.js → ebnfDiagram-BXEA7PRR-xCUr1kpg.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-DS0O0qG9.js → erDiagram-JOGREHBK-BANQHY_A.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-BHIRvRr3.js → flowDiagram-UKHOOZJN-VHJYEMkP.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-DcCJ1JWx.js → ganttDiagram-PKOTCBZU-DiyZQ0C6.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-B9ia5ony.js → gitGraphDiagram-DS77QQ5N-CZtIeBBa.js} +1 -1
- package/dist/worker/console/static/assets/index-BlN6ytLr.css +1 -0
- package/dist/worker/console/static/assets/index-CA-jfWlE.js +449 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-3ogpgegn.js → infoDiagram-6WML65LV-BuSspC0X.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-4tgpQ5SL.js → ishikawaDiagram-WSZJBQD7-DRJZyPFy.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-DNfgHqFx.js → journeyDiagram-NVQOT4AX-DIfXIVuE.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-Dz7ngC8z.js → kanban-definition-27J2QSJJ-CT10_7Lt.js} +1 -1
- package/dist/worker/console/static/assets/{linear-DI7Jv62E.js → linear-ufemj74H.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-CQA4Nzmo.js → mermaid.core-DEQe8-_m.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-CtsMc8Mf.js → mindmap-definition-FAOFIHXS-BRLUrbrC.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-ByppE9yH.js → pegDiagram-VL7TDLO6-BgibKtiF.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-B5PtgFgt.js → pieDiagram-7S7Q4E2Y-D3eyfdV9.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-Cq4p0k2K.js → quadrantDiagram-CIZ2JOQS-DXeTk5Px.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-DjfkyljN.js → railroadDiagram-AXF67PYL-BgXdFEe7.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-DzynNRSl.js → requirementDiagram-LRYGKXZP-CseC2A7R.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-Cj7TCpTJ.js → sankeyDiagram-W5VNT64P-D80PFzfJ.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-C73C6ykA.js → sequenceDiagram-SI44F4Z6-BWzHW7LS.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-BiNLgN2x.js → sizeCapture-X5ZJPWSS-xWQYTLAn.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-UwvjS64-.js → stateDiagram-OKZ733FA-CLxtpZeL.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-CiVoq4G-.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-CC7DH98r.js → swimlanes-SLNWSIFB-DwugtmWV.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-BkdgdpfR.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-LSBXTR70.js → timeline-definition-Z64GVDOM-DHEOwHIe.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-BAuxWwGP.js → vennDiagram-T6HMQDX7-pmbZKlBK.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y--E5_gjZx.js → wardleyDiagram-T6FBY63Y-CivHoM5q.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-dJWNmyzF.js → xychartDiagram-ELKLHX3M-BfHIE7o6.js} +1 -1
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +10 -122
- package/dist/worker/console/static-src/operator-chat/compaction-message.js +0 -7
- package/dist/worker/console/static-src/operator-chat/pending-user-message.js +15 -35
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +62 -136
- package/dist/worker/console/static-src/operator-chat/useChatStream.js +68 -159
- package/dist/worker/console/static-src/operator-chat/useChatThread.js +56 -75
- package/dist/worker/console/static-src/operator-chat/useComposer.js +78 -206
- package/dist/worker/console/workspace-context.js +1 -21
- package/dist/worker/observe/static/router.js +1 -1
- package/dist/worker/observe/static/styles.css +4 -7
- package/dist/worker/observe/static/views/dag-inspector.js +19 -12
- package/dist/worker/observe/static/views/dag.js +2 -2
- package/dist/worker/scheduler/index.js +25 -0
- package/dist/workflows/dag/facts.js +4 -0
- package/dist/workflows/dag/frontend-closeout.js +1 -1
- package/dist/workflows/dag/frontend-design-policy.js +1 -2
- package/dist/workflows/dag/frontend-implementation-contract.js +4 -8
- package/dist/workflows/dag/frontend-prewrite-gate.js +3 -3
- package/dist/workflows/dag/frontend-repair.js +76 -55
- package/dist/workflows/dag/frontend-review-context.js +1 -2
- package/dist/workflows/dag/frontend-review-scopes.js +12 -34
- package/dist/workflows/dag/frontend-shape-facts.js +2 -12
- package/dist/workflows/dag/frontend-verification-trace.js +42 -8
- package/dist/workflows/dag/index.js +6 -0
- package/dist/workflows/dag/init-hybrid.js +160 -97
- package/dist/workflows/dynamic/index.js +7 -0
- package/docs/architecture/runtime-boundaries.md +3 -3
- package/docs/templates/frontend-implementation-dag.json +8 -9
- package/package.json +3 -7
- package/skills/frontend-bounded-implement/SKILL.md +3 -4
- package/skills/frontend-bounded-implement/references/code-standards.md +3 -3
- package/skills/frontend-contract/references/contract-protocol.md +1 -3
- package/skills/frontend-design-review/SKILL.md +7 -12
- package/skills/frontend-design-review/references/review-checklist.md +7 -7
- package/skills/frontend-plan/SKILL.md +0 -19
- package/skills/frontend-plan/references/decision-contract.md +19 -26
- package/skills/frontend-review/SKILL.md +18 -12
- package/skills/frontend-review/references/review-findings.md +43 -24
- package/dist/shared/pi-provider-recovery.js +0 -182
- package/dist/worker/console/chat/browser-automation.js +0 -363
- package/dist/worker/console/chat/browser-policy.js +0 -113
- package/dist/worker/console/chat/browser-routes.js +0 -162
- package/dist/worker/console/chat/pi-runtime/custom-tools/ask-user-question.js +0 -44
- package/dist/worker/console/chat/pi-runtime/custom-tools/browser-tools.js +0 -89
- package/dist/worker/console/chat/pi-runtime/custom-tools/explore-tools.js +0 -202
- package/dist/worker/console/chat/pi-runtime/custom-tools/goal-tools.js +0 -79
- package/dist/worker/console/chat/pi-runtime/custom-tools/operator-tools.js +0 -86
- package/dist/worker/console/chat/pi-runtime/custom-tools/todo-write.js +0 -38
- package/dist/worker/console/chat/pi-runtime/inspection-types.js +0 -28
- package/dist/worker/console/chat/pi-runtime/inspection.js +0 -439
- package/dist/worker/console/chat/pi-runtime/sdk-bindings.js +0 -153
- package/dist/worker/console/chat/pi-runtime/sdk-session.js +0 -5
- package/dist/worker/console/chat/scm-routes.js +0 -217
- package/dist/worker/console/chat/scm-service.js +0 -283
- package/dist/worker/console/chat/scm-tools.js +0 -111
- package/dist/worker/console/chat/session-mode-view.js +0 -49
- package/dist/worker/console/chat/session-mode.js +0 -220
- package/dist/worker/console/chat/terminal-routes.js +0 -216
- package/dist/worker/console/chat/terminal-sessions.js +0 -348
- package/dist/worker/console/chat/terminal-tools.js +0 -211
- package/dist/worker/console/static/assets/channel-BxdTlb_5.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-_ByA1Nf8.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-_ByA1Nf8.js +0 -1
- package/dist/worker/console/static/assets/index-B9goN3S5.css +0 -1
- package/dist/worker/console/static/assets/index-wXlQohNs.js +0 -469
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-nLgMZ0D3.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-BcYqTady.js +0 -8
- package/dist/worker/console/static-src/operator-chat/active-turn-tail.js +0 -10
- package/dist/worker/console/static-src/operator-chat/chat-link.js +0 -94
- package/dist/worker/console/static-src/operator-chat/chat-scroll-position.js +0 -24
- package/dist/worker/console/static-src/operator-chat/composer-session-ledger.js +0 -192
- package/dist/worker/console/static-src/operator-chat/process-label.js +0 -41
- package/dist/worker/console/static-src/operator-chat/turn-group-equality.js +0 -13
- package/dist/worker/console/static-src/operator-chat/turn-process-disclosure.js +0 -10
- package/dist/worker/console/static-src/operator-chat/use-searchable-hidden.js +0 -51
- package/dist/worker/console/static-src/operator-chat/user-turn-anchor-equality.js +0 -21
- package/dist/worker/console/workspace-initialization.js +0 -209
- package/dist/workflows/dag/frontend-capacity.js +0 -9
- package/dist/workflows/dag/frontend-plan-completeness.js +0 -186
- package/dist/workflows/dag/frontend-plan-recovery-policy.js +0 -18
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
import { collectFrontendPlanMissingFacts, collectFrontendPlanPhaseMissingFacts, committedFactFromPlanRecord, planFactStringList, planFactScopeIntersects } from "../workflows/dag/frontend-plan-completeness.js";
|
|
2
|
-
export { collectFrontendPlanMissingFacts, collectFrontendPlanPhaseMissingFacts } from "../workflows/dag/frontend-plan-completeness.js";
|
|
3
|
-
import { classifyFrontendPlanRecovery } from "../workflows/dag/frontend-plan-recovery-policy.js";
|
|
4
1
|
import { collectFrontendExecutionGroups, frontendExecutionSchema } from "../workflows/dag/frontend-execution-groups.js";
|
|
5
2
|
import { FRONTEND_SCOPE_TARGET_BYTES, packFrontendInputUnits, parseFrontendInputBlock, projectFrontendContractPrompt, projectFrontendInputScope } from "../workflows/dag/frontend-input-projection.js";
|
|
6
3
|
import { createDurableFrontendTools } from "../workflows/dag/frontend-durable-tools.js";
|
|
@@ -561,7 +558,6 @@ export function resolveDagPiToolNames(task) {
|
|
|
561
558
|
if (isFrontendReviewTypedTerminalNode(task)) {
|
|
562
559
|
return [
|
|
563
560
|
...DAG_PI_READONLY_TOOLS,
|
|
564
|
-
"complete_review_scope",
|
|
565
561
|
"record_review_finding",
|
|
566
562
|
"approve_review",
|
|
567
563
|
"request_review_changes",
|
|
@@ -570,7 +566,6 @@ export function resolveDagPiToolNames(task) {
|
|
|
570
566
|
if (isFrontendDesignTypedTerminalNode(task)) {
|
|
571
567
|
return [
|
|
572
568
|
...DAG_PI_READONLY_TOOLS,
|
|
573
|
-
"complete_review_scope",
|
|
574
569
|
"record_design_finding",
|
|
575
570
|
"approve_design",
|
|
576
571
|
"request_design_changes",
|
|
@@ -597,7 +592,6 @@ export function resolveDagPiToolNames(task) {
|
|
|
597
592
|
...FRONTEND_PLAN_RECORD_TOOL_NAMES,
|
|
598
593
|
...FRONTEND_PLAN_TERMINAL_TOOL_NAMES,
|
|
599
594
|
...FRONTEND_PLAN_ADOPT_TOOL_NAMES,
|
|
600
|
-
"read_plan_facts",
|
|
601
595
|
];
|
|
602
596
|
}
|
|
603
597
|
if ((task.readSet?.length ?? 0) > 0) {
|
|
@@ -1817,44 +1811,6 @@ export async function createFrontendPlanLedgerTools(input) {
|
|
|
1817
1811
|
});
|
|
1818
1812
|
}
|
|
1819
1813
|
}
|
|
1820
|
-
// Named entries are last-wins at assembly. A scoped correction may
|
|
1821
|
-
// replace its own bindings, but must retain other scopes' coverage.
|
|
1822
|
-
const scope = new Set(scopedRequirementIds());
|
|
1823
|
-
const committed = readCommittedEvents(store, attemptId).map(event => event.fact);
|
|
1824
|
-
const targetOwners = new Map();
|
|
1825
|
-
for (const fact of committed) {
|
|
1826
|
-
if (fact.kind === "plan-verification-target" && isRecordObject(fact.entry) && typeof fact.entry.id === "string") {
|
|
1827
|
-
targetOwners.set(fact.entry.id, stringList(fact.entry.requirementIds));
|
|
1828
|
-
}
|
|
1829
|
-
}
|
|
1830
|
-
for (const [field, removals] of [["uiStates", removeUiStateNames], ["interactions", removeInteractionNames]]) {
|
|
1831
|
-
const live = new Map();
|
|
1832
|
-
for (const fact of committed) {
|
|
1833
|
-
if (fact.kind !== "state-flow")
|
|
1834
|
-
continue;
|
|
1835
|
-
for (const name of stringList(fact[field === "uiStates" ? "removeUiStateNames" : "removeInteractionNames"]))
|
|
1836
|
-
live.delete(name);
|
|
1837
|
-
for (const entry of Array.isArray(fact[field]) ? fact[field] : []) {
|
|
1838
|
-
if (isRecordObject(entry) && typeof entry.name === "string")
|
|
1839
|
-
live.set(entry.name, entry);
|
|
1840
|
-
}
|
|
1841
|
-
}
|
|
1842
|
-
const foreignBindings = (entry) => scope.size === 0 ? [] : stringList(entry.verificationTargetIds).filter(id => {
|
|
1843
|
-
const owners = targetOwners.get(id);
|
|
1844
|
-
return !owners?.length || owners.some(owner => !scope.has(owner));
|
|
1845
|
-
});
|
|
1846
|
-
for (const name of removals) {
|
|
1847
|
-
const previous = live.get(name);
|
|
1848
|
-
if (previous && foreignBindings(previous).length)
|
|
1849
|
-
return planToolReceipt({ ok: false, kind: "state-flow",
|
|
1850
|
-
error: `Cannot remove shared ${name} from this requirement scope; retain it and update its scoped bindings instead` });
|
|
1851
|
-
}
|
|
1852
|
-
for (const entry of field === "uiStates" ? uiStates : interactions) {
|
|
1853
|
-
const previous = live.get(String(entry.name));
|
|
1854
|
-
if (previous)
|
|
1855
|
-
entry.verificationTargetIds = [...new Set([...foreignBindings(previous), ...stringList(entry.verificationTargetIds)])];
|
|
1856
|
-
}
|
|
1857
|
-
}
|
|
1858
1814
|
const result = await adoptPlanFact("state-flow", `${attemptId}:record_state_flow:${randomUUID()}`, {
|
|
1859
1815
|
kind: "state-flow",
|
|
1860
1816
|
origin: "plan",
|
|
@@ -2070,7 +2026,7 @@ export async function createFrontendPlanLedgerTools(input) {
|
|
|
2070
2026
|
const recordPlanVerificationTargetTool = defineTool({
|
|
2071
2027
|
name: "record_plan_verification_target",
|
|
2072
2028
|
label: "record_plan_verification_target",
|
|
2073
|
-
description: "Commit one plan verification target entry (origin=plan plan-verification-target fact). Reference a frozen verification command by commandId (see the frozen command directory in your prompt: static commands are project-wide checks traced by file and command only; behavior commands
|
|
2029
|
+
description: "Commit one plan verification target entry (origin=plan plan-verification-target fact). Reference a frozen verification command by commandId (see the frozen command directory in your prompt: static commands are project-wide checks traced by file and command only; behavior commands need a test file whose describe/it/test title contains the target id). For behavior targets, call once per distinct behavior, not mechanically once per requirement: one target may cover multiple related requirementIds. For a correction, re-submit the same id with replace=true; the ledger compiles the latest replacement. A behavior target id is the stable trace token that implementation must place in a real describe/it/test title. Entry carries id, commandId, file, requirementIds, and uiStates; optional scope (unit | component | integration) is display-only. Free-form symbol text is not accepted. IMPORTANT: batch up to 4 record_* calls per assistant message; never batch more than 4 — a larger single message risks output truncation under a small output window. Example: {\"entry\": {\"id\": \"VT-DASHBOARD-SHELL\", \"commandId\": \"<frozen behavior command id>\", \"file\": \"<test file>\", \"requirementIds\": [\"AC-001\", \"AC-002\"], \"uiStates\": []}}" +
|
|
2074
2030
|
(input.canonicalVerificationTargetIds &&
|
|
2075
2031
|
input.canonicalVerificationTargetIds.length > 0
|
|
2076
2032
|
? ` Frozen canonical behavior target ids (use exactly for behavior targets): ${input.canonicalVerificationTargetIds.join(", ")}.`
|
|
@@ -2286,13 +2242,9 @@ export async function createFrontendPlanLedgerTools(input) {
|
|
|
2286
2242
|
const finalizePlanTool = defineTool({
|
|
2287
2243
|
name: "finalize_plan",
|
|
2288
2244
|
label: "finalize_plan",
|
|
2289
|
-
description: "Commit the finalize_plan terminal. Requirements, verification targets, and evidence gaps (optional) were already committed incrementally through record_plan_requirement / record_plan_verification_target / record_plan_evidence_gap; finalize_plan
|
|
2290
|
-
promptSnippet: "Commit finalize_plan
|
|
2245
|
+
description: "Commit the finalize_plan terminal. Requirements, verification targets, and evidence gaps (optional) were already committed incrementally through record_plan_requirement / record_plan_verification_target / record_plan_evidence_gap; finalize_plan assembles them from the ledger together with these optional remaining fields, publishes the canonical editable patch on a target-surface fact, and commits the terminal. A successful terminal commit occurs exactly once. If validation fails, correct only the reported facts and retry finalize.",
|
|
2246
|
+
promptSnippet: "Commit the finalize_plan terminal (ledger fields + optional residualRisks / realIntegrationGap).",
|
|
2291
2247
|
parameters: Type.Object({
|
|
2292
|
-
implementationSteps: Type.Optional(Type.Array(Type.Object({
|
|
2293
|
-
requirementIds: Type.Array(Type.String({ minLength: 1 }), { minItems: 1 }),
|
|
2294
|
-
action: Type.String({ minLength: 1 }),
|
|
2295
|
-
}, { additionalProperties: false }))),
|
|
2296
2248
|
residualRisks: optionalStringArray,
|
|
2297
2249
|
realIntegrationGap: optionalString,
|
|
2298
2250
|
}, { additionalProperties: false }),
|
|
@@ -2309,23 +2261,8 @@ export async function createFrontendPlanLedgerTools(input) {
|
|
|
2309
2261
|
if (missingData.length)
|
|
2310
2262
|
return planToolReceipt({ ok: false, kind: "finalize_plan", code: "PLAN_DATA_FLOW_INCOMPLETE", error: missingData.map(f => f.reason).join("; ") });
|
|
2311
2263
|
const fragment = assemblePlanPatchFromCommittedFacts(committed, contractInheritance) ?? {};
|
|
2312
|
-
const requiredIds = new Set([
|
|
2313
|
-
...(input.requirementIds ?? []),
|
|
2314
|
-
...contractInheritance.keys(),
|
|
2315
|
-
...(Array.isArray(fragment.requirements) ? fragment.requirements.flatMap((entry) => isRecordObject(entry) && typeof entry.id === "string" ? [entry.id] : []) : []),
|
|
2316
|
-
]);
|
|
2317
|
-
const steps = params?.implementationSteps ?? [];
|
|
2318
|
-
const coveredIds = new Set(steps.flatMap(step => step.requirementIds));
|
|
2319
|
-
const missingRequirementIds = [...requiredIds].filter(id => !coveredIds.has(id));
|
|
2320
|
-
const unknownRequirementIds = [...coveredIds].filter(id => !requiredIds.has(id));
|
|
2321
|
-
if (missingRequirementIds.length || unknownRequirementIds.length || steps.some(step => !step.action.trim())) {
|
|
2322
|
-
return planToolReceipt({ ok: false, kind: "finalize_plan", code: "PLAN_IMPLEMENTATION_STEPS_INCOMPLETE",
|
|
2323
|
-
error: `Submit finalize_plan with non-empty implementationSteps: [{requirementIds: [exact frozen IDs], action: "what to change and where"}]. Cover every requirement; shared steps may cover several IDs. Missing: ${missingRequirementIds.join(", ") || "none"}; unknown: ${unknownRequirementIds.join(", ") || "none"}. Actions must not be blank. Do not add a test plan.`,
|
|
2324
|
-
});
|
|
2325
|
-
}
|
|
2326
2264
|
const patch = {
|
|
2327
2265
|
...fragment,
|
|
2328
|
-
...(steps.length ? { implementationSteps: steps.map(step => `[${[...new Set(step.requirementIds)].join(", ")}] ${step.action.trim()}`) } : {}),
|
|
2329
2266
|
...(params?.residualRisks
|
|
2330
2267
|
? { residualRisks: params.residualRisks }
|
|
2331
2268
|
: {}),
|
|
@@ -2478,74 +2415,6 @@ export async function createFrontendPlanLedgerTools(input) {
|
|
|
2478
2415
|
}
|
|
2479
2416
|
},
|
|
2480
2417
|
});
|
|
2481
|
-
const readPlanFactsTool = defineTool({
|
|
2482
|
-
name: "read_plan_facts",
|
|
2483
|
-
label: "read_plan_facts",
|
|
2484
|
-
description: "Read the LIVE committed plan ledger without repository access. Filter by kind and optional entryId or eventId. Use field (dot path, e.g. entry.requirementIds or uiStates) to page large arrays/strings. offset/limit paginate results; follow nextOffset until null before replacing any full-array fact. expectedRevision pins all pages to one ledger revision; restart if it changes. No writes.",
|
|
2485
|
-
parameters: Type.Object({
|
|
2486
|
-
kind: Type.String(), entryId: Type.Optional(Type.String()), eventId: Type.Optional(Type.String()),
|
|
2487
|
-
field: Type.Optional(Type.String()), offset: Type.Optional(Type.Integer({ minimum: 0 })),
|
|
2488
|
-
limit: Type.Optional(Type.Integer({ minimum: 1, maximum: 50 })),
|
|
2489
|
-
stringOffset: Type.Optional(Type.Integer({ minimum: 0 })),
|
|
2490
|
-
expectedRevision: Type.Optional(Type.Integer({ minimum: 0 })),
|
|
2491
|
-
}, { additionalProperties: false }),
|
|
2492
|
-
async execute(_id, params) {
|
|
2493
|
-
const reply = (details) => ({ content: [{ type: "text", text: JSON.stringify(details) }], details });
|
|
2494
|
-
if (params.expectedRevision !== undefined && params.expectedRevision !== store.revision) {
|
|
2495
|
-
return reply({ ok: false, revision: store.revision, error: "ledger revision changed; restart pagination" });
|
|
2496
|
-
}
|
|
2497
|
-
let records = readCommittedEvents(store, attemptId).filter(record => {
|
|
2498
|
-
const fact = record.fact;
|
|
2499
|
-
return fact.kind === params.kind && (!params.eventId || record.eventId === params.eventId) &&
|
|
2500
|
-
(!params.entryId || fact.entry?.id === params.entryId);
|
|
2501
|
-
});
|
|
2502
|
-
// Full-entry corrections and registries are last-wins. Other kinds
|
|
2503
|
-
// remain an ordered event log (state removals must remain visible).
|
|
2504
|
-
if (params.entryId || params.kind === "state-registry")
|
|
2505
|
-
records = records.slice(-1);
|
|
2506
|
-
let values = records.map(record => ({ eventId: record.eventId, ...record.fact }));
|
|
2507
|
-
if (params.field) {
|
|
2508
|
-
values = records.flatMap(record => {
|
|
2509
|
-
let value = record.fact;
|
|
2510
|
-
for (const key of params.field.split(".")) {
|
|
2511
|
-
if (!value || typeof value !== "object" || !Object.hasOwn(value, key))
|
|
2512
|
-
return [];
|
|
2513
|
-
value = value[key];
|
|
2514
|
-
}
|
|
2515
|
-
return Array.isArray(value) ? value : [value];
|
|
2516
|
-
});
|
|
2517
|
-
}
|
|
2518
|
-
const offset = params.offset ?? 0;
|
|
2519
|
-
if (params.stringOffset !== undefined) {
|
|
2520
|
-
const value = values[offset];
|
|
2521
|
-
if (typeof value !== "string")
|
|
2522
|
-
return reply({ ok: false, error: "stringOffset requires a string field item" });
|
|
2523
|
-
// Unicode code points prevent a page boundary splitting a surrogate pair.
|
|
2524
|
-
const characters = Array.from(value);
|
|
2525
|
-
const chunk = characters.slice(params.stringOffset, params.stringOffset + 1000).join("");
|
|
2526
|
-
const next = params.stringOffset + Array.from(chunk).length;
|
|
2527
|
-
return reply({ ok: true, revision: store.revision, offset, stringOffset: params.stringOffset,
|
|
2528
|
-
chunk, totalCharacters: characters.length, nextStringOffset: next < characters.length ? next : null });
|
|
2529
|
-
}
|
|
2530
|
-
const items = [];
|
|
2531
|
-
let bytes = 0;
|
|
2532
|
-
for (const value of values.slice(offset, offset + (params.limit ?? 10))) {
|
|
2533
|
-
const size = Buffer.byteLength(JSON.stringify(value));
|
|
2534
|
-
if (bytes + size > 12_000)
|
|
2535
|
-
break;
|
|
2536
|
-
bytes += size;
|
|
2537
|
-
items.push(value);
|
|
2538
|
-
}
|
|
2539
|
-
if (!items.length && offset < values.length)
|
|
2540
|
-
return reply({
|
|
2541
|
-
ok: false, revision: store.revision, total: values.length, offset,
|
|
2542
|
-
error: "item exceeds the page budget; select an eventId/entryId and a narrower field path; for a string item set stringOffset: 0 and follow nextStringOffset",
|
|
2543
|
-
fields: typeof values[offset] === "object" && values[offset] !== null ? Object.keys(values[offset]) : [],
|
|
2544
|
-
});
|
|
2545
|
-
return reply({ ok: true, revision: store.revision, total: values.length, offset, items,
|
|
2546
|
-
nextOffset: offset + items.length < values.length ? offset + items.length : null });
|
|
2547
|
-
},
|
|
2548
|
-
});
|
|
2549
2418
|
const durable = await createDurableFrontendTools({
|
|
2550
2419
|
file: path.join(input.runDir, input.nodeId, "plan-typed-facts.jsonl"), attemptId, store: input.store,
|
|
2551
2420
|
setWorkingStore: next => { store = next; },
|
|
@@ -2569,7 +2438,7 @@ export async function createFrontendPlanLedgerTools(input) {
|
|
|
2569
2438
|
],
|
|
2570
2439
|
});
|
|
2571
2440
|
return {
|
|
2572
|
-
customTools:
|
|
2441
|
+
customTools: durable.customTools,
|
|
2573
2442
|
adoptCommittedFacts: async (records) => durable.commitExternal(async () => {
|
|
2574
2443
|
for (const record of records) {
|
|
2575
2444
|
if (record.phase !== "committed")
|
|
@@ -3763,7 +3632,7 @@ const FRONTEND_PLAN_SEGMENTS = [
|
|
|
3763
3632
|
]),
|
|
3764
3633
|
instruction: [
|
|
3765
3634
|
"PLAN PHASE — requirement coverage only.",
|
|
3766
|
-
"Your ONLY job: for every frozen requirement, emit record_plan_requirement (requirement → implementation files) and record_plan_verification_target facts (verification target bound to requirement ids and files). Group related requirements under one non-static behavior target when one observable test behavior proves them together; do not mechanically create one target per requirement.
|
|
3635
|
+
"Your ONLY job: for every frozen requirement, emit record_plan_requirement (requirement → implementation files) and record_plan_verification_target facts (verification target bound to requirement ids and files). Group related requirements under one non-static behavior target when one observable test behavior proves them together; do not mechanically create one target per requirement. A non-static target id is the stable machine trace token; never submit prose as a symbol. Do NOT record components, UI states, mock, dependency, or routes — a follow-up session owns those.",
|
|
3767
3636
|
"A coverage session is complete only when EVERY requirement assigned to this session (the full inventory, or the exact COVERAGE BATCH / shard list when present) has committed coverage facts: a record_plan_requirement entry plus verification targets, or a committed evidence gap. Keep committing in batches of up to 4 record_* calls per assistant message until then; do not write a concluding summary while any assigned requirement is still uncommitted — an early stop strands the remainder into a MISSING-FACT repair session and doubles the sessions needed.",
|
|
3768
3637
|
"If a requirement genuinely cannot have a verification target, record a non-empty record_plan_evidence_gap. Do not call finalize_plan; it is not available in this phase.",
|
|
3769
3638
|
].join(" "),
|
|
@@ -3821,10 +3690,178 @@ const FRONTEND_PLAN_SEGMENTS = [
|
|
|
3821
3690
|
toolNames: null,
|
|
3822
3691
|
instruction: [
|
|
3823
3692
|
"PLAN PHASE — finalize.",
|
|
3824
|
-
"All record_* tools are available only for a narrowly named correction if the finalize receipt reports missing or invalid facts. Otherwise call finalize_plan
|
|
3693
|
+
"All record_* tools are available only for a narrowly named correction if the finalize receipt reports missing or invalid facts. Otherwise call finalize_plan exactly once with no extra fields.",
|
|
3825
3694
|
].join(" "),
|
|
3826
3695
|
},
|
|
3827
3696
|
];
|
|
3697
|
+
function committedFactFromPlanRecord(value) {
|
|
3698
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
3699
|
+
return undefined;
|
|
3700
|
+
const record = value;
|
|
3701
|
+
if (record.phase !== undefined && record.phase !== "committed")
|
|
3702
|
+
return undefined;
|
|
3703
|
+
const fact = record.fact;
|
|
3704
|
+
return fact && typeof fact === "object" && !Array.isArray(fact)
|
|
3705
|
+
? fact
|
|
3706
|
+
: typeof record.kind === "string"
|
|
3707
|
+
? record
|
|
3708
|
+
: undefined;
|
|
3709
|
+
}
|
|
3710
|
+
function planFactStringList(value) {
|
|
3711
|
+
if (!Array.isArray(value))
|
|
3712
|
+
return [];
|
|
3713
|
+
return value.filter((item) => typeof item === "string" && item.trim().length > 0);
|
|
3714
|
+
}
|
|
3715
|
+
function planFactScopeIntersects(fact, requirementIds) {
|
|
3716
|
+
return planFactStringList(fact.scopeRequirementIds).some((id) => requirementIds.has(id));
|
|
3717
|
+
}
|
|
3718
|
+
/** Compute the authoritative coverage queue from the committed plan ledger. */
|
|
3719
|
+
export function collectFrontendPlanMissingFacts(input) {
|
|
3720
|
+
const requirements = new Map();
|
|
3721
|
+
const standaloneEvidenceGaps = new Set();
|
|
3722
|
+
const verificationTargetIds = new Set();
|
|
3723
|
+
const verificationTargetRequirements = new Map();
|
|
3724
|
+
for (const value of input.committedFacts) {
|
|
3725
|
+
const fact = committedFactFromPlanRecord(value);
|
|
3726
|
+
if (!fact || fact.origin !== "plan")
|
|
3727
|
+
continue;
|
|
3728
|
+
if (fact.kind === "plan-requirement" && fact.entry && typeof fact.entry === "object") {
|
|
3729
|
+
const entry = fact.entry;
|
|
3730
|
+
if (typeof entry.id === "string" && entry.id.trim())
|
|
3731
|
+
requirements.set(entry.id, entry);
|
|
3732
|
+
}
|
|
3733
|
+
if (fact.kind === "plan-verification-target" && fact.entry && typeof fact.entry === "object") {
|
|
3734
|
+
const entry = fact.entry;
|
|
3735
|
+
const id = entry.id;
|
|
3736
|
+
if (typeof id === "string" && id.trim()) {
|
|
3737
|
+
verificationTargetIds.add(id);
|
|
3738
|
+
verificationTargetRequirements.set(id, new Set(Array.isArray(entry.requirementIds)
|
|
3739
|
+
? entry.requirementIds.filter((value) => typeof value === "string")
|
|
3740
|
+
: []));
|
|
3741
|
+
}
|
|
3742
|
+
}
|
|
3743
|
+
if (fact.kind === "plan-evidence-gap" && fact.entry && typeof fact.entry === "object") {
|
|
3744
|
+
const entry = fact.entry;
|
|
3745
|
+
const requirementId = entry.requirementId;
|
|
3746
|
+
const description = entry.description;
|
|
3747
|
+
if (typeof requirementId === "string" && requirementId.trim() && typeof description === "string" && description.trim()) {
|
|
3748
|
+
standaloneEvidenceGaps.add(requirementId);
|
|
3749
|
+
}
|
|
3750
|
+
}
|
|
3751
|
+
}
|
|
3752
|
+
const missing = [];
|
|
3753
|
+
for (const id of input.requirementIds) {
|
|
3754
|
+
const entry = requirements.get(id);
|
|
3755
|
+
if (!entry) {
|
|
3756
|
+
missing.push({
|
|
3757
|
+
kind: "plan-requirement",
|
|
3758
|
+
id,
|
|
3759
|
+
requirementIds: [id],
|
|
3760
|
+
reason: `requirement ${id} has no committed plan-requirement fact`,
|
|
3761
|
+
});
|
|
3762
|
+
continue;
|
|
3763
|
+
}
|
|
3764
|
+
const targetIds = Array.isArray(entry.verificationTargetIds)
|
|
3765
|
+
? entry.verificationTargetIds.filter((value) => typeof value === "string" && value.trim().length > 0)
|
|
3766
|
+
: [];
|
|
3767
|
+
const gap = entry.evidenceGap && typeof entry.evidenceGap === "object"
|
|
3768
|
+
? entry.evidenceGap
|
|
3769
|
+
: undefined;
|
|
3770
|
+
const hasEvidenceGap = (typeof gap?.description === "string" && gap.description.trim().length > 0) ||
|
|
3771
|
+
standaloneEvidenceGaps.has(id);
|
|
3772
|
+
if (targetIds.length === 0 && !hasEvidenceGap) {
|
|
3773
|
+
missing.push({
|
|
3774
|
+
kind: "plan-verification-target",
|
|
3775
|
+
requirementIds: [id],
|
|
3776
|
+
reason: `requirement ${id} declares neither a verification target nor a non-empty evidenceGap`,
|
|
3777
|
+
});
|
|
3778
|
+
continue;
|
|
3779
|
+
}
|
|
3780
|
+
for (const targetId of targetIds) {
|
|
3781
|
+
if (!verificationTargetIds.has(targetId) ||
|
|
3782
|
+
!verificationTargetRequirements.get(targetId)?.has(id)) {
|
|
3783
|
+
missing.push({
|
|
3784
|
+
kind: "plan-verification-target",
|
|
3785
|
+
id: targetId,
|
|
3786
|
+
requirementIds: [id],
|
|
3787
|
+
reason: `requirement ${id} references verification target ${targetId}, but that target is not committed`,
|
|
3788
|
+
});
|
|
3789
|
+
}
|
|
3790
|
+
}
|
|
3791
|
+
}
|
|
3792
|
+
return missing;
|
|
3793
|
+
}
|
|
3794
|
+
/** Completeness checks for phases whose facts are committed incrementally. */
|
|
3795
|
+
export function collectFrontendPlanPhaseMissingFacts(input) {
|
|
3796
|
+
const facts = input.committedFacts
|
|
3797
|
+
.map(committedFactFromPlanRecord)
|
|
3798
|
+
.filter((fact) => Boolean(fact && fact.origin === "plan"));
|
|
3799
|
+
if (input.phase === "ux-registry") {
|
|
3800
|
+
return facts.some((fact) => fact.kind === "state-registry")
|
|
3801
|
+
? []
|
|
3802
|
+
: [
|
|
3803
|
+
{
|
|
3804
|
+
kind: "state-registry",
|
|
3805
|
+
requirementIds: [...input.requirementIds],
|
|
3806
|
+
reason: "global UX vocabulary phase has no committed state-registry fact",
|
|
3807
|
+
},
|
|
3808
|
+
];
|
|
3809
|
+
}
|
|
3810
|
+
if (input.phase === "ux-local") {
|
|
3811
|
+
const needsUx = input.requirementIds.some((id) => input.behaviorRequiredRequirementIds?.includes(id));
|
|
3812
|
+
if (!needsUx)
|
|
3813
|
+
return [];
|
|
3814
|
+
const requirementSlice = new Set(input.requirementIds);
|
|
3815
|
+
const scopedFacts = facts.filter((fact) => planFactScopeIntersects(fact, requirementSlice));
|
|
3816
|
+
const hasChoice = scopedFacts.some((fact) => fact.kind === "component-choice" &&
|
|
3817
|
+
Array.isArray(fact.uiComponentChoices) &&
|
|
3818
|
+
fact.uiComponentChoices.length > 0);
|
|
3819
|
+
const canonicalStateFlow = collectCanonicalStateFlowNames(scopedFacts);
|
|
3820
|
+
const hasStateFlow = canonicalStateFlow.uiStateNames.size > 0 ||
|
|
3821
|
+
canonicalStateFlow.interactionNames.size > 0;
|
|
3822
|
+
const missing = [];
|
|
3823
|
+
if (!hasChoice) {
|
|
3824
|
+
missing.push({
|
|
3825
|
+
kind: "component-choice",
|
|
3826
|
+
requirementIds: [...input.requirementIds],
|
|
3827
|
+
reason: "behaviour-required UX slice has no committed component-choice fact",
|
|
3828
|
+
});
|
|
3829
|
+
}
|
|
3830
|
+
if (!hasStateFlow) {
|
|
3831
|
+
missing.push({
|
|
3832
|
+
kind: "state-flow",
|
|
3833
|
+
requirementIds: [...input.requirementIds],
|
|
3834
|
+
reason: "behaviour-required UX slice has no committed state-flow fact",
|
|
3835
|
+
});
|
|
3836
|
+
}
|
|
3837
|
+
return missing;
|
|
3838
|
+
}
|
|
3839
|
+
const hasMockApi = facts.some((fact) => fact.kind === "mock-api");
|
|
3840
|
+
const allInteractions = collectCanonicalStateFlowNames(input.committedFacts).interactionNames;
|
|
3841
|
+
const liveInteractions = new Set([...collectCanonicalStateFlowNames(facts.filter(f => !planFactStringList(f.scopeRequirementIds).length || planFactScopeIntersects(f, new Set(input.requirementIds)))).interactionNames].filter(name => allInteractions.has(name)));
|
|
3842
|
+
const coveredInteractions = new Set(facts
|
|
3843
|
+
.filter((fact) => fact.kind === "data-flow")
|
|
3844
|
+
.flatMap((fact) => planFactStringList(fact.interactions)));
|
|
3845
|
+
const missing = [];
|
|
3846
|
+
if (!hasMockApi) {
|
|
3847
|
+
missing.push({
|
|
3848
|
+
kind: "mock-api",
|
|
3849
|
+
requirementIds: [...input.requirementIds],
|
|
3850
|
+
reason: "global Mock/data phase has no committed mock-api fact",
|
|
3851
|
+
});
|
|
3852
|
+
}
|
|
3853
|
+
for (const interaction of liveInteractions) {
|
|
3854
|
+
if (coveredInteractions.has(interaction))
|
|
3855
|
+
continue;
|
|
3856
|
+
missing.push({
|
|
3857
|
+
kind: "data-flow",
|
|
3858
|
+
id: interaction,
|
|
3859
|
+
requirementIds: [...input.requirementIds],
|
|
3860
|
+
reason: `interaction ${interaction} has no committed data-flow fact`,
|
|
3861
|
+
});
|
|
3862
|
+
}
|
|
3863
|
+
return missing;
|
|
3864
|
+
}
|
|
3828
3865
|
/** Estimate calls conservatively: requirement + one VT, with a second VT
|
|
3829
3866
|
* reserved for behaviour-required requirements. Explicit declarations win. */
|
|
3830
3867
|
export function estimateFrontendPlanRequirementRecordCalls(fact) {
|
|
@@ -3866,8 +3903,9 @@ export function batchFrontendPlanRequirements(input) {
|
|
|
3866
3903
|
}
|
|
3867
3904
|
const FRONTEND_PLAN_COVERAGE_MAX_RECORD_CALLS = 10;
|
|
3868
3905
|
const FRONTEND_PLAN_COVERAGE_MAX_CONCURRENCY = 4;
|
|
3869
|
-
// A
|
|
3870
|
-
//
|
|
3906
|
+
// A large requirement set creates parallel coverage shards; UX remains one
|
|
3907
|
+
// global decision session so behavior names and component/state facts are not
|
|
3908
|
+
// reinvented per AC batch. Keep a safety bound for adaptive coverage retries
|
|
3871
3909
|
// without letting the old 32-session ceiling skip finalize.
|
|
3872
3910
|
const FRONTEND_PLAN_BATCH_MAX_SESSIONS = 128;
|
|
3873
3911
|
const FRONTEND_PLAN_SMALL_MAX_REQUIREMENTS = 8;
|
|
@@ -4008,28 +4046,26 @@ export function compactFrontendPlanPromptForRequirementSlice(basePrompt, require
|
|
|
4008
4046
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
4009
4047
|
return [];
|
|
4010
4048
|
const target = value;
|
|
4011
|
-
|
|
4012
|
-
return [];
|
|
4013
|
-
const targetRequirementIds = planFactStringList(target.requirementIds);
|
|
4049
|
+
const targetRequirementIds = compactPromptStringArray(target.requirementIds, 20, 80);
|
|
4014
4050
|
const relatedRequirementIds = targetRequirementIds.filter((id) => sliceSet.has(id));
|
|
4015
4051
|
if (relatedRequirementIds.length === 0)
|
|
4016
4052
|
return [];
|
|
4017
4053
|
return [
|
|
4018
4054
|
{
|
|
4019
|
-
...(
|
|
4020
|
-
? { id: target.id }
|
|
4055
|
+
...(compactPromptString(target.id, 80)
|
|
4056
|
+
? { id: compactPromptString(target.id, 80) }
|
|
4021
4057
|
: {}),
|
|
4022
|
-
...(
|
|
4023
|
-
? { commandId: target.commandId }
|
|
4058
|
+
...(compactPromptString(target.commandId, 80)
|
|
4059
|
+
? { commandId: compactPromptString(target.commandId, 80) }
|
|
4024
4060
|
: {}),
|
|
4025
4061
|
...(compactPromptString(target.commandLabel, 180)
|
|
4026
4062
|
? { commandLabel: compactPromptString(target.commandLabel, 180) }
|
|
4027
4063
|
: {}),
|
|
4028
|
-
...(
|
|
4029
|
-
? { file: target.file }
|
|
4064
|
+
...(compactPromptString(target.file, 180)
|
|
4065
|
+
? { file: compactPromptString(target.file, 180) }
|
|
4030
4066
|
: {}),
|
|
4031
4067
|
requirementIds: relatedRequirementIds,
|
|
4032
|
-
uiStates:
|
|
4068
|
+
uiStates: compactPromptStringArray(target.uiStates, 12, 100),
|
|
4033
4069
|
},
|
|
4034
4070
|
];
|
|
4035
4071
|
})
|
|
@@ -4081,7 +4117,7 @@ export function compactFrontendPlanPromptForRequirementSlice(basePrompt, require
|
|
|
4081
4117
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
4082
4118
|
return [];
|
|
4083
4119
|
const state = value;
|
|
4084
|
-
const id =
|
|
4120
|
+
const id = compactPromptString(state.id, 80);
|
|
4085
4121
|
if (!id)
|
|
4086
4122
|
return [];
|
|
4087
4123
|
return [{
|
|
@@ -4181,9 +4217,9 @@ function compactFrontendPlanLedgerContext(input) {
|
|
|
4181
4217
|
compactFacts.push({
|
|
4182
4218
|
kind: fact.kind,
|
|
4183
4219
|
entry: {
|
|
4184
|
-
id: entry.id,
|
|
4185
|
-
implementationTargets:
|
|
4186
|
-
verificationTargetIds:
|
|
4220
|
+
id: compactPromptString(entry.id, 80),
|
|
4221
|
+
implementationTargets: compactPromptStringArray(entry.implementationTargets, 12, 180),
|
|
4222
|
+
verificationTargetIds: compactPromptStringArray(entry.verificationTargetIds, 12, 80),
|
|
4187
4223
|
...(compactPromptString(entry.expectedOutcome, 240)
|
|
4188
4224
|
? { expectedOutcome: compactPromptString(entry.expectedOutcome, 240) }
|
|
4189
4225
|
: {}),
|
|
@@ -4197,19 +4233,17 @@ function compactFrontendPlanLedgerContext(input) {
|
|
|
4197
4233
|
if (fact.kind === "plan-verification-target") {
|
|
4198
4234
|
if (!entry)
|
|
4199
4235
|
continue;
|
|
4200
|
-
|
|
4201
|
-
|
|
4202
|
-
const requirementIds = planFactStringList(entry.requirementIds).filter((id) => slice.has(id));
|
|
4203
|
-
if (requirementIds.length === 0)
|
|
4236
|
+
const requirementIds = compactPromptStringArray(entry.requirementIds, 20, 80);
|
|
4237
|
+
if (!requirementIds.some((id) => slice.has(id)))
|
|
4204
4238
|
continue;
|
|
4205
4239
|
compactFacts.push({
|
|
4206
4240
|
kind: fact.kind,
|
|
4207
4241
|
entry: {
|
|
4208
|
-
id: entry.id,
|
|
4209
|
-
commandId: entry.commandId,
|
|
4210
|
-
file: entry.file,
|
|
4242
|
+
id: compactPromptString(entry.id, 80),
|
|
4243
|
+
commandId: compactPromptString(entry.commandId, 80),
|
|
4244
|
+
file: compactPromptString(entry.file, 180),
|
|
4211
4245
|
requirementIds,
|
|
4212
|
-
uiStates:
|
|
4246
|
+
uiStates: compactPromptStringArray(entry.uiStates, 12, 100),
|
|
4213
4247
|
},
|
|
4214
4248
|
});
|
|
4215
4249
|
continue;
|
|
@@ -4236,8 +4270,8 @@ function compactFrontendPlanLedgerContext(input) {
|
|
|
4236
4270
|
if (fact.kind === "state-registry") {
|
|
4237
4271
|
compactFacts.push({
|
|
4238
4272
|
kind: fact.kind,
|
|
4239
|
-
uiStateNames:
|
|
4240
|
-
interactionNames:
|
|
4273
|
+
uiStateNames: compactPromptStringArray(fact.uiStateNames, 40, 100),
|
|
4274
|
+
interactionNames: compactPromptStringArray(fact.interactionNames, 60, 120),
|
|
4241
4275
|
});
|
|
4242
4276
|
continue;
|
|
4243
4277
|
}
|
|
@@ -4289,21 +4323,13 @@ function compactFrontendPlanLedgerContext(input) {
|
|
|
4289
4323
|
return "";
|
|
4290
4324
|
const priorityFacts = compactFacts.filter((fact) => fact.kind === "plan-requirement" || fact.kind === "plan-verification-target");
|
|
4291
4325
|
const otherFacts = compactFacts.filter((fact) => fact.kind !== "plan-requirement" && fact.kind !== "plan-verification-target");
|
|
4292
|
-
let previewBytes = 0;
|
|
4293
4326
|
const boundedFacts = [
|
|
4294
|
-
...priorityFacts
|
|
4295
|
-
...otherFacts
|
|
4296
|
-
]
|
|
4297
|
-
const bytes = Buffer.byteLength(JSON.stringify(fact));
|
|
4298
|
-
if (previewBytes + bytes > 24_000)
|
|
4299
|
-
return false;
|
|
4300
|
-
previewBytes += bytes;
|
|
4301
|
-
return true;
|
|
4302
|
-
});
|
|
4327
|
+
...priorityFacts,
|
|
4328
|
+
...otherFacts,
|
|
4329
|
+
];
|
|
4303
4330
|
return [
|
|
4304
4331
|
"<frontend_plan_ledger>",
|
|
4305
|
-
"
|
|
4306
|
-
JSON.stringify({ complete: false, matchingFacts: compactFacts.length, omittedFacts: compactFacts.length - boundedFacts.length }),
|
|
4332
|
+
"Committed plan facts from earlier sessions. Treat these as authoritative; correct them only with the allowed replacement/removal fields.",
|
|
4307
4333
|
JSON.stringify(boundedFacts),
|
|
4308
4334
|
"</frontend_plan_ledger>",
|
|
4309
4335
|
].join("\n");
|
|
@@ -4337,14 +4363,12 @@ export async function runFrontendReviewSegmentedSessions(input) {
|
|
|
4337
4363
|
index--;
|
|
4338
4364
|
continue;
|
|
4339
4365
|
}
|
|
4340
|
-
const completionInstruction = `${item.repairs ? "REPAIR: the prior session did not commit all required checkpoints/verdict. Do not repeat the review narrative. " : ""}Complete these exact runtime scope IDs using complete_review_scope: ${JSON.stringify(scopes.map(scope => scope.id))}. These are scope IDs, not node IDs. ${finalScope ? `After those checkpoints succeed, call exactly one terminal tool: ${terminalKinds.join(" or ")}. A prose conclusion is not a committed verdict.` : "More scopes remain; do not submit an overall verdict yet."}`;
|
|
4341
|
-
const userMessage = `${input.sessionOptions.userMessage}\n\n${completionInstruction}`;
|
|
4342
4366
|
if (++calls > 128)
|
|
4343
4367
|
return { ...last, ok: false, failureCategory: "frontend-input-exhausted", stderr: "FRONTEND_REVIEW_RECOVERY_EXHAUSTED: session quota reached" };
|
|
4344
|
-
last = await observeFrontendSession({ ...input.observation, phase: `${input.phase}/scope`, scopeIds: scopes.map(s => s.id), prompt, userMessage, customTools,
|
|
4368
|
+
last = await observeFrontendSession({ ...input.observation, phase: `${input.phase}/scope`, scopeIds: scopes.map(s => s.id), prompt, userMessage: input.sessionOptions.userMessage, customTools,
|
|
4345
4369
|
artifactPath: input.observation?.artifactPath ? `${input.observation.artifactPath}-${calls}.json` : undefined,
|
|
4346
4370
|
committedCount: () => protocol.committedFacts().length, durableCommittedCount: () => protocol.committedFacts().length,
|
|
4347
|
-
}, observer => input.piStepFn({ ...input.sessionOptions, prompt,
|
|
4371
|
+
}, observer => input.piStepFn({ ...input.sessionOptions, prompt, onAttemptObservation: observer, writerToolPolicy: { requireSdk: true, customTools } }));
|
|
4348
4372
|
await input.tools.flush();
|
|
4349
4373
|
await input.inventory.validate();
|
|
4350
4374
|
if (last.timedOut || /interrupt|termination-unconfirmed|budget_breach/.test(last.failureCategory))
|
|
@@ -4533,18 +4557,16 @@ function buildFrontendPlanWorkload(input) {
|
|
|
4533
4557
|
const workCost = (ids) => 1 + ids.reduce((total, id) => total + Math.max(1, (input.requirementCosts?.get(id) ?? 2) - 1), 0);
|
|
4534
4558
|
const policy = input.sessionOptions.frontendExecutionPolicy;
|
|
4535
4559
|
const targetBytes = policy?.scopeTargetBytes ?? FRONTEND_SCOPE_TARGET_BYTES;
|
|
4560
|
+
const userMessageBytes = Buffer.byteLength(input.sessionOptions.userMessage ?? "");
|
|
4536
4561
|
const buildWorkBatches = (ids) => {
|
|
4537
4562
|
const work = workGroups.flatMap((g, index) => {
|
|
4538
4563
|
const members = g.requirementIds.filter(id => ids.includes(id));
|
|
4539
4564
|
return members.length ? [{ id: `${index}:${g.id}`, requirementIds: members,
|
|
4540
4565
|
requirements: members.map(id => fullUnits.get(id) ?? { id }), estimatedCalls: workCost(members) }] : [];
|
|
4541
4566
|
});
|
|
4542
|
-
|
|
4543
|
-
// fixed context/tools can leave a one-byte budget and force one AC per
|
|
4544
|
-
// session without reducing that overhead. The SDK checks the actual
|
|
4545
|
-
// request against model capacity; capacity recovery splits unfinished work.
|
|
4567
|
+
const scaffoldBytes = Buffer.byteLength(input.basePrompt.replace(/<frontend_plan_input>[\s\S]*?<\/frontend_plan_input>/, JSON.stringify({ ...compiledInput, requirements: [] }))) + userMessageBytes + Buffer.byteLength(JSON.stringify(input.coverageTools));
|
|
4546
4568
|
return packFrontendInputUnits(work, {
|
|
4547
|
-
targetBytes,
|
|
4569
|
+
targetBytes: Math.max(1, targetBytes - scaffoldBytes),
|
|
4548
4570
|
maxUnits: policy?.maxScopeUnits ?? 4,
|
|
4549
4571
|
maxCost: FRONTEND_PLAN_COVERAGE_MAX_RECORD_CALLS, cost: g => g.estimatedCalls,
|
|
4550
4572
|
}).map(batch => batch.flatMap(g => g.requirementIds));
|
|
@@ -4556,6 +4578,7 @@ function buildFrontendPlanWorkload(input) {
|
|
|
4556
4578
|
frozenInput, workGroups, buildWorkBatches,
|
|
4557
4579
|
compactEligible: input.requirementCosts !== undefined &&
|
|
4558
4580
|
workGroups.length <= FRONTEND_PLAN_SMALL_MAX_REQUIREMENTS &&
|
|
4581
|
+
Buffer.byteLength(input.basePrompt) + userMessageBytes + Buffer.byteLength(JSON.stringify(input.allTools)) <= targetBytes * 2 &&
|
|
4559
4582
|
workGroups.reduce((total, group) => total + workCost(group.requirementIds), 0) <= FRONTEND_PLAN_SMALL_MAX_ESTIMATED_CALLS &&
|
|
4560
4583
|
countFrontendPlanTargetSurfaces(input.basePrompt) === 1,
|
|
4561
4584
|
};
|
|
@@ -4628,7 +4651,7 @@ export async function runFrontendPlanSegmentedSessions(input) {
|
|
|
4628
4651
|
const buildPhasePrompt = (segment, missing = [], scopeIds = allRequirementIds) => {
|
|
4629
4652
|
const compact = allRequirementIds.length > 0
|
|
4630
4653
|
? compactFrontendPlanPromptForRequirementSlice(input.basePrompt, scopeIds, {
|
|
4631
|
-
includeRequirementText: segment.id === "global-mock-data"
|
|
4654
|
+
includeRequirementText: segment.id === "global-mock-data",
|
|
4632
4655
|
includeVerificationTargets: false,
|
|
4633
4656
|
includeDesignEvidence: segment.id === "global-dependency-deviation" || segment.id === "finalize",
|
|
4634
4657
|
includeChecklist: false,
|
|
@@ -4684,7 +4707,7 @@ export async function runFrontendPlanSegmentedSessions(input) {
|
|
|
4684
4707
|
"component-choice",
|
|
4685
4708
|
"state-flow",
|
|
4686
4709
|
],
|
|
4687
|
-
|
|
4710
|
+
scopedKinds: ["state-flow"],
|
|
4688
4711
|
})
|
|
4689
4712
|
: "";
|
|
4690
4713
|
return [
|
|
@@ -4807,6 +4830,8 @@ export async function runFrontendPlanSegmentedSessions(input) {
|
|
|
4807
4830
|
const { workGroups, buildWorkBatches, compactEligible } = buildFrontendPlanWorkload({
|
|
4808
4831
|
basePrompt: input.basePrompt, requirementIds: allRequirementIds,
|
|
4809
4832
|
requirementCosts: input.requirementCosts, sessionOptions: input.sessionOptions,
|
|
4833
|
+
coverageTools: input.segmentCustomTools(coverageSegment.toolNames),
|
|
4834
|
+
allTools: input.segmentCustomTools(null),
|
|
4810
4835
|
});
|
|
4811
4836
|
// Small, single-surface requests do not benefit from six isolated Pi
|
|
4812
4837
|
// sessions. Keep the typed ledger as the authority, but let one local
|
|
@@ -4901,27 +4926,6 @@ export async function runFrontendPlanSegmentedSessions(input) {
|
|
|
4901
4926
|
let lastDurableCount = input.committedFactCount();
|
|
4902
4927
|
while (index < queue.length) {
|
|
4903
4928
|
const session = queue[index];
|
|
4904
|
-
if (input.attempt > 1 && input.committedFacts && session.id === "ux-registry" &&
|
|
4905
|
-
collectFrontendPlanPhaseMissingFacts({ phase: "ux-registry", requirementIds: allRequirementIds, committedFacts: input.committedFacts() }).length === 0) {
|
|
4906
|
-
index += 1;
|
|
4907
|
-
continue;
|
|
4908
|
-
}
|
|
4909
|
-
// Reuse complete UX scopes on node retry. Require explicit per-requirement
|
|
4910
|
-
// ownership; an unrelated/global fact must not prove a slice complete.
|
|
4911
|
-
if (input.attempt > 1 && input.committedFacts && session.id.startsWith("ux-local-") && session.requirementSlice?.length) {
|
|
4912
|
-
const facts = input.committedFacts();
|
|
4913
|
-
const ownedIds = new Set(facts.flatMap(value => {
|
|
4914
|
-
const fact = committedFactFromPlanRecord(value);
|
|
4915
|
-
return fact ? planFactStringList(fact.scopeRequirementIds) : [];
|
|
4916
|
-
}));
|
|
4917
|
-
const complete = session.requirementSlice.every(id => ownedIds.has(id)) &&
|
|
4918
|
-
collectFrontendPlanPhaseMissingFacts({ phase: "ux-local", requirementIds: session.requirementSlice,
|
|
4919
|
-
committedFacts: facts, behaviorRequiredRequirementIds: input.behaviorRequiredRequirementIds }).length === 0;
|
|
4920
|
-
if (complete) {
|
|
4921
|
-
index += 1;
|
|
4922
|
-
continue;
|
|
4923
|
-
}
|
|
4924
|
-
}
|
|
4925
4929
|
const remaining = (session.coverageOnly ? session.coverageSlice ?? [] : []).filter((id) => !input.committedRequirementIds?.().has(id));
|
|
4926
4930
|
const preexistingMissing = session.coverageOnly && session.coverageSlice && input.committedFacts
|
|
4927
4931
|
? collectFrontendPlanMissingFacts({
|
|
@@ -4967,30 +4971,23 @@ export async function runFrontendPlanSegmentedSessions(input) {
|
|
|
4967
4971
|
: buildPhasePrompt(segment, session.missingFacts, session.requirementSlice ?? allRequirementIds);
|
|
4968
4972
|
}
|
|
4969
4973
|
}
|
|
4970
|
-
const atomicFocus = session.atomicRecovery ? session.missingFacts?.[0] : undefined;
|
|
4971
|
-
if (atomicFocus) {
|
|
4972
|
-
prompt = [
|
|
4973
|
-
compactFrontendPlanPromptForRequirementSlice(input.basePrompt, atomicFocus.requirementIds, {
|
|
4974
|
-
includeChecklist: false,
|
|
4975
|
-
...(atomicFocus.kind === "plan-verification-target" && atomicFocus.id
|
|
4976
|
-
? { verificationTargetIds: [atomicFocus.id] } : {}),
|
|
4977
|
-
}),
|
|
4978
|
-
`ATOMIC FACT: ${atomicFocus.kind}${atomicFocus.id ? ` ${atomicFocus.id}` : ""}`,
|
|
4979
|
-
atomicFocus.reason,
|
|
4980
|
-
"Commit ONLY this missing fact with one record_* call, then end this session. Other facts are queued separately. Preserve canonical IDs and shared bindings; use read_plan_facts for existing values. Do not summarize or plan the entire requirement. Capacity exhaustion is not evidence of a requirement gap.",
|
|
4981
|
-
].join("\n\n");
|
|
4982
|
-
}
|
|
4983
4974
|
const committedBefore = input.committedFactCount();
|
|
4984
4975
|
input.setActiveRequirementScope?.(session.coverageOnly ? session.coverageSlice ?? [] : session.requirementSlice ?? []);
|
|
4985
4976
|
if (invocationCount >= FRONTEND_PLAN_BATCH_MAX_SESSIONS)
|
|
4986
4977
|
break;
|
|
4987
4978
|
invocationCount += 1;
|
|
4988
|
-
const
|
|
4989
|
-
|
|
4990
|
-
|
|
4991
|
-
|
|
4992
|
-
|
|
4993
|
-
|
|
4979
|
+
const customTools = input.segmentCustomTools(session.toolNames);
|
|
4980
|
+
const scopeForPacking = session.coverageSlice ?? session.requirementSlice;
|
|
4981
|
+
const envelopeBytes = Buffer.byteLength(prompt) + Buffer.byteLength(input.sessionOptions.userMessage ?? "") + Buffer.byteLength(JSON.stringify(customTools));
|
|
4982
|
+
if (envelopeBytes > (input.sessionOptions.frontendExecutionPolicy?.scopeTargetBytes ?? FRONTEND_SCOPE_TARGET_BYTES) && scopeForPacking && session.id !== "compact-local") {
|
|
4983
|
+
const groups = workGroups.map(g => g.requirementIds.filter(id => scopeForPacking.includes(id))).filter(g => g.length);
|
|
4984
|
+
if (groups.length > 1) {
|
|
4985
|
+
const half = Math.ceil(groups.length / 2);
|
|
4986
|
+
queue.splice(index, 1, ...[groups.slice(0, half).flat(), groups.slice(half).flat()].map(slice => ({ ...session, ...(session.coverageOnly ? { coverageSlice: slice } : { requirementSlice: slice }) })));
|
|
4987
|
+
invocationCount -= 1;
|
|
4988
|
+
continue;
|
|
4989
|
+
}
|
|
4990
|
+
}
|
|
4994
4991
|
const result = await observeFrontendSession({
|
|
4995
4992
|
...input.observation, phase: `plan/${session.id}`, scopeIds: session.requirementSlice ?? session.coverageSlice ?? allRequirementIds,
|
|
4996
4993
|
prompt, userMessage: input.sessionOptions.userMessage, customTools, committedCount: input.committedFactCount, durableCommittedCount: () => lastDurableCount,
|
|
@@ -5108,47 +5105,9 @@ export async function runFrontendPlanSegmentedSessions(input) {
|
|
|
5108
5105
|
: isUxLocalSession
|
|
5109
5106
|
? buildUxPrompt(session.requirementSlice ?? [], missing)
|
|
5110
5107
|
: buildPhasePrompt(globalMockDataSegment, missing, session.requirementSlice ?? allRequirementIds);
|
|
5111
|
-
|
|
5112
|
-
if (recovery === "stop")
|
|
5108
|
+
if (result.timedOut || /interrupt|termination-unconfirmed|budget_breach/.test(result.failureCategory))
|
|
5113
5109
|
return { ...result, ok: false };
|
|
5114
|
-
|
|
5115
|
-
!(missingPhaseFacts.length === 1 && missingPhaseFacts[0].kind === "plan-requirement") &&
|
|
5116
|
-
(session.coverageOnly || isUxRegistrySession || isUxLocalSession || session.id.startsWith("global-mock-data"))) {
|
|
5117
|
-
if (missingPhaseFacts.length === 0) {
|
|
5118
|
-
// A complete validated slice does not need a successful prose turn.
|
|
5119
|
-
// This never accepts finalize or bypasses the final contract validator.
|
|
5120
|
-
accumulated = { ...accumulated, ok: true, failureCategory: "success" };
|
|
5121
|
-
index += 1;
|
|
5122
|
-
continue;
|
|
5123
|
-
}
|
|
5124
|
-
const missingIds = [...new Set(missingPhaseFacts.flatMap(item => item.requirementIds))];
|
|
5125
|
-
const scope = session.coverageSlice ?? session.requirementSlice;
|
|
5126
|
-
if (scope && missingIds.length < scope.length) {
|
|
5127
|
-
queue[index] = { ...session, missingFacts: missingPhaseFacts,
|
|
5128
|
-
...(session.coverageOnly ? { coverageSlice: missingIds } : { requirementSlice: missingIds }),
|
|
5129
|
-
retryCount: 0 };
|
|
5130
|
-
continue;
|
|
5131
|
-
}
|
|
5132
|
-
if (scope && missingIds.length > 1 && (session.coverageOnly || isUxLocalSession)) {
|
|
5133
|
-
const half = Math.ceil(missingIds.length / 2);
|
|
5134
|
-
queue.splice(index, 1, ...[missingIds.slice(0, half), missingIds.slice(half)].map(ids => ({
|
|
5135
|
-
...session, retryCount: 0,
|
|
5136
|
-
...(session.coverageOnly ? { coverageSlice: ids } : { requirementSlice: ids }),
|
|
5137
|
-
missingFacts: missingPhaseFacts.filter(item => item.requirementIds.some(id => ids.includes(id))),
|
|
5138
|
-
})));
|
|
5139
|
-
continue;
|
|
5140
|
-
}
|
|
5141
|
-
const focusStillMissing = atomicFocus && missingPhaseFacts.some(item => item.kind === atomicFocus.kind && item.id === atomicFocus.id &&
|
|
5142
|
-
item.requirementIds.join("\0") === atomicFocus.requirementIds.join("\0"));
|
|
5143
|
-
const retries = focusStillMissing ? (session.retryCount ?? 0) + 1 : 0;
|
|
5144
|
-
if (retries < 2) {
|
|
5145
|
-
queue[index] = { ...session, atomicRecovery: true, missingFacts: missingPhaseFacts, retryCount: retries };
|
|
5146
|
-
continue;
|
|
5147
|
-
}
|
|
5148
|
-
return { ...accumulated, ok: false, failureCategory: OUTPUT_LIMIT_RETRY_CATEGORY,
|
|
5149
|
-
stderr: `${accumulated.stderr}\nfrontend plan capacity recovery exhausted: preserve committed facts; phase=${session.id}; scope=${missingIds.join(",")}; strategy=atomic-fact; missing=${JSON.stringify(missingPhaseFacts)}`.trim() };
|
|
5150
|
-
}
|
|
5151
|
-
const capacityExhausted = recovery === "output" || recovery === "context";
|
|
5110
|
+
const capacityExhausted = readWriterThinkingExhaustionEvidence(result).stopReason === "length" || ["context-overflow", "context-budget-exhausted"].includes(result.failureCategory);
|
|
5152
5111
|
if (capacityExhausted && !result.timedOut) {
|
|
5153
5112
|
const failure = () => mapPlannerExhaustion({ ...result, ok: false, stderr: `${result.stderr}\nFRONTEND_INPUT_UNIT_TOO_LARGE: ${session.id}; no smaller complete scope can finish; unchanged retries are disabled` }, committedAfter > committedBefore);
|
|
5154
5113
|
const missingCoverageIds = input.committedFacts ? [...new Set(collectFrontendPlanMissingFacts({ requirementIds: session.coverageSlice ?? session.requirementSlice ?? allRequirementIds, committedFacts: input.committedFacts() }).flatMap(f => f.requirementIds))] : [...(session.coverageSlice ?? session.requirementSlice ?? allRequirementIds)];
|
|
@@ -5254,7 +5213,7 @@ export async function runFrontendPlanSegmentedSessions(input) {
|
|
|
5254
5213
|
: undefined;
|
|
5255
5214
|
const zeroProgressBurn = coverageSlice !== undefined &&
|
|
5256
5215
|
coverageSlice.length > 1 &&
|
|
5257
|
-
(
|
|
5216
|
+
(readWriterThinkingExhaustionEvidence(result).stopReason === "length" || ["context-overflow", "context-budget-exhausted"].includes(result.failureCategory) || (committedAfter === committedBefore && !(result.assistantText ?? "").trim() && !result.stderr.trim())) &&
|
|
5258
5217
|
!result.timedOut;
|
|
5259
5218
|
if (zeroProgressBurn && coverageSlice) {
|
|
5260
5219
|
const missingIds = input.committedFacts ? new Set(collectFrontendPlanMissingFacts({ requirementIds: coverageSlice, committedFacts: input.committedFacts() }).flatMap(f => f.requirementIds)) : undefined;
|
|
@@ -5974,7 +5933,6 @@ export async function executeDagPiNode(input, meta, piStepFn = executePiStep, wr
|
|
|
5974
5933
|
frontendExecutionPolicy: meta.spec?.frontendExecutionPolicy,
|
|
5975
5934
|
attachedFiles: [],
|
|
5976
5935
|
modelConfig,
|
|
5977
|
-
outputLimitRecovery: { terminalToolNames: ["finalize_plan", "finalize_contract", "approve_design", "request_design_changes", "approve_review", "request_review_changes"] },
|
|
5978
5936
|
repoRoot: input.cwd,
|
|
5979
5937
|
step,
|
|
5980
5938
|
toolNames,
|
|
@@ -6093,13 +6051,16 @@ export async function executeDagPiNode(input, meta, piStepFn = executePiStep, wr
|
|
|
6093
6051
|
? options.ledgerTools.customTools
|
|
6094
6052
|
: options.ledgerTools.customTools.filter((tool) => typeof tool === "object" &&
|
|
6095
6053
|
tool !== null &&
|
|
6096
|
-
|
|
6054
|
+
toolNames.has(tool.name)),
|
|
6097
6055
|
flushLedger: () => options.ledgerTools.flush(),
|
|
6098
6056
|
});
|
|
6099
6057
|
if (planRequirementIds.length > 1) {
|
|
6058
|
+
const coverageTools = FRONTEND_PLAN_SEGMENTS.find(segment => segment.id === "coverage").toolNames;
|
|
6100
6059
|
const workload = buildFrontendPlanWorkload({
|
|
6101
6060
|
basePrompt: input.prompt, requirementIds: planRequirementIds,
|
|
6102
6061
|
requirementCosts: planRequirementCosts, sessionOptions: piSessionOptions,
|
|
6062
|
+
allTools: planLedgerTools.customTools,
|
|
6063
|
+
coverageTools: planLedgerTools.customTools.filter(tool => isRecordObject(tool) && coverageTools.has(String(tool.name))),
|
|
6103
6064
|
});
|
|
6104
6065
|
let coverageBatches;
|
|
6105
6066
|
try {
|