summaryrefslogtreecommitdiffhomepage
path: root/packages/api/tests
diff options
context:
space:
mode:
authorAdam Malczewski <[email protected]>2026-06-02 14:49:49 +0900
committerAdam Malczewski <[email protected]>2026-06-02 14:49:49 +0900
commitecb001ec7a2e573d8dedf5064e860e5a3e7788fd (patch)
tree531371ba6f449a144b5bb45a3d226b025983bd4b /packages/api/tests
parent7c527b4d8a72159954405e720d5bf776802dc0ff (diff)
downloaddispatch-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.ts61
-rw-r--r--packages/api/tests/routes.test.ts21
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 () => {};