diff options
| author | Adam Malczewski <[email protected]> | 2026-06-02 14:49:49 +0900 |
|---|---|---|
| committer | Adam Malczewski <[email protected]> | 2026-06-02 14:49:49 +0900 |
| commit | ecb001ec7a2e573d8dedf5064e860e5a3e7788fd (patch) | |
| tree | 531371ba6f449a144b5bb45a3d226b025983bd4b /packages/api/tests | |
| parent | 7c527b4d8a72159954405e720d5bf776802dc0ff (diff) | |
| download | dispatch-ecb001ec7a2e573d8dedf5064e860e5a3e7788fd.tar.gz dispatch-ecb001ec7a2e573d8dedf5064e860e5a3e7788fd.zip | |
feat(todo): port opencode's declarative whole-list todo tool
Replace the imperative id-based CRUD todo tool (add/update/list/get/remove)
with opencode's declarative whole-list design: a single `todos` param that
replaces the entire list each call. No model-visible ids, no delta reasoning,
no "task not found" spirals.
- core: TaskItem { id, content, status }; statuses pending|in_progress|
completed|cancelled. TaskList.setTasks/getTasks/onChange. New rich
TODO_DESCRIPTION adapted from opencode's todowrite.txt.
- api: TASK_MANAGEMENT_GUIDANCE system-prompt section (from anthropic.txt);
updated TOOL_DESCRIPTIONS.todo. Reload fix: TabStatusSnapshot now carries
per-tab tasks so getAllStatuses rehydrates the panel on reconnect.
- frontend: mirror types; hydrate tasks from snapshot in both restore paths;
upgrade sidebar Tasks panel to render content + all four statuses + progress.
- tests: new core task-list.test.ts (15); updated api TaskList mocks +
getAllStatuses task-snapshot coverage.
bun run check clean; 569 tests pass; all packages typecheck.
Diffstat (limited to 'packages/api/tests')
| -rw-r--r-- | packages/api/tests/agent-manager.test.ts | 61 | ||||
| -rw-r--r-- | packages/api/tests/routes.test.ts | 21 |
2 files changed, 50 insertions, 32 deletions
diff --git a/packages/api/tests/agent-manager.test.ts b/packages/api/tests/agent-manager.test.ts index 9da6a70..014022a 100644 --- a/packages/api/tests/agent-manager.test.ts +++ b/packages/api/tests/agent-manager.test.ts @@ -279,20 +279,17 @@ vi.mock("@dispatch/core", () => ({ } }, TaskList: class MockTaskList { + private tasks: Array<{ id: string; content: string; status: string }> = []; getTasks() { - return []; - } - getTask() { - return undefined; - } - addTask() { - return { id: "task-1", title: "", description: "", status: "pending" }; - } - updateTask() { - return undefined; + return this.tasks.map((t) => ({ ...t })); } - removeTask() { - return false; + setTasks(items: Array<{ content: string; status?: string }>) { + this.tasks = items.map((item, i) => ({ + id: `task-${i + 1}`, + content: item.content, + status: item.status ?? "pending", + })); + return this.getTasks(); } onChange(_cb: unknown) { return () => {}; @@ -907,7 +904,7 @@ describe("AgentManager", () => { status: "running" | "idle" | "error"; keyId: null; modelId: null; - taskList: { onChange: (cb: unknown) => void }; + taskList: { onChange: (cb: unknown) => void; getTasks: () => unknown[] }; messageQueue: unknown[]; queueListeners: unknown[]; shellStore: unknown; @@ -922,7 +919,7 @@ describe("AgentManager", () => { status: "running", keyId: null, modelId: null, - taskList: { onChange: () => {} }, + taskList: { onChange: () => {}, getTasks: () => [] }, messageQueue: [], queueListeners: [], shellStore: {}, @@ -954,7 +951,7 @@ describe("AgentManager", () => { status: "running"; keyId: null; modelId: null; - taskList: { onChange: (cb: unknown) => void }; + taskList: { onChange: (cb: unknown) => void; getTasks: () => unknown[] }; messageQueue: unknown[]; queueListeners: unknown[]; shellStore: unknown; @@ -970,7 +967,7 @@ describe("AgentManager", () => { status: "running", keyId: null, modelId: null, - taskList: { onChange: () => {} }, + taskList: { onChange: () => {}, getTasks: () => [] }, messageQueue: [], queueListeners: [], shellStore: {}, @@ -996,7 +993,7 @@ describe("AgentManager", () => { status: "running"; keyId: null; modelId: null; - taskList: { onChange: (cb: unknown) => void }; + taskList: { onChange: (cb: unknown) => void; getTasks: () => unknown[] }; messageQueue: unknown[]; queueListeners: unknown[]; shellStore: unknown; @@ -1011,7 +1008,7 @@ describe("AgentManager", () => { status: "running", keyId: null, modelId: null, - taskList: { onChange: () => {} }, + taskList: { onChange: () => {}, getTasks: () => [] }, messageQueue: [], queueListeners: [], shellStore: {}, @@ -1026,6 +1023,30 @@ describe("AgentManager", () => { expect(snap["tab-early"]).not.toHaveProperty("currentAssistantId"); }); + it("getAllStatuses includes a tab's todo list (for reload rehydration)", () => { + const manager = new AgentManager(); + // Public API: getTaskList creates+returns the tab's list. setTasks is + // the declarative whole-list write. + const list = manager.getTaskList("tab-todos"); + list.setTasks([ + { content: "plan", status: "completed" }, + { content: "build", status: "in_progress" }, + ]); + const snap = manager.getAllStatuses(); + expect(snap["tab-todos"]?.tasks).toEqual([ + { id: "task-1", content: "plan", status: "completed" }, + { id: "task-2", content: "build", status: "in_progress" }, + ]); + }); + + it("getAllStatuses omits tasks for a tab with an empty todo list", () => { + const manager = new AgentManager(); + manager.getTaskList("tab-empty"); + const snap = manager.getAllStatuses(); + expect(snap["tab-empty"]).toBeDefined(); + expect(snap["tab-empty"]).not.toHaveProperty("tasks"); + }); + // ─── Tab-to-tab communication ───────────────────────────────── describe("deliverMessage", () => { @@ -1054,7 +1075,7 @@ describe("AgentManager", () => { status: "running", keyId: null, modelId: null, - taskList: { onChange: () => {} }, + taskList: { onChange: () => {}, getTasks: () => [] }, messageQueue: [], queueListeners: [], shellStore: {}, @@ -1175,7 +1196,7 @@ describe("AgentManager", () => { status: "running", keyId: null, modelId: null, - taskList: { onChange: () => {} }, + taskList: { onChange: () => {}, getTasks: () => [] }, messageQueue: [], queueListeners: [], shellStore: {}, diff --git a/packages/api/tests/routes.test.ts b/packages/api/tests/routes.test.ts index c1971b0..a8db5ce 100644 --- a/packages/api/tests/routes.test.ts +++ b/packages/api/tests/routes.test.ts @@ -140,20 +140,17 @@ vi.mock("@dispatch/core", () => ({ } }, TaskList: class MockTaskList { + private tasks: Array<{ id: string; content: string; status: string }> = []; getTasks() { - return []; - } - getTask() { - return undefined; - } - addTask() { - return { id: "task-1", title: "", description: "", status: "pending" }; + return this.tasks.map((t) => ({ ...t })); } - updateTask() { - return undefined; - } - removeTask() { - return false; + setTasks(items: Array<{ content: string; status?: string }>) { + this.tasks = items.map((item, i) => ({ + id: `task-${i + 1}`, + content: item.content, + status: item.status ?? "pending", + })); + return this.getTasks(); } onChange(_cb: unknown) { return () => {}; |
