fix(ai): partial JSONL failures hard-rollback so retry actually fires
Previous "failed with non-empty insertedNodes" combination still bypassed retry. orchestrator-sub-agent.ts gates retry on `result.nodes.length === 0` — the partial inserts surfaced through DispatchResult.insertedNodes flowed through to the subtask's `nodes` field, made it look non-empty, and skipped the retry / minimal-skills / batch_design fallback chain. Hard-rollback partial inserts on JSONL fallback failure: call `store.removeNode(id)` for every root that did land, then return `failed` with `insertedNodes: []`. The dispatcher's surrounding history-batch wrapper absorbs both the addNode and removeNode calls so the user-visible undo entry is a net no-op, and the retry condition upstream now sees a genuinely empty result and re-runs the subtask cleanly. Three outcomes after this: - All roots land → `applied` with full insertedNodes. - Partial / total failure → `failed` with `insertedNodes: []` (any partial successes rolled back) so retry fires and the doc returns to its pre-dispatch state.
This commit is contained in:
parent
4043e9915f
commit
5ffbd1a86a
|
|
@ -502,16 +502,28 @@ function applyBatchDesignAsJsonl(jsonl: string, ctx: DispatchContext): DispatchR
|
|||
}
|
||||
}
|
||||
// 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.
|
||||
// root usually means defaultParentId is stale, the model emitted a
|
||||
// dangling _parent reference, or doc state has drifted. Returning
|
||||
// `applied` for partial inserts would lie to the orchestrator (it
|
||||
// checks `status === 'applied'` to decide whether to retry); returning
|
||||
// `failed` with non-empty `insertedNodes` would also bypass retry,
|
||||
// because the caller in orchestrator-sub-agent.ts gates retry on
|
||||
// `result.nodes.length === 0` — the partial inserts get reported as
|
||||
// the subtask's `nodes` and look "non-empty enough" to skip the retry
|
||||
// / minimal-skills fallback chain. Hard-rollback the partial inserts
|
||||
// so the doc returns to its pre-dispatch state, the surrounding
|
||||
// history batch nets to zero, and the retry condition fires.
|
||||
if (failedIds.length > 0) {
|
||||
if (inserted.length > 0) {
|
||||
const rollbackStore = useDocumentStore.getState();
|
||||
for (const node of inserted) {
|
||||
try {
|
||||
rollbackStore.removeNode(node.id);
|
||||
} catch {
|
||||
/* already removed by an upstream cleanup — ignore */
|
||||
}
|
||||
}
|
||||
}
|
||||
const failedList = `${failedIds.slice(0, 4).join(', ')}${failedIds.length > 4 ? '…' : ''}`;
|
||||
if (inserted.length === 0) {
|
||||
return {
|
||||
|
|
@ -532,8 +544,9 @@ function applyBatchDesignAsJsonl(jsonl: string, ctx: DispatchContext): DispatchR
|
|||
message:
|
||||
`batch_design.operations partial failure: ${inserted.length}/${tree.length} root(s) ` +
|
||||
`landed, ${failedIds.length} did not (parent id '${parentId ?? 'page-root'}'). ` +
|
||||
`Rolled back partial inserts so retry can re-run the subtask cleanly. ` +
|
||||
`Failed root ids: ${failedList}`,
|
||||
insertedNodes: inserted,
|
||||
insertedNodes: [],
|
||||
};
|
||||
}
|
||||
const totalLive = countDescendants(inserted);
|
||||
|
|
|
|||
Loading…
Reference in a new issue