feat(todo): allow several in_progress todos at once

Remove the single-in_progress cap from todo_write execute validation and
the durable-log invariant so a task list can mirror genuinely parallel
work (concurrent subagents, background commands). Update the tool
description to instruct marking every actively worked task in_progress,
refresh the tool catalog and keyless snapshot expected outputs, and
record the decision in a new Agent Note superseding the original cap.
This commit is contained in:
Chinesezjc
2026-07-26 02:50:47 +08:00
parent 6f50208ea3
commit 876065a97d
44 changed files with 150 additions and 68 deletions
@@ -17,11 +17,12 @@ function event(todos: unknown): SessionEvent {
}
describe('todo snapshot invariants', () => {
it('accepts a unique whole-list snapshot with one active item', async () => {
it('accepts a unique whole-list snapshot, including several active items', async () => {
const ctx = await setup()
expect(() => { ctx.emit('session/event', {} as Session, event([
{ content: 'Inspect state', status: 'completed' },
{ content: 'Apply fix', status: 'in_progress' },
{ content: 'Watch background build', status: 'in_progress' },
{ content: 'Run checks', status: 'pending' },
])) }).not.toThrow()
})
@@ -36,7 +37,6 @@ describe('todo snapshot invariants', () => {
[[{ content: 'same', status: 'pending' }, { content: 'same', status: 'completed' }], /repeats content/],
[[{ content: 'task', status: 42 }], /unknown status/],
[[{ content: 'task', status: 'paused' }], /unknown status/],
[[{ content: 'one', status: 'in_progress' }, { content: 'two', status: 'in_progress' }], /at most one/],
])('rejects an incoherent durable todo snapshot', async (todos, message) => {
const ctx = await setup()
expect(() => { ctx.emit('session/event', {} as Session, event(todos)) }).toThrow(message)
@@ -122,10 +122,27 @@ describe('dsh-tool-todo', () => {
expect(result.isError).toBe(true)
})
it('accepts several in_progress items at once (parallel work)', async () => {
const ctx = await setup()
const agent = agentWithSession('parallel')
const todos: TodoItem[] = [
{ content: 'run subagent a', status: 'in_progress' },
{ content: 'run subagent b', status: 'in_progress' },
{ content: 'merge results', status: 'pending' },
]
const result = await callTodo(ctx, { todos }, { agent })
expect(result.isError).toBe(false)
if (result.isError) throw new Error('expected todo_write success')
expect(result.value).toEqual({
todos,
counts: { pending: 1, inProgress: 2, completed: 0 },
})
expect(agent.session.events.findLast(e => e.type === 'todo/write')!.data.todos).toEqual(todos)
})
it.each([
{ label: 'empty content', todos: [{ content: ' ', status: 'pending' }], fragment: 'non-empty' },
{ label: 'duplicate content', todos: [{ content: 'dup', status: 'pending' }, { content: 'dup', status: 'completed' }], fragment: 'duplicate' },
{ label: 'two in_progress', todos: [{ content: 'a', status: 'in_progress' }, { content: 'b', status: 'in_progress' }], fragment: 'in_progress' },
])('rejects $label as an isError result', async ({ todos, fragment }) => {
const ctx = await setup()
const result = await callTodo(ctx, { todos })