fix(coding-agent): reject task-N ID confusion, error on partial todo apply

- todo-write.md adds an explicit note that tasks are referenced by
  verbatim content text; the tool never emits task-N IDs.
- resolveTaskOrError rejects ^task-\d+$ inputs with a clarifying error.
- execute sets isError:true when any op failed.
- appendItems short-circuits on the first "already exists" error so the
  call no longer applies the prefix of a doomed batch.
This commit is contained in:
can1357
2026-05-19 19:24:16 +09:00
committed by Can Bölük
parent fcd38b7143
commit b191e6c5ff
2 changed files with 13 additions and 4 deletions
@@ -1,3 +1,5 @@
**Tasks are referenced by their verbatim content string, not by any auto-generated ID. There is no "task-1"/"task-N" identifier — the tool never emits one. Pass the task's content text in the `task` field.**
Manages a phased task list. Pass `ops`: a flat array of operations.
The next pending task is auto-promoted to `in_progress` after each completion.
Allowed `op` values are only `init`, `start`, `done`, `drop`, `rm`, `append`, and `note`. `pending` is a task status, not an `op`; leave not-yet-started tasks implicit in `init`/`append` lists.
+11 -4
View File
@@ -150,9 +150,15 @@ function resolveTaskOrError(
}
const hit = findTaskByContent(phases, content);
if (!hit) {
const totalTasks = phases.reduce((sum, phase) => sum + phase.tasks.length, 0);
const hint = totalTasks === 0 ? " (todo list is empty — was it replaced or not yet created?)" : "";
errors.push(`Task "${content}" not found${hint}`);
if (/^task-\d+$/.test(content)) {
errors.push(
`Task "${content}" not found. Tasks are referenced by content, not by IDs — pass the task's full text from the previous result.`,
);
} else {
const totalTasks = phases.reduce((sum, phase) => sum + phase.tasks.length, 0);
const hint = totalTasks === 0 ? " (todo list is empty — was it replaced or not yet created?)" : "";
errors.push(`Task "${content}" not found${hint}`);
}
}
return hit;
}
@@ -209,7 +215,7 @@ function appendItems(phases: TodoPhase[], entry: TodoOpEntryValue, errors: strin
for (const content of entry.items) {
if (findTaskByContent(phases, content)) {
errors.push(`Task "${content}" already exists`);
continue;
return phases;
}
phase.tasks.push({ content, status: "pending" });
}
@@ -513,6 +519,7 @@ export class TodoWriteTool implements AgentTool<typeof todoWriteSchema, TodoWrit
return {
content: [{ type: "text", text: formatSummary(updated, errors) }],
details: { phases: updated, storage },
isError: errors.length > 0 ? true : undefined,
};
}
}