@tea-agent/loop-agent 0.43.0-next.4 → 0.43.0-next.6
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 +82 -9
- package/dist/application/task-lifecycle/advance.js +17 -4
- package/dist/application/task-lifecycle/observe.js +43 -29
- package/dist/application/task-lifecycle/plan-transitions.js +5 -4
- package/dist/build-stamp.json +3 -3
- package/dist/executors/dag-pi-executor.js +233 -202
- package/dist/executors/pi-executor.js +2 -0
- package/dist/executors/pi-sdk-executor.js +77 -39
- package/dist/task/source-prepare/source-fidelity-pi.js +2 -2
- package/dist/task/source-references.js +48 -23
- package/dist/worker/console/chat/pi-runtime.js +113 -95
- package/dist/worker/console/chat/routes.js +164 -54
- package/dist/worker/console/chat/scm-routes.js +193 -0
- package/dist/worker/console/chat/scm-service.js +253 -0
- package/dist/worker/console/chat/scm-tools.js +111 -0
- package/dist/worker/console/chat/session-catalog.js +1 -0
- package/dist/worker/console/chat/session-mode-view.js +48 -0
- package/dist/worker/console/chat/session-mode.js +218 -0
- package/dist/worker/console/chat/session-store.js +22 -6
- package/dist/worker/console/chat/shortcuts.js +6 -0
- package/dist/worker/console/chat/tools.js +1 -0
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-CCj36aaC.js → abnfDiagram-N423BO3Z-BuyrlONK.js} +1 -1
- package/dist/worker/console/static/assets/{arc-Cl3MIVFh.js → arc-BvCl_wpi.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-C_n4kMJS.js → architectureDiagram-T3A2C74G-BkBpBaRq.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-DlWYJ7cK.js → blockDiagram-VBNYF7ZC-yve4Mcb_.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-Ud5pS7vS.js → c4Diagram-5PPSVZJV-9yLGboBx.js} +1 -1
- package/dist/worker/console/static/assets/channel-BRahIhjw.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-CQPwOv_j.js → chunk-2GRJ4B5K-BKP69b8r.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-CMzjxrWI.js → chunk-2Q5K7J3B-NWFRnXju.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-pcnXnD6t.js → chunk-5RXB4S5H-BiweqLEc.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-CEB3AvU2.js → chunk-5VM5RSS4-CP9GHmRY.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-DSKnbXbp.js → chunk-6Q2QTUOP-BiGDkBtj.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-CS6-PjCZ.js → chunk-GF5L2VYU-DkJ4ZjQS.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-f6q8Tnzz.js → chunk-JWPE2WC7-B5jjn7zq.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-DkB0QX3q.js → chunk-KBJHAD2P-C6KoK9O0.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-BdB4jAv6.js → chunk-RYQCIY6F-BvNIfyn-.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-BVm1bnX6.js → chunk-XXDRQBXY-BZ33iX4W.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-GOC80mhq.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-GOC80mhq.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-FV_P-IEp.js → cose-bilkent-JH36ORCC-CP8eIuZd.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-BM7RtkA2.js → cynefin-VYW2F7L2-DZECjpOe.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-C1BZwTG5.js → cynefinDiagram-MW4NZA55-CvVJoFqf.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-C6McNKmY.js → dagre-VZM6K2ZE-D_JTqdAn.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-C0aWRUaE.js → diagram-7IWD3JNH-Db7AQ0z0.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-Bfk_SKE8.js → diagram-B4RE2ZJO-AZPVo5CZ.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-prx1T4qf.js → diagram-LBJQPF4R-BgXqI3E6.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-DBvty0eU.js → diagram-Q27KOJAE-BYjFn3Ba.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-CF0em8e9.js → diagram-UB23O5K3-B4rJgGGO.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-CF90b4Ws.js → ebnfDiagram-BXEA7PRR-C6XWZhpw.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-hfunxzof.js → erDiagram-JOGREHBK-1YdQRIQW.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-D-QSy4Nl.js → flowDiagram-UKHOOZJN-l3a0AwN0.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-DaBGNcvT.js → ganttDiagram-PKOTCBZU-CQL96F2Z.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-BChcubGL.js → gitGraphDiagram-DS77QQ5N-BxCvafRb.js} +1 -1
- package/dist/worker/console/static/assets/index-4wCGsXuU.js +465 -0
- package/dist/worker/console/static/assets/index-CiF9WBoR.css +1 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-BkC67fIa.js → infoDiagram-6WML65LV-BUipIeDO.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-ibhLczCD.js → ishikawaDiagram-WSZJBQD7-CRfomVMX.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-DCRMitay.js → journeyDiagram-NVQOT4AX-_UFvyviX.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-DJ_hKaYI.js → kanban-definition-27J2QSJJ-CKIRoqKv.js} +1 -1
- package/dist/worker/console/static/assets/{linear-CIlLsYsA.js → linear-CjttJDCM.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-C-5IQMP_.js → mermaid.core-DPvHX155.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-TCzV5TQT.js → mindmap-definition-FAOFIHXS-Qmj-qwBe.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-bfhd2BQH.js → pegDiagram-VL7TDLO6-Auw6DRgq.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-BxFRAm5N.js → pieDiagram-7S7Q4E2Y-CqMrfD4c.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-C-m12lxv.js → quadrantDiagram-CIZ2JOQS-BAddeggH.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-CwmLiGgx.js → railroadDiagram-AXF67PYL-DG1JuOu2.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-oc0Q1jiD.js → requirementDiagram-LRYGKXZP-DLYGMKlC.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-9Ght6Yur.js → sankeyDiagram-W5VNT64P-g68SbLM9.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-ndm3lyz9.js → sequenceDiagram-SI44F4Z6-CmszLKv8.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-7sPlKVcw.js → sizeCapture-X5ZJPWSS-D2tEKUCj.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-Izl21-oS.js → stateDiagram-OKZ733FA-Syc5E_Jf.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-CwWwuPI-.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-CtO3D65o.js → swimlanes-SLNWSIFB-Ibn0IRCa.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-A7fqemr6.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-Cyg5cY7z.js → timeline-definition-Z64GVDOM-2ZW9W3iS.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-CwtE_TFK.js → vennDiagram-T6HMQDX7-v50HsKUh.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-Dtt9v8OO.js → wardleyDiagram-T6FBY63Y-DqCXEoDH.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-B2Y3ryb6.js → xychartDiagram-ELKLHX3M-vIP8Vo9d.js} +1 -1
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +20 -4
- package/dist/worker/console/static-src/operator-chat/useComposer.js +13 -7
- package/dist/worker/console/workspace-context.js +6 -0
- package/dist/workflows/dag/frontend-capacity.js +9 -0
- package/dist/workflows/dag/frontend-design-policy.js +2 -1
- package/dist/workflows/dag/frontend-plan-completeness.js +186 -0
- package/dist/workflows/dag/frontend-plan-recovery-policy.js +18 -0
- package/dist/workflows/dag/frontend-shape-facts.js +12 -2
- package/package.json +1 -1
- package/skills/frontend-plan/SKILL.md +7 -0
- package/dist/worker/console/static/assets/channel-CEMRpNm-.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-C9lhaZkR.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-C9lhaZkR.js +0 -1
- package/dist/worker/console/static/assets/index-CJkCY1XR.js +0 -464
- package/dist/worker/console/static/assets/index-DUBCbiqv.css +0 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-CmElLFpP.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-DKRbGG8V.js +0 -8
|
@@ -1,3 +1,6 @@
|
|
|
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";
|
|
1
4
|
import { collectFrontendExecutionGroups, frontendExecutionSchema } from "../workflows/dag/frontend-execution-groups.js";
|
|
2
5
|
import { FRONTEND_SCOPE_TARGET_BYTES, packFrontendInputUnits, parseFrontendInputBlock, projectFrontendContractPrompt, projectFrontendInputScope } from "../workflows/dag/frontend-input-projection.js";
|
|
3
6
|
import { createDurableFrontendTools } from "../workflows/dag/frontend-durable-tools.js";
|
|
@@ -592,6 +595,7 @@ export function resolveDagPiToolNames(task) {
|
|
|
592
595
|
...FRONTEND_PLAN_RECORD_TOOL_NAMES,
|
|
593
596
|
...FRONTEND_PLAN_TERMINAL_TOOL_NAMES,
|
|
594
597
|
...FRONTEND_PLAN_ADOPT_TOOL_NAMES,
|
|
598
|
+
"read_plan_facts",
|
|
595
599
|
];
|
|
596
600
|
}
|
|
597
601
|
if ((task.readSet?.length ?? 0) > 0) {
|
|
@@ -1811,6 +1815,44 @@ export async function createFrontendPlanLedgerTools(input) {
|
|
|
1811
1815
|
});
|
|
1812
1816
|
}
|
|
1813
1817
|
}
|
|
1818
|
+
// Named entries are last-wins at assembly. A scoped correction may
|
|
1819
|
+
// replace its own bindings, but must retain other scopes' coverage.
|
|
1820
|
+
const scope = new Set(scopedRequirementIds());
|
|
1821
|
+
const committed = readCommittedEvents(store, attemptId).map(event => event.fact);
|
|
1822
|
+
const targetOwners = new Map();
|
|
1823
|
+
for (const fact of committed) {
|
|
1824
|
+
if (fact.kind === "plan-verification-target" && isRecordObject(fact.entry) && typeof fact.entry.id === "string") {
|
|
1825
|
+
targetOwners.set(fact.entry.id, stringList(fact.entry.requirementIds));
|
|
1826
|
+
}
|
|
1827
|
+
}
|
|
1828
|
+
for (const [field, removals] of [["uiStates", removeUiStateNames], ["interactions", removeInteractionNames]]) {
|
|
1829
|
+
const live = new Map();
|
|
1830
|
+
for (const fact of committed) {
|
|
1831
|
+
if (fact.kind !== "state-flow")
|
|
1832
|
+
continue;
|
|
1833
|
+
for (const name of stringList(fact[field === "uiStates" ? "removeUiStateNames" : "removeInteractionNames"]))
|
|
1834
|
+
live.delete(name);
|
|
1835
|
+
for (const entry of Array.isArray(fact[field]) ? fact[field] : []) {
|
|
1836
|
+
if (isRecordObject(entry) && typeof entry.name === "string")
|
|
1837
|
+
live.set(entry.name, entry);
|
|
1838
|
+
}
|
|
1839
|
+
}
|
|
1840
|
+
const foreignBindings = (entry) => scope.size === 0 ? [] : stringList(entry.verificationTargetIds).filter(id => {
|
|
1841
|
+
const owners = targetOwners.get(id);
|
|
1842
|
+
return !owners?.length || owners.some(owner => !scope.has(owner));
|
|
1843
|
+
});
|
|
1844
|
+
for (const name of removals) {
|
|
1845
|
+
const previous = live.get(name);
|
|
1846
|
+
if (previous && foreignBindings(previous).length)
|
|
1847
|
+
return planToolReceipt({ ok: false, kind: "state-flow",
|
|
1848
|
+
error: `Cannot remove shared ${name} from this requirement scope; retain it and update its scoped bindings instead` });
|
|
1849
|
+
}
|
|
1850
|
+
for (const entry of field === "uiStates" ? uiStates : interactions) {
|
|
1851
|
+
const previous = live.get(String(entry.name));
|
|
1852
|
+
if (previous)
|
|
1853
|
+
entry.verificationTargetIds = [...new Set([...foreignBindings(previous), ...stringList(entry.verificationTargetIds)])];
|
|
1854
|
+
}
|
|
1855
|
+
}
|
|
1814
1856
|
const result = await adoptPlanFact("state-flow", `${attemptId}:record_state_flow:${randomUUID()}`, {
|
|
1815
1857
|
kind: "state-flow",
|
|
1816
1858
|
origin: "plan",
|
|
@@ -2415,6 +2457,74 @@ export async function createFrontendPlanLedgerTools(input) {
|
|
|
2415
2457
|
}
|
|
2416
2458
|
},
|
|
2417
2459
|
});
|
|
2460
|
+
const readPlanFactsTool = defineTool({
|
|
2461
|
+
name: "read_plan_facts",
|
|
2462
|
+
label: "read_plan_facts",
|
|
2463
|
+
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.",
|
|
2464
|
+
parameters: Type.Object({
|
|
2465
|
+
kind: Type.String(), entryId: Type.Optional(Type.String()), eventId: Type.Optional(Type.String()),
|
|
2466
|
+
field: Type.Optional(Type.String()), offset: Type.Optional(Type.Integer({ minimum: 0 })),
|
|
2467
|
+
limit: Type.Optional(Type.Integer({ minimum: 1, maximum: 50 })),
|
|
2468
|
+
stringOffset: Type.Optional(Type.Integer({ minimum: 0 })),
|
|
2469
|
+
expectedRevision: Type.Optional(Type.Integer({ minimum: 0 })),
|
|
2470
|
+
}, { additionalProperties: false }),
|
|
2471
|
+
async execute(_id, params) {
|
|
2472
|
+
const reply = (details) => ({ content: [{ type: "text", text: JSON.stringify(details) }], details });
|
|
2473
|
+
if (params.expectedRevision !== undefined && params.expectedRevision !== store.revision) {
|
|
2474
|
+
return reply({ ok: false, revision: store.revision, error: "ledger revision changed; restart pagination" });
|
|
2475
|
+
}
|
|
2476
|
+
let records = readCommittedEvents(store, attemptId).filter(record => {
|
|
2477
|
+
const fact = record.fact;
|
|
2478
|
+
return fact.kind === params.kind && (!params.eventId || record.eventId === params.eventId) &&
|
|
2479
|
+
(!params.entryId || fact.entry?.id === params.entryId);
|
|
2480
|
+
});
|
|
2481
|
+
// Full-entry corrections and registries are last-wins. Other kinds
|
|
2482
|
+
// remain an ordered event log (state removals must remain visible).
|
|
2483
|
+
if (params.entryId || params.kind === "state-registry")
|
|
2484
|
+
records = records.slice(-1);
|
|
2485
|
+
let values = records.map(record => ({ eventId: record.eventId, ...record.fact }));
|
|
2486
|
+
if (params.field) {
|
|
2487
|
+
values = records.flatMap(record => {
|
|
2488
|
+
let value = record.fact;
|
|
2489
|
+
for (const key of params.field.split(".")) {
|
|
2490
|
+
if (!value || typeof value !== "object" || !Object.hasOwn(value, key))
|
|
2491
|
+
return [];
|
|
2492
|
+
value = value[key];
|
|
2493
|
+
}
|
|
2494
|
+
return Array.isArray(value) ? value : [value];
|
|
2495
|
+
});
|
|
2496
|
+
}
|
|
2497
|
+
const offset = params.offset ?? 0;
|
|
2498
|
+
if (params.stringOffset !== undefined) {
|
|
2499
|
+
const value = values[offset];
|
|
2500
|
+
if (typeof value !== "string")
|
|
2501
|
+
return reply({ ok: false, error: "stringOffset requires a string field item" });
|
|
2502
|
+
// Unicode code points prevent a page boundary splitting a surrogate pair.
|
|
2503
|
+
const characters = Array.from(value);
|
|
2504
|
+
const chunk = characters.slice(params.stringOffset, params.stringOffset + 1000).join("");
|
|
2505
|
+
const next = params.stringOffset + Array.from(chunk).length;
|
|
2506
|
+
return reply({ ok: true, revision: store.revision, offset, stringOffset: params.stringOffset,
|
|
2507
|
+
chunk, totalCharacters: characters.length, nextStringOffset: next < characters.length ? next : null });
|
|
2508
|
+
}
|
|
2509
|
+
const items = [];
|
|
2510
|
+
let bytes = 0;
|
|
2511
|
+
for (const value of values.slice(offset, offset + (params.limit ?? 10))) {
|
|
2512
|
+
const size = Buffer.byteLength(JSON.stringify(value));
|
|
2513
|
+
if (bytes + size > 12_000)
|
|
2514
|
+
break;
|
|
2515
|
+
bytes += size;
|
|
2516
|
+
items.push(value);
|
|
2517
|
+
}
|
|
2518
|
+
if (!items.length && offset < values.length)
|
|
2519
|
+
return reply({
|
|
2520
|
+
ok: false, revision: store.revision, total: values.length, offset,
|
|
2521
|
+
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",
|
|
2522
|
+
fields: typeof values[offset] === "object" && values[offset] !== null ? Object.keys(values[offset]) : [],
|
|
2523
|
+
});
|
|
2524
|
+
return reply({ ok: true, revision: store.revision, total: values.length, offset, items,
|
|
2525
|
+
nextOffset: offset + items.length < values.length ? offset + items.length : null });
|
|
2526
|
+
},
|
|
2527
|
+
});
|
|
2418
2528
|
const durable = await createDurableFrontendTools({
|
|
2419
2529
|
file: path.join(input.runDir, input.nodeId, "plan-typed-facts.jsonl"), attemptId, store: input.store,
|
|
2420
2530
|
setWorkingStore: next => { store = next; },
|
|
@@ -2438,7 +2548,7 @@ export async function createFrontendPlanLedgerTools(input) {
|
|
|
2438
2548
|
],
|
|
2439
2549
|
});
|
|
2440
2550
|
return {
|
|
2441
|
-
customTools: durable.customTools,
|
|
2551
|
+
customTools: [...durable.customTools, { ...readPlanFactsTool, execute: async (...args) => { await durable.flush(); return readPlanFactsTool.execute(...args); } }],
|
|
2442
2552
|
adoptCommittedFacts: async (records) => durable.commitExternal(async () => {
|
|
2443
2553
|
for (const record of records) {
|
|
2444
2554
|
if (record.phase !== "committed")
|
|
@@ -3694,174 +3804,6 @@ const FRONTEND_PLAN_SEGMENTS = [
|
|
|
3694
3804
|
].join(" "),
|
|
3695
3805
|
},
|
|
3696
3806
|
];
|
|
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
|
-
}
|
|
3865
3807
|
/** Estimate calls conservatively: requirement + one VT, with a second VT
|
|
3866
3808
|
* reserved for behaviour-required requirements. Explicit declarations win. */
|
|
3867
3809
|
export function estimateFrontendPlanRequirementRecordCalls(fact) {
|
|
@@ -3903,9 +3845,8 @@ export function batchFrontendPlanRequirements(input) {
|
|
|
3903
3845
|
}
|
|
3904
3846
|
const FRONTEND_PLAN_COVERAGE_MAX_RECORD_CALLS = 10;
|
|
3905
3847
|
const FRONTEND_PLAN_COVERAGE_MAX_CONCURRENCY = 4;
|
|
3906
|
-
// A
|
|
3907
|
-
//
|
|
3908
|
-
// reinvented per AC batch. Keep a safety bound for adaptive coverage retries
|
|
3848
|
+
// A single registry owns global names; large UX work uses bounded serial
|
|
3849
|
+
// scopes against that registry. Keep a safety bound for adaptive retries
|
|
3909
3850
|
// without letting the old 32-session ceiling skip finalize.
|
|
3910
3851
|
const FRONTEND_PLAN_BATCH_MAX_SESSIONS = 128;
|
|
3911
3852
|
const FRONTEND_PLAN_SMALL_MAX_REQUIREMENTS = 8;
|
|
@@ -4046,26 +3987,28 @@ export function compactFrontendPlanPromptForRequirementSlice(basePrompt, require
|
|
|
4046
3987
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
4047
3988
|
return [];
|
|
4048
3989
|
const target = value;
|
|
4049
|
-
|
|
3990
|
+
if (options.verificationTargetIds && !options.verificationTargetIds.includes(String(target.id)))
|
|
3991
|
+
return [];
|
|
3992
|
+
const targetRequirementIds = planFactStringList(target.requirementIds);
|
|
4050
3993
|
const relatedRequirementIds = targetRequirementIds.filter((id) => sliceSet.has(id));
|
|
4051
3994
|
if (relatedRequirementIds.length === 0)
|
|
4052
3995
|
return [];
|
|
4053
3996
|
return [
|
|
4054
3997
|
{
|
|
4055
|
-
...(
|
|
4056
|
-
? { id:
|
|
3998
|
+
...(typeof target.id === "string"
|
|
3999
|
+
? { id: target.id }
|
|
4057
4000
|
: {}),
|
|
4058
|
-
...(
|
|
4059
|
-
? { commandId:
|
|
4001
|
+
...(typeof target.commandId === "string"
|
|
4002
|
+
? { commandId: target.commandId }
|
|
4060
4003
|
: {}),
|
|
4061
4004
|
...(compactPromptString(target.commandLabel, 180)
|
|
4062
4005
|
? { commandLabel: compactPromptString(target.commandLabel, 180) }
|
|
4063
4006
|
: {}),
|
|
4064
|
-
...(
|
|
4065
|
-
? { file:
|
|
4007
|
+
...(typeof target.file === "string"
|
|
4008
|
+
? { file: target.file }
|
|
4066
4009
|
: {}),
|
|
4067
4010
|
requirementIds: relatedRequirementIds,
|
|
4068
|
-
uiStates:
|
|
4011
|
+
uiStates: planFactStringList(target.uiStates),
|
|
4069
4012
|
},
|
|
4070
4013
|
];
|
|
4071
4014
|
})
|
|
@@ -4117,7 +4060,7 @@ export function compactFrontendPlanPromptForRequirementSlice(basePrompt, require
|
|
|
4117
4060
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
4118
4061
|
return [];
|
|
4119
4062
|
const state = value;
|
|
4120
|
-
const id =
|
|
4063
|
+
const id = typeof state.id === "string" ? state.id : undefined;
|
|
4121
4064
|
if (!id)
|
|
4122
4065
|
return [];
|
|
4123
4066
|
return [{
|
|
@@ -4217,9 +4160,9 @@ function compactFrontendPlanLedgerContext(input) {
|
|
|
4217
4160
|
compactFacts.push({
|
|
4218
4161
|
kind: fact.kind,
|
|
4219
4162
|
entry: {
|
|
4220
|
-
id:
|
|
4221
|
-
implementationTargets:
|
|
4222
|
-
verificationTargetIds:
|
|
4163
|
+
id: entry.id,
|
|
4164
|
+
implementationTargets: planFactStringList(entry.implementationTargets).slice(0, 12),
|
|
4165
|
+
verificationTargetIds: planFactStringList(entry.verificationTargetIds).slice(0, 12),
|
|
4223
4166
|
...(compactPromptString(entry.expectedOutcome, 240)
|
|
4224
4167
|
? { expectedOutcome: compactPromptString(entry.expectedOutcome, 240) }
|
|
4225
4168
|
: {}),
|
|
@@ -4233,17 +4176,19 @@ function compactFrontendPlanLedgerContext(input) {
|
|
|
4233
4176
|
if (fact.kind === "plan-verification-target") {
|
|
4234
4177
|
if (!entry)
|
|
4235
4178
|
continue;
|
|
4236
|
-
|
|
4237
|
-
|
|
4179
|
+
// Scope against the complete canonical binding before compacting.
|
|
4180
|
+
// A shared target may bind a late requirement beyond the preview cap.
|
|
4181
|
+
const requirementIds = planFactStringList(entry.requirementIds).filter((id) => slice.has(id));
|
|
4182
|
+
if (requirementIds.length === 0)
|
|
4238
4183
|
continue;
|
|
4239
4184
|
compactFacts.push({
|
|
4240
4185
|
kind: fact.kind,
|
|
4241
4186
|
entry: {
|
|
4242
|
-
id:
|
|
4243
|
-
commandId:
|
|
4244
|
-
file:
|
|
4187
|
+
id: entry.id,
|
|
4188
|
+
commandId: entry.commandId,
|
|
4189
|
+
file: entry.file,
|
|
4245
4190
|
requirementIds,
|
|
4246
|
-
uiStates:
|
|
4191
|
+
uiStates: planFactStringList(entry.uiStates).slice(0, 12),
|
|
4247
4192
|
},
|
|
4248
4193
|
});
|
|
4249
4194
|
continue;
|
|
@@ -4270,8 +4215,8 @@ function compactFrontendPlanLedgerContext(input) {
|
|
|
4270
4215
|
if (fact.kind === "state-registry") {
|
|
4271
4216
|
compactFacts.push({
|
|
4272
4217
|
kind: fact.kind,
|
|
4273
|
-
uiStateNames:
|
|
4274
|
-
interactionNames:
|
|
4218
|
+
uiStateNames: planFactStringList(fact.uiStateNames).slice(0, 40),
|
|
4219
|
+
interactionNames: planFactStringList(fact.interactionNames).slice(0, 60),
|
|
4275
4220
|
});
|
|
4276
4221
|
continue;
|
|
4277
4222
|
}
|
|
@@ -4323,13 +4268,21 @@ function compactFrontendPlanLedgerContext(input) {
|
|
|
4323
4268
|
return "";
|
|
4324
4269
|
const priorityFacts = compactFacts.filter((fact) => fact.kind === "plan-requirement" || fact.kind === "plan-verification-target");
|
|
4325
4270
|
const otherFacts = compactFacts.filter((fact) => fact.kind !== "plan-requirement" && fact.kind !== "plan-verification-target");
|
|
4271
|
+
let previewBytes = 0;
|
|
4326
4272
|
const boundedFacts = [
|
|
4327
|
-
...priorityFacts,
|
|
4328
|
-
...otherFacts,
|
|
4329
|
-
]
|
|
4273
|
+
...priorityFacts.slice(0, 64),
|
|
4274
|
+
...otherFacts.slice(-32),
|
|
4275
|
+
].slice(0, 96).filter(fact => {
|
|
4276
|
+
const bytes = Buffer.byteLength(JSON.stringify(fact));
|
|
4277
|
+
if (previewBytes + bytes > 24_000)
|
|
4278
|
+
return false;
|
|
4279
|
+
previewBytes += bytes;
|
|
4280
|
+
return true;
|
|
4281
|
+
});
|
|
4330
4282
|
return [
|
|
4331
4283
|
"<frontend_plan_ledger>",
|
|
4332
|
-
"
|
|
4284
|
+
"Preview of committed plan facts; not a replacement payload. Fields and lists may be omitted or shortened. Use read_plan_facts with kind, entryId/eventId and optional field to retrieve complete values in bounded pages before corrections; preserve all other bindings. Absence from this preview is not absence from the ledger.",
|
|
4285
|
+
JSON.stringify({ complete: false, matchingFacts: compactFacts.length, omittedFacts: compactFacts.length - boundedFacts.length }),
|
|
4333
4286
|
JSON.stringify(boundedFacts),
|
|
4334
4287
|
"</frontend_plan_ledger>",
|
|
4335
4288
|
].join("\n");
|
|
@@ -4707,7 +4660,7 @@ export async function runFrontendPlanSegmentedSessions(input) {
|
|
|
4707
4660
|
"component-choice",
|
|
4708
4661
|
"state-flow",
|
|
4709
4662
|
],
|
|
4710
|
-
|
|
4663
|
+
// Include shared state bindings from other scopes; page full values before correcting.
|
|
4711
4664
|
})
|
|
4712
4665
|
: "";
|
|
4713
4666
|
return [
|
|
@@ -4926,6 +4879,27 @@ export async function runFrontendPlanSegmentedSessions(input) {
|
|
|
4926
4879
|
let lastDurableCount = input.committedFactCount();
|
|
4927
4880
|
while (index < queue.length) {
|
|
4928
4881
|
const session = queue[index];
|
|
4882
|
+
if (input.attempt > 1 && input.committedFacts && session.id === "ux-registry" &&
|
|
4883
|
+
collectFrontendPlanPhaseMissingFacts({ phase: "ux-registry", requirementIds: allRequirementIds, committedFacts: input.committedFacts() }).length === 0) {
|
|
4884
|
+
index += 1;
|
|
4885
|
+
continue;
|
|
4886
|
+
}
|
|
4887
|
+
// Reuse complete UX scopes on node retry. Require explicit per-requirement
|
|
4888
|
+
// ownership; an unrelated/global fact must not prove a slice complete.
|
|
4889
|
+
if (input.attempt > 1 && input.committedFacts && session.id.startsWith("ux-local-") && session.requirementSlice?.length) {
|
|
4890
|
+
const facts = input.committedFacts();
|
|
4891
|
+
const ownedIds = new Set(facts.flatMap(value => {
|
|
4892
|
+
const fact = committedFactFromPlanRecord(value);
|
|
4893
|
+
return fact ? planFactStringList(fact.scopeRequirementIds) : [];
|
|
4894
|
+
}));
|
|
4895
|
+
const complete = session.requirementSlice.every(id => ownedIds.has(id)) &&
|
|
4896
|
+
collectFrontendPlanPhaseMissingFacts({ phase: "ux-local", requirementIds: session.requirementSlice,
|
|
4897
|
+
committedFacts: facts, behaviorRequiredRequirementIds: input.behaviorRequiredRequirementIds }).length === 0;
|
|
4898
|
+
if (complete) {
|
|
4899
|
+
index += 1;
|
|
4900
|
+
continue;
|
|
4901
|
+
}
|
|
4902
|
+
}
|
|
4929
4903
|
const remaining = (session.coverageOnly ? session.coverageSlice ?? [] : []).filter((id) => !input.committedRequirementIds?.().has(id));
|
|
4930
4904
|
const preexistingMissing = session.coverageOnly && session.coverageSlice && input.committedFacts
|
|
4931
4905
|
? collectFrontendPlanMissingFacts({
|
|
@@ -4971,12 +4945,30 @@ export async function runFrontendPlanSegmentedSessions(input) {
|
|
|
4971
4945
|
: buildPhasePrompt(segment, session.missingFacts, session.requirementSlice ?? allRequirementIds);
|
|
4972
4946
|
}
|
|
4973
4947
|
}
|
|
4948
|
+
const atomicFocus = session.atomicRecovery ? session.missingFacts?.[0] : undefined;
|
|
4949
|
+
if (atomicFocus) {
|
|
4950
|
+
prompt = [
|
|
4951
|
+
compactFrontendPlanPromptForRequirementSlice(input.basePrompt, atomicFocus.requirementIds, {
|
|
4952
|
+
includeChecklist: false,
|
|
4953
|
+
...(atomicFocus.kind === "plan-verification-target" && atomicFocus.id
|
|
4954
|
+
? { verificationTargetIds: [atomicFocus.id] } : {}),
|
|
4955
|
+
}),
|
|
4956
|
+
`ATOMIC FACT: ${atomicFocus.kind}${atomicFocus.id ? ` ${atomicFocus.id}` : ""}`,
|
|
4957
|
+
atomicFocus.reason,
|
|
4958
|
+
"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.",
|
|
4959
|
+
].join("\n\n");
|
|
4960
|
+
}
|
|
4974
4961
|
const committedBefore = input.committedFactCount();
|
|
4975
4962
|
input.setActiveRequirementScope?.(session.coverageOnly ? session.coverageSlice ?? [] : session.requirementSlice ?? []);
|
|
4976
4963
|
if (invocationCount >= FRONTEND_PLAN_BATCH_MAX_SESSIONS)
|
|
4977
4964
|
break;
|
|
4978
4965
|
invocationCount += 1;
|
|
4979
|
-
const
|
|
4966
|
+
const atomicTools = {
|
|
4967
|
+
"plan-requirement": "record_plan_requirement", "plan-verification-target": "record_plan_verification_target",
|
|
4968
|
+
"state-registry": "record_state_registry", "component-choice": "record_component_choice",
|
|
4969
|
+
"state-flow": "record_state_flow", "data-flow": "record_data_flow", "mock-api": "record_mock_api",
|
|
4970
|
+
};
|
|
4971
|
+
const customTools = input.segmentCustomTools(atomicFocus ? new Set([atomicTools[atomicFocus.kind]]) : session.toolNames);
|
|
4980
4972
|
const scopeForPacking = session.coverageSlice ?? session.requirementSlice;
|
|
4981
4973
|
const envelopeBytes = Buffer.byteLength(prompt) + Buffer.byteLength(input.sessionOptions.userMessage ?? "") + Buffer.byteLength(JSON.stringify(customTools));
|
|
4982
4974
|
if (envelopeBytes > (input.sessionOptions.frontendExecutionPolicy?.scopeTargetBytes ?? FRONTEND_SCOPE_TARGET_BYTES) && scopeForPacking && session.id !== "compact-local") {
|
|
@@ -5105,9 +5097,47 @@ export async function runFrontendPlanSegmentedSessions(input) {
|
|
|
5105
5097
|
: isUxLocalSession
|
|
5106
5098
|
? buildUxPrompt(session.requirementSlice ?? [], missing)
|
|
5107
5099
|
: buildPhasePrompt(globalMockDataSegment, missing, session.requirementSlice ?? allRequirementIds);
|
|
5108
|
-
|
|
5100
|
+
const recovery = classifyFrontendPlanRecovery({ ...result, stopReason: readWriterThinkingExhaustionEvidence(result).stopReason });
|
|
5101
|
+
if (recovery === "stop")
|
|
5109
5102
|
return { ...result, ok: false };
|
|
5110
|
-
|
|
5103
|
+
if ((recovery === "output" || (session.atomicRecovery && result.ok)) && input.committedFacts &&
|
|
5104
|
+
!(missingPhaseFacts.length === 1 && missingPhaseFacts[0].kind === "plan-requirement") &&
|
|
5105
|
+
(session.coverageOnly || isUxRegistrySession || isUxLocalSession || session.id.startsWith("global-mock-data"))) {
|
|
5106
|
+
if (missingPhaseFacts.length === 0) {
|
|
5107
|
+
// A complete validated slice does not need a successful prose turn.
|
|
5108
|
+
// This never accepts finalize or bypasses the final contract validator.
|
|
5109
|
+
accumulated = { ...accumulated, ok: true, failureCategory: "success" };
|
|
5110
|
+
index += 1;
|
|
5111
|
+
continue;
|
|
5112
|
+
}
|
|
5113
|
+
const missingIds = [...new Set(missingPhaseFacts.flatMap(item => item.requirementIds))];
|
|
5114
|
+
const scope = session.coverageSlice ?? session.requirementSlice;
|
|
5115
|
+
if (scope && missingIds.length < scope.length) {
|
|
5116
|
+
queue[index] = { ...session, missingFacts: missingPhaseFacts,
|
|
5117
|
+
...(session.coverageOnly ? { coverageSlice: missingIds } : { requirementSlice: missingIds }),
|
|
5118
|
+
retryCount: 0 };
|
|
5119
|
+
continue;
|
|
5120
|
+
}
|
|
5121
|
+
if (scope && missingIds.length > 1 && (session.coverageOnly || isUxLocalSession)) {
|
|
5122
|
+
const half = Math.ceil(missingIds.length / 2);
|
|
5123
|
+
queue.splice(index, 1, ...[missingIds.slice(0, half), missingIds.slice(half)].map(ids => ({
|
|
5124
|
+
...session, retryCount: 0,
|
|
5125
|
+
...(session.coverageOnly ? { coverageSlice: ids } : { requirementSlice: ids }),
|
|
5126
|
+
missingFacts: missingPhaseFacts.filter(item => item.requirementIds.some(id => ids.includes(id))),
|
|
5127
|
+
})));
|
|
5128
|
+
continue;
|
|
5129
|
+
}
|
|
5130
|
+
const focusStillMissing = atomicFocus && missingPhaseFacts.some(item => item.kind === atomicFocus.kind && item.id === atomicFocus.id &&
|
|
5131
|
+
item.requirementIds.join("\0") === atomicFocus.requirementIds.join("\0"));
|
|
5132
|
+
const retries = focusStillMissing ? (session.retryCount ?? 0) + 1 : 0;
|
|
5133
|
+
if (retries < 2) {
|
|
5134
|
+
queue[index] = { ...session, atomicRecovery: true, missingFacts: missingPhaseFacts, retryCount: retries };
|
|
5135
|
+
continue;
|
|
5136
|
+
}
|
|
5137
|
+
return { ...accumulated, ok: false, failureCategory: OUTPUT_LIMIT_RETRY_CATEGORY,
|
|
5138
|
+
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() };
|
|
5139
|
+
}
|
|
5140
|
+
const capacityExhausted = recovery === "output" || recovery === "context";
|
|
5111
5141
|
if (capacityExhausted && !result.timedOut) {
|
|
5112
5142
|
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);
|
|
5113
5143
|
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)];
|
|
@@ -5213,7 +5243,7 @@ export async function runFrontendPlanSegmentedSessions(input) {
|
|
|
5213
5243
|
: undefined;
|
|
5214
5244
|
const zeroProgressBurn = coverageSlice !== undefined &&
|
|
5215
5245
|
coverageSlice.length > 1 &&
|
|
5216
|
-
(
|
|
5246
|
+
(capacityExhausted || (["empty-output", "unknown"].includes(result.failureCategory) && committedAfter === committedBefore && !(result.assistantText ?? "").trim() && !result.stderr.trim())) &&
|
|
5217
5247
|
!result.timedOut;
|
|
5218
5248
|
if (zeroProgressBurn && coverageSlice) {
|
|
5219
5249
|
const missingIds = input.committedFacts ? new Set(collectFrontendPlanMissingFacts({ requirementIds: coverageSlice, committedFacts: input.committedFacts() }).flatMap(f => f.requirementIds)) : undefined;
|
|
@@ -5933,6 +5963,7 @@ export async function executeDagPiNode(input, meta, piStepFn = executePiStep, wr
|
|
|
5933
5963
|
frontendExecutionPolicy: meta.spec?.frontendExecutionPolicy,
|
|
5934
5964
|
attachedFiles: [],
|
|
5935
5965
|
modelConfig,
|
|
5966
|
+
outputLimitRecovery: { terminalToolNames: ["finalize_plan", "finalize_contract", "approve_design", "request_design_changes", "approve_review", "request_review_changes"] },
|
|
5936
5967
|
repoRoot: input.cwd,
|
|
5937
5968
|
step,
|
|
5938
5969
|
toolNames,
|
|
@@ -6051,7 +6082,7 @@ export async function executeDagPiNode(input, meta, piStepFn = executePiStep, wr
|
|
|
6051
6082
|
? options.ledgerTools.customTools
|
|
6052
6083
|
: options.ledgerTools.customTools.filter((tool) => typeof tool === "object" &&
|
|
6053
6084
|
tool !== null &&
|
|
6054
|
-
toolNames.has(tool.name)),
|
|
6085
|
+
(toolNames.has(tool.name) || tool.name === "read_plan_facts")),
|
|
6055
6086
|
flushLedger: () => options.ledgerTools.flush(),
|
|
6056
6087
|
});
|
|
6057
6088
|
if (planRequirementIds.length > 1) {
|
|
@@ -707,6 +707,8 @@ function shouldFallbackSdkToCli(sdkResult, validationIssues, options) {
|
|
|
707
707
|
return true;
|
|
708
708
|
if (!sdkResult.ok &&
|
|
709
709
|
(sdkResult.failureCategory === "auth" ||
|
|
710
|
+
sdkResult.failureCategory === "output-limit" ||
|
|
711
|
+
sdkResult.failureCategory === "cancelled" ||
|
|
710
712
|
sdkResult.failureCategory === "timeout" ||
|
|
711
713
|
sdkResult.failureCategory === "termination-unconfirmed")) {
|
|
712
714
|
return false;
|