From 4043e9915f2bbb2ca8809f099f5ed83c8c422eac Mon Sep 17 00:00:00 2001 From: Fini Date: Tue, 5 May 2026 01:04:47 +0800 Subject: [PATCH] fix(ai): JSONL fallback returns 'failed' on partial-insert (was 'applied') MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previous version reported `status: 'applied'` whenever at least one root landed, with a partial-failure note in `message`. But the orchestrator's retry / minimal-skills / batch_design-fallback chain checks `status === 'applied'` to decide whether to bypass retry — a partial insert (e.g. 1/5 roots landed because `defaultParentId` was stale) would short-circuit retry and leave the user with a degraded design that the system never tried to fix. Now any failed root flips the dispatch to `status: 'failed'` so the orchestrator's retry path can take over. The successful partial inserts are still surfaced in `insertedNodes` so the surrounding history-batch wrapper can roll them back / clean up — `failed` with non-empty `insertedNodes` is a legitimate combination meaning "side effects happened but the dispatch did not complete its contract". Three outcomes now: - All N roots land → `applied` with full count. - 1..N-1 land → `failed` with partial-success `insertedNodes` and a message naming the parent id + failed root ids. - 0 land → `failed` with empty `insertedNodes` and the same diagnostic. --- .../services/ai/element-tools-dispatcher.ts | 36 +++++++++++++++---- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/apps/web/src/services/ai/element-tools-dispatcher.ts b/apps/web/src/services/ai/element-tools-dispatcher.ts index 64c9b384a..31de6d14e 100644 --- a/apps/web/src/services/ai/element-tools-dispatcher.ts +++ b/apps/web/src/services/ai/element-tools-dispatcher.ts @@ -501,25 +501,47 @@ function applyBatchDesignAsJsonl(jsonl: string, ctx: DispatchContext): DispatchR failedIds.push(root.id); } } - if (inserted.length === 0) { + // Any failure to land a root is a hard failure — even a single missing + // root usually means the defaultParentId is stale, the model emitted a + // dangling _parent reference, or the doc state has drifted. Reporting + // `applied` for partial inserts would lie to the orchestrator: it + // checks `status === 'applied'` to decide whether to skip retry, so a + // partial would silently degrade the design without ever exercising + // the orchestrator's retry / minimal-skills / batch_design fallback + // chain. We still surface the partial-success roots in `insertedNodes` + // so the caller can clean them up (or roll back through the history + // batch wrapper) instead of leaving orphans. + if (failedIds.length > 0) { + const failedList = `${failedIds.slice(0, 4).join(', ')}${failedIds.length > 4 ? '…' : ''}`; + if (inserted.length === 0) { + return { + status: 'failed', + route: 'batch-design-dsl', + toolName: 'batch_design', + message: + `batch_design.operations parsed as JSONL (${tree.length} root(s)) but none landed — ` + + `parent id '${parentId ?? 'page-root'}' may be stale or unresolved. ` + + `Failed root ids: ${failedList}`, + insertedNodes: [], + }; + } return { status: 'failed', route: 'batch-design-dsl', toolName: 'batch_design', message: - `batch_design.operations parsed as JSONL (${tree.length} root(s)) but none landed — ` + - `parent id '${parentId ?? 'page-root'}' may be stale or unresolved. ` + - `Failed root ids: ${failedIds.slice(0, 4).join(', ')}${failedIds.length > 4 ? '…' : ''}`, - insertedNodes: [], + `batch_design.operations partial failure: ${inserted.length}/${tree.length} root(s) ` + + `landed, ${failedIds.length} did not (parent id '${parentId ?? 'page-root'}'). ` + + `Failed root ids: ${failedList}`, + insertedNodes: inserted, }; } const totalLive = countDescendants(inserted); - const partial = failedIds.length > 0 ? ` (${failedIds.length} root(s) failed to insert)` : ''; return { status: 'applied', route: 'batch-design-dsl', toolName: 'batch_design', - message: `Applied batch_design.operations as JSONL (${inserted.length}/${tree.length} root node(s); ${totalLive} total)${partial}.`, + message: `Applied batch_design.operations as JSONL (${tree.length} root node(s); ${totalLive} total).`, insertedNodes: inserted, }; }