{"version":3,"file":"todo-update.d.ts","sourceRoot":"","sources":["../../../src/core/tools/todo-update.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,+BAA+B,CAAC;AAC/D,OAAO,EAAE,KAAK,MAAM,EAAQ,MAAM,mBAAmB,CAAC;AACtD,OAAO,EAAc,UAAU,EAAwC,MAAM,kBAAkB,CAAC;AAChG,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAErD,sCAAsC;AACtC,QAAA,MAAM,gBAAgB;;;;;;;;EAoBpB,CAAC;AAEH,MAAM,MAAM,eAAe,GAAG,MAAM,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAE9D,MAAM,WAAW,kBAAkB;IAClC,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,6BAA6B;IAC7C,yDAAyD;IACzD,WAAW,EAAE,MAAM,kBAAkB,GAAG,IAAI,CAAC;IAC7C,oEAAoE;IACpE,kBAAkB,EAAE,MAAM,IAAI,CAAC;CAC/B;AAED;;;GAGG;AACH,MAAM,WAAW,wBAAwB;IACxC,KAAK,EAAE,MAAM,CAAC;CACd;AAGD,YAAY,EAAE,QAAQ,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAEnE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAuDjD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,oBAAoB,CACnC,eAAe,EAAE,MAAM,QAAQ,EAAE,EACjC,eAAe,EAAE,CAAC,KAAK,EAAE,QAAQ,EAAE,KAAK,IAAI,EAC5C,WAAW,EAAE,MAAM,MAAM,EACzB,UAAU,EAAE,aAAa,EACzB,mBAAmB,CAAC,EAAE,6BAA6B,EACnD,eAAe,CAAC,EAAE,wBAAwB,EAC1C,MAAM,GAAE,UAAsC,GAC5C,SAAS,CAAC,OAAO,gBAAgB,CAAC,CA+YpC;AAED,+EAA+E;AAC/E,eAAO,MAAM,cAAc,EAAE,SAAS,CAAC,OAAO,gBAAgB,CAK7D,CAAC","sourcesContent":["import type { AgentTool } from \"@apholdings/jensen-agent-core\";\nimport { type Static, Type } from \"@sinclair/typebox\";\nimport { hashIntent, TodoEngine, type TodoPatchOp, validateTransition } from \"../todo/index.js\";\nimport { TodoLoopGuard } from \"./todo-loop-guard.js\";\n\n/** Schema for the todo_update tool */\nconst todoUpdateSchema = Type.Object({\n\tupdates: Type.Array(\n\t\tType.Object({\n\t\t\tid: Type.String({ description: \"Stable identifier of the todo item to update\" }),\n\t\t\tstatus: Type.Optional(\n\t\t\t\tType.Union([Type.Literal(\"pending\"), Type.Literal(\"in_progress\"), Type.Literal(\"completed\")], {\n\t\t\t\t\tdescription: \"New status for the todo item\",\n\t\t\t\t}),\n\t\t\t),\n\t\t\tactiveForm: Type.Optional(\n\t\t\t\tType.String({ description: \"Updated present continuous form shown during execution\" }),\n\t\t\t),\n\t\t\tcontent: Type.Optional(Type.String({ description: \"Updated imperative task description\" })),\n\t\t}),\n\t\t{ description: \"Array of partial updates to apply. Each update identifies a todo by stable id.\", minItems: 1 },\n\t),\n\texpectedRevision: Type.Number({\n\t\tdescription:\n\t\t\t\"Current revision from todo_read or last successful todo_write/todo_update. Jensen automatically reads and rebases once if stale.\",\n\t}),\n});\n\nexport type TodoUpdateInput = Static<typeof todoUpdateSchema>;\n\nexport interface TodoUpdateSnapshot {\n\trevision: number;\n\ttimestamp: number;\n}\n\nexport interface TodoUpdateSnapshotEnforcement {\n\t/** Returns the current read snapshot, or null if none */\n\tgetSnapshot: () => TodoUpdateSnapshot | null;\n\t/** Invalidates the current snapshot, requiring a fresh todo_read */\n\tinvalidateSnapshot: () => void;\n}\n\n/**\n * @deprecated Retained for compatibility. Loop detection is now progress-aware\n * and nonfatal inside the engine; this counter no longer terminates the run.\n */\nexport interface TodoUpdateRejectionState {\n\tcount: number;\n}\n\n// Re-export the engine types for callers.\nexport type { TodoItem, TodoRebaseResult } from \"../todo/index.js\";\n\nimport type { TodoItem } from \"../todo/index.js\";\n\nfunction normaliseUpdateField(value: string | undefined): string | undefined {\n\tif (value === undefined) return undefined;\n\treturn value.trim();\n}\n\n/**\n * Apply a set of patch operations to a clone of the current items.\n * Returns the new items and whether anything actually changed.\n */\nfunction applyOps(current: TodoItem[], ops: TodoPatchOp[]): { items: TodoItem[]; changed: boolean } {\n\tconst items = current.map((t) => ({ ...t }));\n\tconst byId = new Map<string, number>();\n\tfor (let i = 0; i < items.length; i++) {\n\t\tconst id = items[i].id;\n\t\tif (!id) continue;\n\t\tbyId.set(id, i);\n\t}\n\tlet changed = false;\n\tfor (const op of ops) {\n\t\tconst idx = byId.get(op.id);\n\t\tif (idx === undefined) continue; // unknown id — handled by caller\n\t\tconst item = items[idx];\n\t\tif (op.status !== undefined && op.status !== item.status) {\n\t\t\titem.status = op.status;\n\t\t\tif (op.status === \"completed\") {\n\t\t\t\titem.completedAt = Date.now();\n\t\t\t}\n\t\t\tchanged = true;\n\t\t}\n\t\tconst newActiveForm = normaliseUpdateField(op.activeForm);\n\t\tif (newActiveForm !== undefined && newActiveForm !== item.activeForm) {\n\t\t\titem.activeForm = newActiveForm;\n\t\t\tchanged = true;\n\t\t}\n\t\tconst newContent = normaliseUpdateField(op.content);\n\t\tif (newContent !== undefined && newContent !== item.content) {\n\t\t\titem.content = newContent;\n\t\t\tchanged = true;\n\t\t}\n\t\tif (changed && item.version !== undefined) item.version++;\n\t}\n\treturn { items, changed };\n}\n\nfunction summarize(items: TodoItem[]): { total: number; pending: number; inProgress: number; completed: number } {\n\treturn {\n\t\ttotal: items.length,\n\t\tpending: items.filter((t) => t.status === \"pending\").length,\n\t\tinProgress: items.filter((t) => t.status === \"in_progress\").length,\n\t\tcompleted: items.filter((t) => t.status === \"completed\").length,\n\t};\n}\n\n/**\n * Create the todo_update tool.\n *\n * Stale revisions are recovered internally and deterministically: the engine\n * reads the current state, rebases the (non-conflicting) intent exactly once,\n * and applies it. TODO failures are typed and never terminate the active run.\n *\n * @param getSessionTodos - Callback to get the current todos from session\n * @param setSessionTodos - Callback to set todos in session\n * @param getRevision - Callback to get the current todo revision number\n * @param loopGuard - Guard instance to track consecutive calls (backward-compat)\n * @param snapshotEnforcement - Optional snapshot validation (stale-safe)\n * @param rejectionState - Deprecated; retained for compatibility\n * @param engine - Optional shared TodoEngine (idempotency, rebase, loop detection)\n */\nexport function createTodoUpdateTool(\n\tgetSessionTodos: () => TodoItem[],\n\tsetSessionTodos: (todos: TodoItem[]) => void,\n\tgetRevision: () => number,\n\t_loopGuard: TodoLoopGuard,\n\tsnapshotEnforcement?: TodoUpdateSnapshotEnforcement,\n\t_rejectionState?: TodoUpdateRejectionState,\n\tengine: TodoEngine = new TodoEngine(\"session\"),\n): AgentTool<typeof todoUpdateSchema> {\n\treturn {\n\t\tname: \"todo_update\",\n\t\tlabel: \"todo_update\",\n\t\tdescription:\n\t\t\t\"Apply partial progress transitions to the todo list without replacing the entire list. \" +\n\t\t\t\"Use this to mark items as in_progress or completed, or to update activeForm/content. \" +\n\t\t\t\"Each update identifies a todo by its stable id from todo_read or a prior todo_write. \" +\n\t\t\t\"Requires expectedRevision from the last read or mutation. \" +\n\t\t\t\"Multiple updates in one call are applied atomically. \" +\n\t\t\t\"Jensen automatically reads the current state and rebases a stale revision once.\",\n\t\tparameters: todoUpdateSchema,\n\t\texecute: async (_toolCallId: string, input: TodoUpdateInput, _signal?: AbortSignal) => {\n\t\t\tconst { updates } = input;\n\t\t\tconst requestedRevision = input.expectedRevision;\n\n\t\t\tconst returnConflict = (result: {\n\t\t\t\tmessage: string;\n\t\t\t\tcurrentItems: TodoItem[];\n\t\t\t\tcurrentRevision: number;\n\t\t\t\terrorCode?: string;\n\t\t\t\tconsecutiveFailures?: number;\n\t\t\t}) => {\n\t\t\t\tconst sum = summarize(result.currentItems);\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [{ type: \"text\" as const, text: result.message }],\n\t\t\t\t\tdetails: {\n\t\t\t\t\t\terrorCode: result.errorCode ?? \"TODO_REBASE_CONFLICT\",\n\t\t\t\t\t\trecoverable: true,\n\t\t\t\t\t\trunMustContinue: true,\n\t\t\t\t\t\tconsecutiveFailures: result.consecutiveFailures,\n\t\t\t\t\t\trequestedRevision,\n\t\t\t\t\t\tcurrentRevision: result.currentRevision,\n\t\t\t\t\t\tconflictItemIds: undefined,\n\t\t\t\t\t\tsum,\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t};\n\n\t\t\tconst registerNonfatalFailure = (\n\t\t\t\terrorCode: string,\n\t\t\t\tcurrentRevision: number,\n\t\t\t\tconflictItemIds: string[] = [],\n\t\t\t): { blocked: boolean; consecutive: number } =>\n\t\t\t\tengine.registerFailure({\n\t\t\t\t\tscopeId: engine.scopeId,\n\t\t\t\t\terrorCode,\n\t\t\t\t\tintentHash,\n\t\t\t\t\trequestedRevision,\n\t\t\t\t\tcurrentRevision,\n\t\t\t\t\tconflictItemIds,\n\t\t\t\t});\n\n\t\t\t// Validate updates non-empty\n\t\t\tif (!Array.isArray(updates) || updates.length === 0) {\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [{ type: \"text\", text: \"Error: updates must be a non-empty array\" }],\n\t\t\t\t\tdetails: undefined,\n\t\t\t\t};\n\t\t\t}\n\n\t\t\t// Validate each update has at least one change and a valid status\n\t\t\tconst ops: TodoPatchOp[] = [];\n\t\t\tfor (const update of updates) {\n\t\t\t\tif (!update.id || typeof update.id !== \"string\") {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tcontent: [{ type: \"text\", text: \"Error: each update must have a non-empty id field\" }],\n\t\t\t\t\t\tdetails: undefined,\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\tif (update.status === undefined && update.activeForm === undefined && update.content === undefined) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tcontent: [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\t\ttext: `Error: update for id \"${update.id}\" has no fields to change (status, activeForm, or content required)`,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t],\n\t\t\t\t\t\tdetails: undefined,\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\tif (update.status !== undefined && ![\"pending\", \"in_progress\", \"completed\"].includes(update.status)) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tcontent: [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\t\ttext: `Error: invalid status \"${update.status}\" for id \"${update.id}\"`,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t],\n\t\t\t\t\t\tdetails: undefined,\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\tops.push({\n\t\t\t\t\tid: update.id,\n\t\t\t\t\tstatus: update.status,\n\t\t\t\t\tactiveForm: update.activeForm,\n\t\t\t\t\tcontent: update.content,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tconst currentItems = getSessionTodos().map((t) => ({ ...t }));\n\t\t\tconst currentRevision = getRevision();\n\t\t\tconst intentHash = hashIntent(ops);\n\t\t\tconst idempotencyKey = `${engine.scopeId}|${intentHash}|${requestedRevision}`;\n\n\t\t\t// Idempotency: exact already-applied retries return the original result.\n\t\t\tif (engine.lookupApplied(idempotencyKey)) {\n\t\t\t\tengine.emit({\n\t\t\t\t\ttype: \"TODO_INTENT_ALREADY_APPLIED\",\n\t\t\t\t\tintentId: intentHash,\n\t\t\t\t\trequestedRevision,\n\t\t\t\t\tcurrentRevision,\n\t\t\t\t});\n\t\t\t\tconst sum = summarize(currentItems);\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\ttext: `Todo progress already applied in a previous call (revision ${requestedRevision}). Continue executing the active task.`,\n\t\t\t\t\t\t},\n\t\t\t\t\t],\n\t\t\t\t\tdetails: {\n\t\t\t\t\t\tchanged: true,\n\t\t\t\t\t\talreadyApplied: true,\n\t\t\t\t\t\ttotal: sum.total,\n\t\t\t\t\t\tpending: sum.pending,\n\t\t\t\t\t\tinProgress: sum.inProgress,\n\t\t\t\t\t\tcompleted: sum.completed,\n\t\t\t\t\t\trevision: currentRevision,\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tengine.emit({\n\t\t\t\ttype: \"TODO_MUTATION_INTENT_CREATED\",\n\t\t\t\tintentId: intentHash,\n\t\t\t\trequestedRevision,\n\t\t\t\tcurrentRevision,\n\t\t\t});\n\n\t\t\t// -------- Current-revision fast path --------\n\t\t\tif (requestedRevision === currentRevision) {\n\t\t\t\tconst before = summarize(currentItems);\n\t\t\t\t// All IDs must exist for a direct apply.\n\t\t\t\tconst knownIds = new Set(currentItems.map((t) => t.id).filter(Boolean));\n\t\t\t\tfor (const op of ops) {\n\t\t\t\t\tif (!knownIds.has(op.id)) {\n\t\t\t\t\t\tconst failure = registerNonfatalFailure(\"TODO_ITEM_NOT_FOUND\", currentRevision, [op.id]);\n\t\t\t\t\t\tconst sum = summarize(currentItems);\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tcontent: [\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\t\t\ttext: `Error: unknown todo id \"${op.id}\". Call todo_read to get current IDs and retry.`,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\tdetails: {\n\t\t\t\t\t\t\t\tunknownId: op.id,\n\t\t\t\t\t\t\t\terrorCode: failure.blocked ? \"TODO_NO_PROGRESS_LOOP\" : \"TODO_ITEM_NOT_FOUND\",\n\t\t\t\t\t\t\t\trecoverable: true,\n\t\t\t\t\t\t\t\trunMustContinue: true,\n\t\t\t\t\t\t\t\tconsecutiveFailures: failure.consecutive,\n\t\t\t\t\t\t\t\tsum,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t};\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tfor (const op of ops) {\n\t\t\t\t\tif (op.status === undefined) continue;\n\t\t\t\t\tconst item = currentItems.find((candidate) => candidate.id === op.id);\n\t\t\t\t\tif (!item) continue;\n\t\t\t\t\tconst transition = validateTransition(item.status, op.status);\n\t\t\t\t\tif (!transition.ok) {\n\t\t\t\t\t\tconst failure = registerNonfatalFailure(\"TODO_INVALID_STATUS_TRANSITION\", currentRevision, [op.id]);\n\t\t\t\t\t\tconst sum = summarize(currentItems);\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tcontent: [{ type: \"text\", text: `TODO_INVALID_STATUS_TRANSITION: ${transition.reason}` }],\n\t\t\t\t\t\t\tdetails: {\n\t\t\t\t\t\t\t\terrorCode: failure.blocked ? \"TODO_NO_PROGRESS_LOOP\" : \"TODO_INVALID_STATUS_TRANSITION\",\n\t\t\t\t\t\t\t\trecoverable: true,\n\t\t\t\t\t\t\t\trunMustContinue: true,\n\t\t\t\t\t\t\t\tconsecutiveFailures: failure.consecutive,\n\t\t\t\t\t\t\t\tsum,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t};\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tconst { items, changed } = applyOps(currentItems, ops);\n\t\t\t\tif (!changed) {\n\t\t\t\t\tengine.emit({\n\t\t\t\t\t\ttype: \"TODO_MUTATION_REJECTED\",\n\t\t\t\t\t\tintentId: intentHash,\n\t\t\t\t\t\trequestedRevision,\n\t\t\t\t\t\tcurrentRevision,\n\t\t\t\t\t});\n\t\t\t\t\treturn {\n\t\t\t\t\t\tcontent: [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\t\ttext: `Todo progress unchanged (${before.total} total: ${before.pending} pending, ${before.inProgress} in progress, ${before.completed} completed). Continue executing the active task.`,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t],\n\t\t\t\t\t\tdetails: {\n\t\t\t\t\t\t\tchanged: false,\n\t\t\t\t\t\t\ttotal: before.total,\n\t\t\t\t\t\t\tpending: before.pending,\n\t\t\t\t\t\t\tinProgress: before.inProgress,\n\t\t\t\t\t\t\tcompleted: before.completed,\n\t\t\t\t\t\t\trevision: currentRevision,\n\t\t\t\t\t\t},\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\tsetSessionTodos(items);\n\t\t\t\tconst newRevision = getRevision();\n\t\t\t\tengine.recordState(newRevision, items);\n\t\t\t\tengine.recordApplied(idempotencyKey, newRevision);\n\t\t\t\tengine.recordProgress();\n\t\t\t\tengine.emit({\n\t\t\t\t\ttype: \"TODO_MUTATION_COMMITTED\",\n\t\t\t\t\tintentId: intentHash,\n\t\t\t\t\tcurrentRevision: newRevision,\n\t\t\t\t});\n\t\t\t\tsnapshotEnforcement?.invalidateSnapshot();\n\t\t\t\tconst sum = summarize(items);\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [{ type: \"text\", text: `Todo progress updated. Continue executing the active task.` }],\n\t\t\t\t\tdetails: {\n\t\t\t\t\t\tchanged: true,\n\t\t\t\t\t\ttotal: sum.total,\n\t\t\t\t\t\tpending: sum.pending,\n\t\t\t\t\t\tinProgress: sum.inProgress,\n\t\t\t\t\t\tcompleted: sum.completed,\n\t\t\t\t\t\trequestedRevision,\n\t\t\t\t\t\trevision: newRevision,\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t}\n\n\t\t\t// -------- Stale revision: internal read + bounded rebase --------\n\t\t\tengine.emit({\n\t\t\t\ttype: \"TODO_REVISION_STALE_DETECTED\",\n\t\t\t\tintentId: intentHash,\n\t\t\t\trequestedRevision,\n\t\t\t\tcurrentRevision,\n\t\t\t});\n\t\t\tengine.emit({\n\t\t\t\ttype: \"TODO_INTERNAL_READ_COMPLETED\",\n\t\t\t\tcurrentRevision,\n\t\t\t});\n\t\t\tengine.emit({ type: \"TODO_REBASE_STARTED\", intentId: intentHash, currentRevision });\n\n\t\t\tconst maxAttempts = engine.limits.maxInternalRebaseAttempts;\n\t\t\tlet attempt = 0;\n\t\t\tlet rebasedItems = currentItems;\n\t\t\tlet rebasedRevision = currentRevision;\n\t\t\tlet rebase = engine.rebase(requestedRevision, rebasedRevision, rebasedItems, ops);\n\n\t\t\t// Bounded retry: if the state advanced again during our read, retry once.\n\t\t\twhile (rebase.status === \"conflict\" && attempt < maxAttempts && getRevision() !== currentRevision) {\n\t\t\t\trebasedItems = getSessionTodos().map((t) => ({ ...t }));\n\t\t\t\trebasedRevision = getRevision();\n\t\t\t\tengine.emit({\n\t\t\t\t\ttype: \"TODO_INTERNAL_READ_COMPLETED\",\n\t\t\t\t\tcurrentRevision: rebasedRevision,\n\t\t\t\t});\n\t\t\t\tengine.recordReadSnapshot(rebasedRevision, rebasedItems);\n\t\t\t\trebase = engine.rebase(requestedRevision, rebasedRevision, rebasedItems, ops);\n\t\t\t\tattempt++;\n\t\t\t}\n\n\t\t\tif (rebase.status === \"conflict\") {\n\t\t\t\tconst failure = registerNonfatalFailure(\n\t\t\t\t\t\"TODO_REBASE_CONFLICT\",\n\t\t\t\t\trebase.currentRevision,\n\t\t\t\t\trebase.conflictItemIds,\n\t\t\t\t);\n\t\t\t\tengine.emit({\n\t\t\t\t\ttype: \"TODO_REBASE_CONFLICT\",\n\t\t\t\t\tintentId: intentHash,\n\t\t\t\t\tcurrentRevision: rebase.currentRevision,\n\t\t\t\t\tconflictItemIds: rebase.conflictItemIds,\n\t\t\t\t});\n\t\t\t\tengine.emit({\n\t\t\t\t\ttype: \"TODO_MUTATION_REJECTED\",\n\t\t\t\t\tintentId: intentHash,\n\t\t\t\t\trequestedRevision,\n\t\t\t\t\tcurrentRevision: rebase.currentRevision,\n\t\t\t\t});\n\t\t\t\t// Typed, nonfatal conflict (or rebase ambiguity). Run continues.\n\t\t\t\tconst conflictList =\n\t\t\t\t\trebase.conflictItemIds.length > 0 ? rebase.conflictItemIds.join(\", \") : \"unknown items\";\n\t\t\t\treturn returnConflict({\n\t\t\t\t\tmessage: `${failure.blocked ? \"TODO_NO_PROGRESS_LOOP\" : \"TODO_REBASE_CONFLICT\"}: The todo list advanced to revision ${rebase.currentRevision}; your intent based on revision ${requestedRevision} conflicts with concurrent changes (items: ${conflictList}). No update was applied and execution continues.`,\n\t\t\t\t\tcurrentItems: rebasedItems,\n\t\t\t\t\tcurrentRevision: rebase.currentRevision,\n\t\t\t\t\terrorCode: failure.blocked ? \"TODO_NO_PROGRESS_LOOP\" : \"TODO_REBASE_CONFLICT\",\n\t\t\t\t\tconsecutiveFailures: failure.consecutive,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\t// Rebase succeeded (rebased or already_applied).\n\t\t\tif (rebase.status === \"already_applied\") {\n\t\t\t\tengine.recordApplied(idempotencyKey, currentRevision);\n\t\t\t\tengine.emit({\n\t\t\t\t\ttype: \"TODO_INTENT_ALREADY_APPLIED\",\n\t\t\t\t\tintentId: intentHash,\n\t\t\t\t\trequestedRevision,\n\t\t\t\t\tcurrentRevision,\n\t\t\t\t});\n\t\t\t\tengine.resetFailureChain();\n\t\t\t\tconst sum = summarize(currentItems);\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\ttext: `Todo progress unchanged (revision ${currentRevision}); your intended transitions were already applied. Continuing execution.`,\n\t\t\t\t\t\t},\n\t\t\t\t\t],\n\t\t\t\t\tdetails: {\n\t\t\t\t\t\tchanged: false,\n\t\t\t\t\t\talreadyApplied: true,\n\t\t\t\t\t\trebaseStatus: \"already_applied\",\n\t\t\t\t\t\ttotal: sum.total,\n\t\t\t\t\t\tpending: sum.pending,\n\t\t\t\t\t\tinProgress: sum.inProgress,\n\t\t\t\t\t\tcompleted: sum.completed,\n\t\t\t\t\t\trequestedRevision,\n\t\t\t\t\t\tcurrentRevision,\n\t\t\t\t\t\trevision: currentRevision,\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t}\n\n\t\t\t// Conflict-free rebase: apply rebased ops onto the current state.\n\t\t\tconst { items, changed } = applyOps(rebasedItems, ops);\n\t\t\tif (changed) {\n\t\t\t\tsetSessionTodos(items);\n\t\t\t\tconst newRevision = getRevision();\n\t\t\t\tengine.recordState(newRevision, items);\n\t\t\t\tengine.recordApplied(idempotencyKey, newRevision);\n\t\t\t\tengine.recordProgress();\n\t\t\t\tengine.emit({\n\t\t\t\t\ttype: \"TODO_REBASE_SUCCEEDED\",\n\t\t\t\t\tintentId: intentHash,\n\t\t\t\t\tcurrentRevision: newRevision,\n\t\t\t\t\trequestedRevision,\n\t\t\t\t});\n\t\t\t\tengine.emit({\n\t\t\t\t\ttype: \"TODO_MUTATION_COMMITTED\",\n\t\t\t\t\tintentId: intentHash,\n\t\t\t\t\tcurrentRevision: newRevision,\n\t\t\t\t});\n\t\t\t\tsnapshotEnforcement?.invalidateSnapshot();\n\t\t\t\tengine.resetFailureChain();\n\t\t\t\tconst sum = summarize(items);\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\ttext: `Todo progress updated after automatic rebase (revision ${requestedRevision} -> ${newRevision}). Continued executing the active task.`,\n\t\t\t\t\t\t},\n\t\t\t\t\t],\n\t\t\t\t\tdetails: {\n\t\t\t\t\t\tchanged: true,\n\t\t\t\t\t\trebased: true,\n\t\t\t\t\t\toriginalRevision: requestedRevision,\n\t\t\t\t\t\tcurrentRevision: newRevision,\n\t\t\t\t\t\tpreservedConcurrentChanges: rebase.preservedConcurrentChanges,\n\t\t\t\t\t\ttotal: sum.total,\n\t\t\t\t\t\tpending: sum.pending,\n\t\t\t\t\t\tinProgress: sum.inProgress,\n\t\t\t\t\t\tcompleted: sum.completed,\n\t\t\t\t\t\trevision: newRevision,\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t}\n\n\t\t\t// No actual change after rebase.\n\t\t\tengine.resetFailureChain();\n\t\t\tconst sum = summarize(currentItems);\n\t\t\treturn {\n\t\t\t\tcontent: [\n\t\t\t\t\t{\n\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\ttext: `Todo progress unchanged (revision ${currentRevision}). Continuing execution.`,\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t\tdetails: {\n\t\t\t\t\tchanged: false,\n\t\t\t\t\trebaseStatus: \"rebased\",\n\t\t\t\t\ttotal: sum.total,\n\t\t\t\t\tpending: sum.pending,\n\t\t\t\t\tinProgress: sum.inProgress,\n\t\t\t\t\tcompleted: sum.completed,\n\t\t\t\t\trevision: currentRevision,\n\t\t\t\t},\n\t\t\t};\n\t\t},\n\t};\n}\n\n/** Default todo_update tool - requires session binding for state management */\nexport const todoUpdateTool: AgentTool<typeof todoUpdateSchema> = createTodoUpdateTool(\n\t() => [],\n\t() => {},\n\t() => 0,\n\tnew TodoLoopGuard(),\n);\n"]}