From 39a0d111c2dd66e11b53dbb9df2b3312b35f619e Mon Sep 17 00:00:00 2001 From: MarkXian Date: Sat, 29 Aug 2026 18:23:08 +0800 Subject: [PATCH 1/2] fix(vscode): restore todo list card display Signed-off-by: MarkXian --- .changeset/clean-todos-display.md | 5 +++ .../todo/tools/todo-list/todoListTool.ts | 4 ++ .../features/todo/tools/todo-list.test.ts | 39 +++++++++++++++++++ 3 files changed, 48 insertions(+) create mode 100644 .changeset/clean-todos-display.md diff --git a/.changeset/clean-todos-display.md b/.changeset/clean-todos-display.md new file mode 100644 index 00000000000..ca712d58891 --- /dev/null +++ b/.changeset/clean-todos-display.md @@ -0,0 +1,5 @@ +--- +'@moonshot-ai/kimi-code-sdk': patch +--- + +Restore structured TodoList display metadata for v2 sessions so SDK consumers can render todo items and statuses. diff --git a/packages/agent-core-v2/src/features/todo/tools/todo-list/todoListTool.ts b/packages/agent-core-v2/src/features/todo/tools/todo-list/todoListTool.ts index 03a2bc1af9d..bbcd7485eff 100644 --- a/packages/agent-core-v2/src/features/todo/tools/todo-list/todoListTool.ts +++ b/packages/agent-core-v2/src/features/todo/tools/todo-list/todoListTool.ts @@ -42,6 +42,10 @@ export class TodoListTool implements ITodoListTool { : 'Updating todo list'; return { description, + display: { + kind: 'todo_list', + items: (args.todos ?? this.todo.get()).map((todo) => ({ ...todo })), + }, approvalRule: this.name, execute: async () => { if (args.todos === undefined) { diff --git a/packages/agent-core-v2/test/features/todo/tools/todo-list.test.ts b/packages/agent-core-v2/test/features/todo/tools/todo-list.test.ts index d53c310343e..84316fbb38b 100644 --- a/packages/agent-core-v2/test/features/todo/tools/todo-list.test.ts +++ b/packages/agent-core-v2/test/features/todo/tools/todo-list.test.ts @@ -152,4 +152,43 @@ describe('TodoListTool', () => { expect(clearExecution.description).toBe('Clearing todo list'); expect(updateExecution.description).toBe('Updating todo list'); }); + + it('exposes a defensive todo-list display for query, update, and clear modes', () => { + const current: TodoItem[] = [{ title: 'existing', status: 'in_progress' }]; + const { tool } = makeTool(current); + const next: TodoItem[] = [{ title: 'next', status: 'pending' }]; + + const readExecution = tool.resolveExecution({}); + const updateExecution = tool.resolveExecution({ todos: next }); + const clearExecution = tool.resolveExecution({ todos: [] }); + + if ( + readExecution.isError === true || + updateExecution.isError === true || + clearExecution.isError === true + ) { + throw new TypeError('expected runnable executions'); + } + + expect(readExecution.display).toEqual({ + kind: 'todo_list', + items: [{ title: 'existing', status: 'in_progress' }], + }); + expect(updateExecution.display).toEqual({ + kind: 'todo_list', + items: [{ title: 'next', status: 'pending' }], + }); + expect(clearExecution.display).toEqual({ kind: 'todo_list', items: [] }); + + current[0] = { title: 'mutated current', status: 'done' }; + next[0] = { title: 'mutated next', status: 'done' }; + expect(readExecution.display).toEqual({ + kind: 'todo_list', + items: [{ title: 'existing', status: 'in_progress' }], + }); + expect(updateExecution.display).toEqual({ + kind: 'todo_list', + items: [{ title: 'next', status: 'pending' }], + }); + }); }); From 0c12f1c72012fe8c680a3d85f2d0439938bb18a8 Mon Sep 17 00:00:00 2001 From: MarkXian Date: Sat, 29 Aug 2026 19:07:50 +0800 Subject: [PATCH 2/2] fix(vscode): resolve todo query display at execution Signed-off-by: MarkXian --- .../agent/toolExecutor/toolExecutorService.ts | 2 ++ .../todo/tools/todo-list/todoListTool.ts | 18 ++++++++--- .../agent-core-v2/src/tool/toolContract.ts | 2 ++ .../agent/toolExecutor/toolExecutor.test.ts | 22 +++++++++++++ .../features/todo/tools/todo-list.test.ts | 32 +++++++++++++------ 5 files changed, 62 insertions(+), 14 deletions(-) diff --git a/packages/agent-core-v2/src/agent/toolExecutor/toolExecutorService.ts b/packages/agent-core-v2/src/agent/toolExecutor/toolExecutorService.ts index 47fc56a48cf..8c05d9cded3 100644 --- a/packages/agent-core-v2/src/agent/toolExecutor/toolExecutorService.ts +++ b/packages/agent-core-v2/src/agent/toolExecutor/toolExecutorService.ts @@ -882,6 +882,7 @@ function normalizeToolResult(result: ExecutableToolResult): ToolResult { } const base: { output: ToolResult['output']; + display?: ToolInputDisplay; stopTurn?: boolean; truncated?: true; note?: string; @@ -889,6 +890,7 @@ function normalizeToolResult(result: ExecutableToolResult): ToolResult { spillExempt?: true; } = { output, + display: result.display, stopTurn: result.stopTurn, spill: result.spill, spillExempt: result.spillExempt, diff --git a/packages/agent-core-v2/src/features/todo/tools/todo-list/todoListTool.ts b/packages/agent-core-v2/src/features/todo/tools/todo-list/todoListTool.ts index bbcd7485eff..37a7c837049 100644 --- a/packages/agent-core-v2/src/features/todo/tools/todo-list/todoListTool.ts +++ b/packages/agent-core-v2/src/features/todo/tools/todo-list/todoListTool.ts @@ -42,14 +42,22 @@ export class TodoListTool implements ITodoListTool { : 'Updating todo list'; return { description, - display: { - kind: 'todo_list', - items: (args.todos ?? this.todo.get()).map((todo) => ({ ...todo })), - }, + display: + args.todos === undefined + ? undefined + : { + kind: 'todo_list', + items: args.todos.map((todo) => ({ ...todo })), + }, approvalRule: this.name, execute: async () => { if (args.todos === undefined) { - return { isError: false, output: renderTodoList(this.todo.get()) }; + const current = this.todo.get().map((todo) => ({ ...todo })); + return { + isError: false, + output: renderTodoList(current), + display: { kind: 'todo_list' as const, items: current }, + }; } const next: readonly TodoItem[] = args.todos.map((todo) => ({ diff --git a/packages/agent-core-v2/src/tool/toolContract.ts b/packages/agent-core-v2/src/tool/toolContract.ts index b368b2aa3b4..c2f00d4cc1a 100644 --- a/packages/agent-core-v2/src/tool/toolContract.ts +++ b/packages/agent-core-v2/src/tool/toolContract.ts @@ -31,6 +31,7 @@ export interface ToolDelivery { export interface ExecutableToolSuccessResult { readonly output: ExecutableToolOutput; + readonly display?: ToolInputDisplay; readonly isError?: false | undefined; readonly stopTurn?: boolean | undefined; readonly truncated?: boolean | undefined; @@ -43,6 +44,7 @@ export interface ExecutableToolSuccessResult { export interface ExecutableToolErrorResult { readonly output: ExecutableToolOutput; readonly isError: true; + readonly display?: ToolInputDisplay; readonly stopTurn?: boolean | undefined; readonly truncated?: boolean | undefined; readonly note?: string; diff --git a/packages/agent-core-v2/test/agent/toolExecutor/toolExecutor.test.ts b/packages/agent-core-v2/test/agent/toolExecutor/toolExecutor.test.ts index 6fd95df3191..6f7a01f295f 100644 --- a/packages/agent-core-v2/test/agent/toolExecutor/toolExecutor.test.ts +++ b/packages/agent-core-v2/test/agent/toolExecutor/toolExecutor.test.ts @@ -589,6 +589,28 @@ describe('AgentToolExecutorService', () => { }); }); + it('preserves a display produced at execution time in the final result', async () => { + const tool = new TestTool('dynamic-display', { + result: { + output: 'current state', + display: { + kind: 'generic', + summary: 'Resolved during execution', + detail: { value: 2 }, + }, + }, + }); + registry.register(tool); + + const [result] = await execute([toolCall('call_dynamic_display', 'dynamic-display', {})]); + + expect(result?.display).toEqual({ + kind: 'generic', + summary: 'Resolved during execution', + detail: { value: 2 }, + }); + }); + it('captures tool execution failures as error results', async () => { const tool = new TestTool('fail', { execute: async () => { diff --git a/packages/agent-core-v2/test/features/todo/tools/todo-list.test.ts b/packages/agent-core-v2/test/features/todo/tools/todo-list.test.ts index 84316fbb38b..9d194ff72ff 100644 --- a/packages/agent-core-v2/test/features/todo/tools/todo-list.test.ts +++ b/packages/agent-core-v2/test/features/todo/tools/todo-list.test.ts @@ -153,7 +153,7 @@ describe('TodoListTool', () => { expect(updateExecution.description).toBe('Updating todo list'); }); - it('exposes a defensive todo-list display for query, update, and clear modes', () => { + it('defers query display until execution while writes expose defensive input displays', async () => { const current: TodoItem[] = [{ title: 'existing', status: 'in_progress' }]; const { tool } = makeTool(current); const next: TodoItem[] = [{ title: 'next', status: 'pending' }]; @@ -170,23 +170,37 @@ describe('TodoListTool', () => { throw new TypeError('expected runnable executions'); } - expect(readExecution.display).toEqual({ - kind: 'todo_list', - items: [{ title: 'existing', status: 'in_progress' }], - }); + expect(readExecution.display).toBeUndefined(); expect(updateExecution.display).toEqual({ kind: 'todo_list', items: [{ title: 'next', status: 'pending' }], }); expect(clearExecution.display).toEqual({ kind: 'todo_list', items: [] }); - current[0] = { title: 'mutated current', status: 'done' }; next[0] = { title: 'mutated next', status: 'done' }; - expect(readExecution.display).toEqual({ + expect(updateExecution.display).toEqual({ kind: 'todo_list', - items: [{ title: 'existing', status: 'in_progress' }], + items: [{ title: 'next', status: 'pending' }], }); - expect(updateExecution.display).toEqual({ + + next[0] = { title: 'next', status: 'pending' }; + await updateExecution.execute({ + turnId: 1, + toolCallId: 'update', + signal, + }); + const readResult = await readExecution.execute({ + turnId: 1, + toolCallId: 'read', + signal, + }); + expect(readResult.display).toEqual({ + kind: 'todo_list', + items: [{ title: 'next', status: 'pending' }], + }); + + current[0] = { title: 'mutated current', status: 'done' }; + expect(readResult.display).toEqual({ kind: 'todo_list', items: [{ title: 'next', status: 'pending' }], });