Merge remote-tracking branch 'origin/master' into feat/todo-multi-in-progress

# Conflicts:
#	.agents/notes/implemented/feature/2026-06-29-todo-write-tool.i18n.yaml
#	.agents/notes/implemented/feature/2026-07-23-web-todo-display.i18n.yaml
#	docs/core-data-structures/session.i18n.yaml
#	examples/acp-agent/tests/snapshots/fs-policy-reject/session.jsonl
#	examples/acp-agent/tests/snapshots/fs-write-overwrite/session.jsonl
#	examples/acp-agent/tests/snapshots/fs-write/session.jsonl
#	examples/acp-agent/tests/snapshots/todo-write/session.jsonl
#	examples/headless-agent/tests/todo-write.e2e.ts
#	packages/client/ui-conversation/README.i18n.yaml
#	packages/client/ui-conversation/README.md
#	packages/client/ui-conversation/README.zh.md
#	packages/todo/tool-todo/README.i18n.yaml
This commit is contained in:
Chinesezjc
2026-07-28 20:41:55 +08:00
391 changed files with 6129 additions and 3065 deletions
@@ -1,3 +1,4 @@
import { createUserMessage } from '@deepseek-ai/dsh-llm'
import { describe, expect, it } from 'vitest'
import { Context } from 'cordis'
import { SessionId, type SessionEvent } from '@deepseek-ai/dsh-session'
@@ -59,12 +60,12 @@ describe('todo_write tool through the agent loop', () => {
const ctx = await harness(adapter)
const agent = ctx.agentLoop.create(SessionId('it-todo'), { provider: 'mock', model: 'mock' })
agent.followup({ content: [{ type: 'text', text: 'plan a two-step task' }], source: { kind: 'user' } })
agent.followup(createUserMessage({ content: [{ type: 'text', text: 'plan a two-step task' }], source: { kind: 'user' } }))
await waitForIdle(ctx, agent)
const log = agent.session.events
expect(findEvent(log, 'tool/call').data.name).toBe('todo_write')
expect(findEvent(log, 'tool/result').data.isError).toBe(false)
expect(findEvent(log, 'tool/result').data.message.content[0].isError).toBe(false)
const todoEvent = findEvent(log, 'todo/write')
expect(todoEvent.data.todos).toEqual([
@@ -87,7 +88,7 @@ describe('todo_write tool through the agent loop', () => {
const ctx = await harness(adapter)
const agent = ctx.agentLoop.create(SessionId('it-todo-2'), { provider: 'mock', model: 'mock' })
agent.followup({ content: [{ type: 'text', text: 'plan then update' }], source: { kind: 'user' } })
agent.followup(createUserMessage({ content: [{ type: 'text', text: 'plan then update' }], source: { kind: 'user' } }))
await waitForIdle(ctx, agent)
const todoEvents = agent.session.events.filter(e => e.type === 'todo/write')
@@ -11,6 +11,7 @@ import { describe, expect, it } from 'vitest'
import { Context } from 'cordis'
import AgentRegistry from '@deepseek-ai/dsh-agent'
import type { Agent } from '@deepseek-ai/dsh-agent'
import { createUserMessage } from '@deepseek-ai/dsh-llm'
import SessionStore from '@deepseek-ai/dsh-session'
import type { Session, TodoItem } from '@deepseek-ai/dsh-session'
import SystemPrompt from '@deepseek-ai/dsh-system-prompt'
@@ -58,7 +59,10 @@ async function harness(withTodoTool: boolean): Promise<Bench> {
/** One paginable message so the tail page is non-degenerate. */
function seedMessage(session: Session): void {
session.append('user/message', { content: [{ type: 'text', text: 'hi' }], source: { kind: 'user' } }, { surfaceOp: 'append' })
session.append('user/message', createUserMessage({
content: [{ type: 'text', text: 'hi' }],
source: { kind: 'user' },
}), { surfaceOp: 'append' })
}
describe('todos projection provider', () => {
@@ -87,6 +91,20 @@ describe('todos projection provider', () => {
expect(projections?.asOfSeq).toBe(session.seq - 1)
})
it('clears the standing plan on the next turn/start (turn/end keeps it)', async () => {
const bench = await harness(true)
const session = bench.session
seedMessage(session)
const list: TodoItem[] = [{ content: 'done', status: 'completed' }]
session.append('todo/write', { todos: list })
session.append('turn/end', { turn: 0, reason: { kind: 'completed' } })
expect((await bench.tailProjections())?.values.todos).toEqual(list)
session.append('turn/start', { turn: 1, trigger: { kind: 'message', source: { kind: 'user' } } })
const cleared = await bench.tailProjections()
expect(cleared?.values.todos).toBeNull()
expect(cleared?.asOfSeq).toBe(session.seq - 1)
})
it('has no todos key when tool-todo is not composed', async () => {
const bench = await harness(false)
seedMessage(bench.session)