@tea-agent/loop-agent 0.43.0-next.4 → 0.43.0-next.5
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 +80 -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 +244 -33
- package/dist/executors/pi-executor.js +2 -0
- package/dist/executors/pi-sdk-executor.js +75 -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-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
|
@@ -268,6 +268,16 @@ export function isPlannerThinkingExhausted(result, committedAnyFacts) {
|
|
|
268
268
|
return false;
|
|
269
269
|
return true;
|
|
270
270
|
}
|
|
271
|
+
/** Capacity recovery must not depend on whether a gateway emitted prose or
|
|
272
|
+
* diagnostics. Precise provider/governance failures retain their ownership. */
|
|
273
|
+
function isFrontendPlanCapacityStop(result) {
|
|
274
|
+
if (result.ok || result.timedOut)
|
|
275
|
+
return false;
|
|
276
|
+
if (result.failureCategory === OUTPUT_LIMIT_RETRY_CATEGORY)
|
|
277
|
+
return true;
|
|
278
|
+
return readWriterThinkingExhaustionEvidence(result).stopReason === "length" &&
|
|
279
|
+
(!result.failureCategory || ["empty-output", "network", "nonzero-exit", "unknown"].includes(result.failureCategory));
|
|
280
|
+
}
|
|
271
281
|
/**
|
|
272
282
|
* The writer session burned an excessive token budget (a read-edit-test loop
|
|
273
283
|
* that never converged) and still failed. Distinct from empty-output so the
|
|
@@ -592,6 +602,7 @@ export function resolveDagPiToolNames(task) {
|
|
|
592
602
|
...FRONTEND_PLAN_RECORD_TOOL_NAMES,
|
|
593
603
|
...FRONTEND_PLAN_TERMINAL_TOOL_NAMES,
|
|
594
604
|
...FRONTEND_PLAN_ADOPT_TOOL_NAMES,
|
|
605
|
+
"read_plan_facts",
|
|
595
606
|
];
|
|
596
607
|
}
|
|
597
608
|
if ((task.readSet?.length ?? 0) > 0) {
|
|
@@ -1811,6 +1822,44 @@ export async function createFrontendPlanLedgerTools(input) {
|
|
|
1811
1822
|
});
|
|
1812
1823
|
}
|
|
1813
1824
|
}
|
|
1825
|
+
// Named entries are last-wins at assembly. A scoped correction may
|
|
1826
|
+
// replace its own bindings, but must retain other scopes' coverage.
|
|
1827
|
+
const scope = new Set(scopedRequirementIds());
|
|
1828
|
+
const committed = readCommittedEvents(store, attemptId).map(event => event.fact);
|
|
1829
|
+
const targetOwners = new Map();
|
|
1830
|
+
for (const fact of committed) {
|
|
1831
|
+
if (fact.kind === "plan-verification-target" && isRecordObject(fact.entry) && typeof fact.entry.id === "string") {
|
|
1832
|
+
targetOwners.set(fact.entry.id, stringList(fact.entry.requirementIds));
|
|
1833
|
+
}
|
|
1834
|
+
}
|
|
1835
|
+
for (const [field, removals] of [["uiStates", removeUiStateNames], ["interactions", removeInteractionNames]]) {
|
|
1836
|
+
const live = new Map();
|
|
1837
|
+
for (const fact of committed) {
|
|
1838
|
+
if (fact.kind !== "state-flow")
|
|
1839
|
+
continue;
|
|
1840
|
+
for (const name of stringList(fact[field === "uiStates" ? "removeUiStateNames" : "removeInteractionNames"]))
|
|
1841
|
+
live.delete(name);
|
|
1842
|
+
for (const entry of Array.isArray(fact[field]) ? fact[field] : []) {
|
|
1843
|
+
if (isRecordObject(entry) && typeof entry.name === "string")
|
|
1844
|
+
live.set(entry.name, entry);
|
|
1845
|
+
}
|
|
1846
|
+
}
|
|
1847
|
+
const foreignBindings = (entry) => scope.size === 0 ? [] : stringList(entry.verificationTargetIds).filter(id => {
|
|
1848
|
+
const owners = targetOwners.get(id);
|
|
1849
|
+
return !owners?.length || owners.some(owner => !scope.has(owner));
|
|
1850
|
+
});
|
|
1851
|
+
for (const name of removals) {
|
|
1852
|
+
const previous = live.get(name);
|
|
1853
|
+
if (previous && foreignBindings(previous).length)
|
|
1854
|
+
return planToolReceipt({ ok: false, kind: "state-flow",
|
|
1855
|
+
error: `Cannot remove shared ${name} from this requirement scope; retain it and update its scoped bindings instead` });
|
|
1856
|
+
}
|
|
1857
|
+
for (const entry of field === "uiStates" ? uiStates : interactions) {
|
|
1858
|
+
const previous = live.get(String(entry.name));
|
|
1859
|
+
if (previous)
|
|
1860
|
+
entry.verificationTargetIds = [...new Set([...foreignBindings(previous), ...stringList(entry.verificationTargetIds)])];
|
|
1861
|
+
}
|
|
1862
|
+
}
|
|
1814
1863
|
const result = await adoptPlanFact("state-flow", `${attemptId}:record_state_flow:${randomUUID()}`, {
|
|
1815
1864
|
kind: "state-flow",
|
|
1816
1865
|
origin: "plan",
|
|
@@ -2415,6 +2464,74 @@ export async function createFrontendPlanLedgerTools(input) {
|
|
|
2415
2464
|
}
|
|
2416
2465
|
},
|
|
2417
2466
|
});
|
|
2467
|
+
const readPlanFactsTool = defineTool({
|
|
2468
|
+
name: "read_plan_facts",
|
|
2469
|
+
label: "read_plan_facts",
|
|
2470
|
+
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.",
|
|
2471
|
+
parameters: Type.Object({
|
|
2472
|
+
kind: Type.String(), entryId: Type.Optional(Type.String()), eventId: Type.Optional(Type.String()),
|
|
2473
|
+
field: Type.Optional(Type.String()), offset: Type.Optional(Type.Integer({ minimum: 0 })),
|
|
2474
|
+
limit: Type.Optional(Type.Integer({ minimum: 1, maximum: 50 })),
|
|
2475
|
+
stringOffset: Type.Optional(Type.Integer({ minimum: 0 })),
|
|
2476
|
+
expectedRevision: Type.Optional(Type.Integer({ minimum: 0 })),
|
|
2477
|
+
}, { additionalProperties: false }),
|
|
2478
|
+
async execute(_id, params) {
|
|
2479
|
+
const reply = (details) => ({ content: [{ type: "text", text: JSON.stringify(details) }], details });
|
|
2480
|
+
if (params.expectedRevision !== undefined && params.expectedRevision !== store.revision) {
|
|
2481
|
+
return reply({ ok: false, revision: store.revision, error: "ledger revision changed; restart pagination" });
|
|
2482
|
+
}
|
|
2483
|
+
let records = readCommittedEvents(store, attemptId).filter(record => {
|
|
2484
|
+
const fact = record.fact;
|
|
2485
|
+
return fact.kind === params.kind && (!params.eventId || record.eventId === params.eventId) &&
|
|
2486
|
+
(!params.entryId || fact.entry?.id === params.entryId);
|
|
2487
|
+
});
|
|
2488
|
+
// Full-entry corrections and registries are last-wins. Other kinds
|
|
2489
|
+
// remain an ordered event log (state removals must remain visible).
|
|
2490
|
+
if (params.entryId || params.kind === "state-registry")
|
|
2491
|
+
records = records.slice(-1);
|
|
2492
|
+
let values = records.map(record => ({ eventId: record.eventId, ...record.fact }));
|
|
2493
|
+
if (params.field) {
|
|
2494
|
+
values = records.flatMap(record => {
|
|
2495
|
+
let value = record.fact;
|
|
2496
|
+
for (const key of params.field.split(".")) {
|
|
2497
|
+
if (!value || typeof value !== "object" || !Object.hasOwn(value, key))
|
|
2498
|
+
return [];
|
|
2499
|
+
value = value[key];
|
|
2500
|
+
}
|
|
2501
|
+
return Array.isArray(value) ? value : [value];
|
|
2502
|
+
});
|
|
2503
|
+
}
|
|
2504
|
+
const offset = params.offset ?? 0;
|
|
2505
|
+
if (params.stringOffset !== undefined) {
|
|
2506
|
+
const value = values[offset];
|
|
2507
|
+
if (typeof value !== "string")
|
|
2508
|
+
return reply({ ok: false, error: "stringOffset requires a string field item" });
|
|
2509
|
+
// Unicode code points prevent a page boundary splitting a surrogate pair.
|
|
2510
|
+
const characters = Array.from(value);
|
|
2511
|
+
const chunk = characters.slice(params.stringOffset, params.stringOffset + 1000).join("");
|
|
2512
|
+
const next = params.stringOffset + Array.from(chunk).length;
|
|
2513
|
+
return reply({ ok: true, revision: store.revision, offset, stringOffset: params.stringOffset,
|
|
2514
|
+
chunk, totalCharacters: characters.length, nextStringOffset: next < characters.length ? next : null });
|
|
2515
|
+
}
|
|
2516
|
+
const items = [];
|
|
2517
|
+
let bytes = 0;
|
|
2518
|
+
for (const value of values.slice(offset, offset + (params.limit ?? 10))) {
|
|
2519
|
+
const size = Buffer.byteLength(JSON.stringify(value));
|
|
2520
|
+
if (bytes + size > 12_000)
|
|
2521
|
+
break;
|
|
2522
|
+
bytes += size;
|
|
2523
|
+
items.push(value);
|
|
2524
|
+
}
|
|
2525
|
+
if (!items.length && offset < values.length)
|
|
2526
|
+
return reply({
|
|
2527
|
+
ok: false, revision: store.revision, total: values.length, offset,
|
|
2528
|
+
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",
|
|
2529
|
+
fields: typeof values[offset] === "object" && values[offset] !== null ? Object.keys(values[offset]) : [],
|
|
2530
|
+
});
|
|
2531
|
+
return reply({ ok: true, revision: store.revision, total: values.length, offset, items,
|
|
2532
|
+
nextOffset: offset + items.length < values.length ? offset + items.length : null });
|
|
2533
|
+
},
|
|
2534
|
+
});
|
|
2418
2535
|
const durable = await createDurableFrontendTools({
|
|
2419
2536
|
file: path.join(input.runDir, input.nodeId, "plan-typed-facts.jsonl"), attemptId, store: input.store,
|
|
2420
2537
|
setWorkingStore: next => { store = next; },
|
|
@@ -2438,7 +2555,7 @@ export async function createFrontendPlanLedgerTools(input) {
|
|
|
2438
2555
|
],
|
|
2439
2556
|
});
|
|
2440
2557
|
return {
|
|
2441
|
-
customTools: durable.customTools,
|
|
2558
|
+
customTools: [...durable.customTools, { ...readPlanFactsTool, execute: async (...args) => { await durable.flush(); return readPlanFactsTool.execute(...args); } }],
|
|
2442
2559
|
adoptCommittedFacts: async (records) => durable.commitExternal(async () => {
|
|
2443
2560
|
for (const record of records) {
|
|
2444
2561
|
if (record.phase !== "committed")
|
|
@@ -3808,6 +3925,11 @@ export function collectFrontendPlanPhaseMissingFacts(input) {
|
|
|
3808
3925
|
];
|
|
3809
3926
|
}
|
|
3810
3927
|
if (input.phase === "ux-local") {
|
|
3928
|
+
if (input.requirementIds.length > 1) {
|
|
3929
|
+
return input.requirementIds.flatMap(id => collectFrontendPlanPhaseMissingFacts({
|
|
3930
|
+
...input, requirementIds: [id],
|
|
3931
|
+
}));
|
|
3932
|
+
}
|
|
3811
3933
|
const needsUx = input.requirementIds.some((id) => input.behaviorRequiredRequirementIds?.includes(id));
|
|
3812
3934
|
if (!needsUx)
|
|
3813
3935
|
return [];
|
|
@@ -3817,8 +3939,9 @@ export function collectFrontendPlanPhaseMissingFacts(input) {
|
|
|
3817
3939
|
Array.isArray(fact.uiComponentChoices) &&
|
|
3818
3940
|
fact.uiComponentChoices.length > 0);
|
|
3819
3941
|
const canonicalStateFlow = collectCanonicalStateFlowNames(scopedFacts);
|
|
3820
|
-
const
|
|
3821
|
-
|
|
3942
|
+
const liveStateFlow = collectCanonicalStateFlowNames(facts);
|
|
3943
|
+
const hasStateFlow = [...canonicalStateFlow.uiStateNames].some(name => liveStateFlow.uiStateNames.has(name)) ||
|
|
3944
|
+
[...canonicalStateFlow.interactionNames].some(name => liveStateFlow.interactionNames.has(name));
|
|
3822
3945
|
const missing = [];
|
|
3823
3946
|
if (!hasChoice) {
|
|
3824
3947
|
missing.push({
|
|
@@ -3903,9 +4026,8 @@ export function batchFrontendPlanRequirements(input) {
|
|
|
3903
4026
|
}
|
|
3904
4027
|
const FRONTEND_PLAN_COVERAGE_MAX_RECORD_CALLS = 10;
|
|
3905
4028
|
const FRONTEND_PLAN_COVERAGE_MAX_CONCURRENCY = 4;
|
|
3906
|
-
// A
|
|
3907
|
-
//
|
|
3908
|
-
// reinvented per AC batch. Keep a safety bound for adaptive coverage retries
|
|
4029
|
+
// A single registry owns global names; large UX work uses bounded serial
|
|
4030
|
+
// scopes against that registry. Keep a safety bound for adaptive retries
|
|
3909
4031
|
// without letting the old 32-session ceiling skip finalize.
|
|
3910
4032
|
const FRONTEND_PLAN_BATCH_MAX_SESSIONS = 128;
|
|
3911
4033
|
const FRONTEND_PLAN_SMALL_MAX_REQUIREMENTS = 8;
|
|
@@ -4046,26 +4168,28 @@ export function compactFrontendPlanPromptForRequirementSlice(basePrompt, require
|
|
|
4046
4168
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
4047
4169
|
return [];
|
|
4048
4170
|
const target = value;
|
|
4049
|
-
|
|
4171
|
+
if (options.verificationTargetIds && !options.verificationTargetIds.includes(String(target.id)))
|
|
4172
|
+
return [];
|
|
4173
|
+
const targetRequirementIds = planFactStringList(target.requirementIds);
|
|
4050
4174
|
const relatedRequirementIds = targetRequirementIds.filter((id) => sliceSet.has(id));
|
|
4051
4175
|
if (relatedRequirementIds.length === 0)
|
|
4052
4176
|
return [];
|
|
4053
4177
|
return [
|
|
4054
4178
|
{
|
|
4055
|
-
...(
|
|
4056
|
-
? { id:
|
|
4179
|
+
...(typeof target.id === "string"
|
|
4180
|
+
? { id: target.id }
|
|
4057
4181
|
: {}),
|
|
4058
|
-
...(
|
|
4059
|
-
? { commandId:
|
|
4182
|
+
...(typeof target.commandId === "string"
|
|
4183
|
+
? { commandId: target.commandId }
|
|
4060
4184
|
: {}),
|
|
4061
4185
|
...(compactPromptString(target.commandLabel, 180)
|
|
4062
4186
|
? { commandLabel: compactPromptString(target.commandLabel, 180) }
|
|
4063
4187
|
: {}),
|
|
4064
|
-
...(
|
|
4065
|
-
? { file:
|
|
4188
|
+
...(typeof target.file === "string"
|
|
4189
|
+
? { file: target.file }
|
|
4066
4190
|
: {}),
|
|
4067
4191
|
requirementIds: relatedRequirementIds,
|
|
4068
|
-
uiStates:
|
|
4192
|
+
uiStates: planFactStringList(target.uiStates),
|
|
4069
4193
|
},
|
|
4070
4194
|
];
|
|
4071
4195
|
})
|
|
@@ -4117,7 +4241,7 @@ export function compactFrontendPlanPromptForRequirementSlice(basePrompt, require
|
|
|
4117
4241
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
4118
4242
|
return [];
|
|
4119
4243
|
const state = value;
|
|
4120
|
-
const id =
|
|
4244
|
+
const id = typeof state.id === "string" ? state.id : undefined;
|
|
4121
4245
|
if (!id)
|
|
4122
4246
|
return [];
|
|
4123
4247
|
return [{
|
|
@@ -4217,9 +4341,9 @@ function compactFrontendPlanLedgerContext(input) {
|
|
|
4217
4341
|
compactFacts.push({
|
|
4218
4342
|
kind: fact.kind,
|
|
4219
4343
|
entry: {
|
|
4220
|
-
id:
|
|
4221
|
-
implementationTargets:
|
|
4222
|
-
verificationTargetIds:
|
|
4344
|
+
id: entry.id,
|
|
4345
|
+
implementationTargets: planFactStringList(entry.implementationTargets).slice(0, 12),
|
|
4346
|
+
verificationTargetIds: planFactStringList(entry.verificationTargetIds).slice(0, 12),
|
|
4223
4347
|
...(compactPromptString(entry.expectedOutcome, 240)
|
|
4224
4348
|
? { expectedOutcome: compactPromptString(entry.expectedOutcome, 240) }
|
|
4225
4349
|
: {}),
|
|
@@ -4233,17 +4357,19 @@ function compactFrontendPlanLedgerContext(input) {
|
|
|
4233
4357
|
if (fact.kind === "plan-verification-target") {
|
|
4234
4358
|
if (!entry)
|
|
4235
4359
|
continue;
|
|
4236
|
-
|
|
4237
|
-
|
|
4360
|
+
// Scope against the complete canonical binding before compacting.
|
|
4361
|
+
// A shared target may bind a late requirement beyond the preview cap.
|
|
4362
|
+
const requirementIds = planFactStringList(entry.requirementIds).filter((id) => slice.has(id));
|
|
4363
|
+
if (requirementIds.length === 0)
|
|
4238
4364
|
continue;
|
|
4239
4365
|
compactFacts.push({
|
|
4240
4366
|
kind: fact.kind,
|
|
4241
4367
|
entry: {
|
|
4242
|
-
id:
|
|
4243
|
-
commandId:
|
|
4244
|
-
file:
|
|
4368
|
+
id: entry.id,
|
|
4369
|
+
commandId: entry.commandId,
|
|
4370
|
+
file: entry.file,
|
|
4245
4371
|
requirementIds,
|
|
4246
|
-
uiStates:
|
|
4372
|
+
uiStates: planFactStringList(entry.uiStates).slice(0, 12),
|
|
4247
4373
|
},
|
|
4248
4374
|
});
|
|
4249
4375
|
continue;
|
|
@@ -4270,8 +4396,8 @@ function compactFrontendPlanLedgerContext(input) {
|
|
|
4270
4396
|
if (fact.kind === "state-registry") {
|
|
4271
4397
|
compactFacts.push({
|
|
4272
4398
|
kind: fact.kind,
|
|
4273
|
-
uiStateNames:
|
|
4274
|
-
interactionNames:
|
|
4399
|
+
uiStateNames: planFactStringList(fact.uiStateNames).slice(0, 40),
|
|
4400
|
+
interactionNames: planFactStringList(fact.interactionNames).slice(0, 60),
|
|
4275
4401
|
});
|
|
4276
4402
|
continue;
|
|
4277
4403
|
}
|
|
@@ -4323,13 +4449,21 @@ function compactFrontendPlanLedgerContext(input) {
|
|
|
4323
4449
|
return "";
|
|
4324
4450
|
const priorityFacts = compactFacts.filter((fact) => fact.kind === "plan-requirement" || fact.kind === "plan-verification-target");
|
|
4325
4451
|
const otherFacts = compactFacts.filter((fact) => fact.kind !== "plan-requirement" && fact.kind !== "plan-verification-target");
|
|
4452
|
+
let previewBytes = 0;
|
|
4326
4453
|
const boundedFacts = [
|
|
4327
|
-
...priorityFacts,
|
|
4328
|
-
...otherFacts,
|
|
4329
|
-
]
|
|
4454
|
+
...priorityFacts.slice(0, 64),
|
|
4455
|
+
...otherFacts.slice(-32),
|
|
4456
|
+
].slice(0, 96).filter(fact => {
|
|
4457
|
+
const bytes = Buffer.byteLength(JSON.stringify(fact));
|
|
4458
|
+
if (previewBytes + bytes > 24_000)
|
|
4459
|
+
return false;
|
|
4460
|
+
previewBytes += bytes;
|
|
4461
|
+
return true;
|
|
4462
|
+
});
|
|
4330
4463
|
return [
|
|
4331
4464
|
"<frontend_plan_ledger>",
|
|
4332
|
-
"
|
|
4465
|
+
"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.",
|
|
4466
|
+
JSON.stringify({ complete: false, matchingFacts: compactFacts.length, omittedFacts: compactFacts.length - boundedFacts.length }),
|
|
4333
4467
|
JSON.stringify(boundedFacts),
|
|
4334
4468
|
"</frontend_plan_ledger>",
|
|
4335
4469
|
].join("\n");
|
|
@@ -4707,7 +4841,7 @@ export async function runFrontendPlanSegmentedSessions(input) {
|
|
|
4707
4841
|
"component-choice",
|
|
4708
4842
|
"state-flow",
|
|
4709
4843
|
],
|
|
4710
|
-
|
|
4844
|
+
// Include shared state bindings from other scopes; page full values before correcting.
|
|
4711
4845
|
})
|
|
4712
4846
|
: "";
|
|
4713
4847
|
return [
|
|
@@ -4926,6 +5060,27 @@ export async function runFrontendPlanSegmentedSessions(input) {
|
|
|
4926
5060
|
let lastDurableCount = input.committedFactCount();
|
|
4927
5061
|
while (index < queue.length) {
|
|
4928
5062
|
const session = queue[index];
|
|
5063
|
+
if (input.attempt > 1 && input.committedFacts && session.id === "ux-registry" &&
|
|
5064
|
+
collectFrontendPlanPhaseMissingFacts({ phase: "ux-registry", requirementIds: allRequirementIds, committedFacts: input.committedFacts() }).length === 0) {
|
|
5065
|
+
index += 1;
|
|
5066
|
+
continue;
|
|
5067
|
+
}
|
|
5068
|
+
// Reuse complete UX scopes on node retry. Require explicit per-requirement
|
|
5069
|
+
// ownership; an unrelated/global fact must not prove a slice complete.
|
|
5070
|
+
if (input.attempt > 1 && input.committedFacts && session.id.startsWith("ux-local-") && session.requirementSlice?.length) {
|
|
5071
|
+
const facts = input.committedFacts();
|
|
5072
|
+
const complete = session.requirementSlice.every(id => {
|
|
5073
|
+
const owned = facts.filter(value => {
|
|
5074
|
+
const fact = committedFactFromPlanRecord(value);
|
|
5075
|
+
return fact && planFactStringList(fact.scopeRequirementIds).includes(id);
|
|
5076
|
+
});
|
|
5077
|
+
return owned.length > 0 && collectFrontendPlanPhaseMissingFacts({ phase: "ux-local", requirementIds: [id], committedFacts: facts, behaviorRequiredRequirementIds: input.behaviorRequiredRequirementIds }).length === 0;
|
|
5078
|
+
});
|
|
5079
|
+
if (complete) {
|
|
5080
|
+
index += 1;
|
|
5081
|
+
continue;
|
|
5082
|
+
}
|
|
5083
|
+
}
|
|
4929
5084
|
const remaining = (session.coverageOnly ? session.coverageSlice ?? [] : []).filter((id) => !input.committedRequirementIds?.().has(id));
|
|
4930
5085
|
const preexistingMissing = session.coverageOnly && session.coverageSlice && input.committedFacts
|
|
4931
5086
|
? collectFrontendPlanMissingFacts({
|
|
@@ -4971,12 +5126,30 @@ export async function runFrontendPlanSegmentedSessions(input) {
|
|
|
4971
5126
|
: buildPhasePrompt(segment, session.missingFacts, session.requirementSlice ?? allRequirementIds);
|
|
4972
5127
|
}
|
|
4973
5128
|
}
|
|
5129
|
+
const atomicFocus = session.atomicRecovery ? session.missingFacts?.[0] : undefined;
|
|
5130
|
+
if (atomicFocus) {
|
|
5131
|
+
prompt = [
|
|
5132
|
+
compactFrontendPlanPromptForRequirementSlice(input.basePrompt, atomicFocus.requirementIds, {
|
|
5133
|
+
includeChecklist: false,
|
|
5134
|
+
...(atomicFocus.kind === "plan-verification-target" && atomicFocus.id
|
|
5135
|
+
? { verificationTargetIds: [atomicFocus.id] } : {}),
|
|
5136
|
+
}),
|
|
5137
|
+
`ATOMIC FACT: ${atomicFocus.kind}${atomicFocus.id ? ` ${atomicFocus.id}` : ""}`,
|
|
5138
|
+
atomicFocus.reason,
|
|
5139
|
+
"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.",
|
|
5140
|
+
].join("\n\n");
|
|
5141
|
+
}
|
|
4974
5142
|
const committedBefore = input.committedFactCount();
|
|
4975
5143
|
input.setActiveRequirementScope?.(session.coverageOnly ? session.coverageSlice ?? [] : session.requirementSlice ?? []);
|
|
4976
5144
|
if (invocationCount >= FRONTEND_PLAN_BATCH_MAX_SESSIONS)
|
|
4977
5145
|
break;
|
|
4978
5146
|
invocationCount += 1;
|
|
4979
|
-
const
|
|
5147
|
+
const atomicTools = {
|
|
5148
|
+
"plan-requirement": "record_plan_requirement", "plan-verification-target": "record_plan_verification_target",
|
|
5149
|
+
"state-registry": "record_state_registry", "component-choice": "record_component_choice",
|
|
5150
|
+
"state-flow": "record_state_flow", "data-flow": "record_data_flow", "mock-api": "record_mock_api",
|
|
5151
|
+
};
|
|
5152
|
+
const customTools = input.segmentCustomTools(atomicFocus ? new Set([atomicTools[atomicFocus.kind]]) : session.toolNames);
|
|
4980
5153
|
const scopeForPacking = session.coverageSlice ?? session.requirementSlice;
|
|
4981
5154
|
const envelopeBytes = Buffer.byteLength(prompt) + Buffer.byteLength(input.sessionOptions.userMessage ?? "") + Buffer.byteLength(JSON.stringify(customTools));
|
|
4982
5155
|
if (envelopeBytes > (input.sessionOptions.frontendExecutionPolicy?.scopeTargetBytes ?? FRONTEND_SCOPE_TARGET_BYTES) && scopeForPacking && session.id !== "compact-local") {
|
|
@@ -5107,6 +5280,43 @@ export async function runFrontendPlanSegmentedSessions(input) {
|
|
|
5107
5280
|
: buildPhasePrompt(globalMockDataSegment, missing, session.requirementSlice ?? allRequirementIds);
|
|
5108
5281
|
if (result.timedOut || /interrupt|termination-unconfirmed|budget_breach/.test(result.failureCategory))
|
|
5109
5282
|
return { ...result, ok: false };
|
|
5283
|
+
if ((isFrontendPlanCapacityStop(result) || (session.atomicRecovery && result.ok)) && input.committedFacts &&
|
|
5284
|
+
!(missingPhaseFacts.length === 1 && missingPhaseFacts[0].kind === "plan-requirement") &&
|
|
5285
|
+
(session.coverageOnly || isUxRegistrySession || isUxLocalSession || session.id.startsWith("global-mock-data"))) {
|
|
5286
|
+
if (missingPhaseFacts.length === 0) {
|
|
5287
|
+
// A complete validated slice does not need a successful prose turn.
|
|
5288
|
+
// This never accepts finalize or bypasses the final contract validator.
|
|
5289
|
+
accumulated = { ...accumulated, ok: true, failureCategory: "success" };
|
|
5290
|
+
index += 1;
|
|
5291
|
+
continue;
|
|
5292
|
+
}
|
|
5293
|
+
const missingIds = [...new Set(missingPhaseFacts.flatMap(item => item.requirementIds))];
|
|
5294
|
+
const scope = session.coverageSlice ?? session.requirementSlice;
|
|
5295
|
+
if (scope && missingIds.length < scope.length) {
|
|
5296
|
+
queue[index] = { ...session, missingFacts: missingPhaseFacts,
|
|
5297
|
+
...(session.coverageOnly ? { coverageSlice: missingIds } : { requirementSlice: missingIds }),
|
|
5298
|
+
retryCount: 0 };
|
|
5299
|
+
continue;
|
|
5300
|
+
}
|
|
5301
|
+
if (scope && missingIds.length > 1 && (session.coverageOnly || isUxLocalSession)) {
|
|
5302
|
+
const half = Math.ceil(missingIds.length / 2);
|
|
5303
|
+
queue.splice(index, 1, ...[missingIds.slice(0, half), missingIds.slice(half)].map(ids => ({
|
|
5304
|
+
...session, retryCount: 0,
|
|
5305
|
+
...(session.coverageOnly ? { coverageSlice: ids } : { requirementSlice: ids }),
|
|
5306
|
+
missingFacts: missingPhaseFacts.filter(item => item.requirementIds.some(id => ids.includes(id))),
|
|
5307
|
+
})));
|
|
5308
|
+
continue;
|
|
5309
|
+
}
|
|
5310
|
+
const focusStillMissing = atomicFocus && missingPhaseFacts.some(item => item.kind === atomicFocus.kind && item.id === atomicFocus.id &&
|
|
5311
|
+
item.requirementIds.join("\0") === atomicFocus.requirementIds.join("\0"));
|
|
5312
|
+
const retries = focusStillMissing ? (session.retryCount ?? 0) + 1 : 0;
|
|
5313
|
+
if (retries < 2) {
|
|
5314
|
+
queue[index] = { ...session, atomicRecovery: true, missingFacts: missingPhaseFacts, retryCount: retries };
|
|
5315
|
+
continue;
|
|
5316
|
+
}
|
|
5317
|
+
return { ...accumulated, ok: false, failureCategory: OUTPUT_LIMIT_RETRY_CATEGORY,
|
|
5318
|
+
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() };
|
|
5319
|
+
}
|
|
5110
5320
|
const capacityExhausted = readWriterThinkingExhaustionEvidence(result).stopReason === "length" || ["context-overflow", "context-budget-exhausted"].includes(result.failureCategory);
|
|
5111
5321
|
if (capacityExhausted && !result.timedOut) {
|
|
5112
5322
|
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);
|
|
@@ -5933,6 +6143,7 @@ export async function executeDagPiNode(input, meta, piStepFn = executePiStep, wr
|
|
|
5933
6143
|
frontendExecutionPolicy: meta.spec?.frontendExecutionPolicy,
|
|
5934
6144
|
attachedFiles: [],
|
|
5935
6145
|
modelConfig,
|
|
6146
|
+
outputLimitRecovery: { terminalToolNames: ["finalize_plan", "finalize_contract", "approve_design", "request_design_changes", "approve_review", "request_review_changes"] },
|
|
5936
6147
|
repoRoot: input.cwd,
|
|
5937
6148
|
step,
|
|
5938
6149
|
toolNames,
|
|
@@ -6051,7 +6262,7 @@ export async function executeDagPiNode(input, meta, piStepFn = executePiStep, wr
|
|
|
6051
6262
|
? options.ledgerTools.customTools
|
|
6052
6263
|
: options.ledgerTools.customTools.filter((tool) => typeof tool === "object" &&
|
|
6053
6264
|
tool !== null &&
|
|
6054
|
-
toolNames.has(tool.name)),
|
|
6265
|
+
(toolNames.has(tool.name) || tool.name === "read_plan_facts")),
|
|
6055
6266
|
flushLedger: () => options.ledgerTools.flush(),
|
|
6056
6267
|
});
|
|
6057
6268
|
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;
|