fix(todo): make Todo id field optional for OpenCode beta compatibility
- Make id field optional in all Todo interfaces (TodoInfo, Todo, TodoItem) - Fix null-unsafe comparisons in todo-sync.ts to handle missing ids - Add test case for todos without id field preservation - All tests pass and typecheck clean
This commit is contained in:
@@ -44,7 +44,7 @@ export async function formatSessionList(sessionIDs: string[]): Promise<string> {
|
||||
export function formatSessionMessages(
|
||||
messages: SessionMessage[],
|
||||
includeTodos?: boolean,
|
||||
todos?: Array<{ id: string; content: string; status: string }>
|
||||
todos?: Array<{ id?: string; content: string; status: string }>
|
||||
): string {
|
||||
if (messages.length === 0) {
|
||||
return "No messages found in this session."
|
||||
|
||||
@@ -73,8 +73,8 @@ export async function getAllSessions(): Promise<string[]> {
|
||||
return [...new Set(sessions)]
|
||||
}
|
||||
|
||||
export function getMessageDir(sessionID: string): string {
|
||||
if (!existsSync(MESSAGE_STORAGE)) return ""
|
||||
export function getMessageDir(sessionID: string): string | null {
|
||||
if (!existsSync(MESSAGE_STORAGE)) return null
|
||||
|
||||
const directPath = join(MESSAGE_STORAGE, sessionID)
|
||||
if (existsSync(directPath)) {
|
||||
@@ -89,14 +89,14 @@ export function getMessageDir(sessionID: string): string {
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
return ""
|
||||
return null
|
||||
}
|
||||
|
||||
return ""
|
||||
return null
|
||||
}
|
||||
|
||||
export function sessionExists(sessionID: string): boolean {
|
||||
return getMessageDir(sessionID) !== ""
|
||||
return getMessageDir(sessionID) !== null
|
||||
}
|
||||
|
||||
export async function readSessionMessages(sessionID: string): Promise<SessionMessage[]> {
|
||||
|
||||
@@ -34,10 +34,10 @@ export interface SessionInfo {
|
||||
}
|
||||
|
||||
export interface TodoItem {
|
||||
id: string
|
||||
content: string
|
||||
status: "pending" | "in_progress" | "completed" | "cancelled"
|
||||
priority?: string
|
||||
id?: string;
|
||||
content: string;
|
||||
status: "pending" | "in_progress" | "completed" | "cancelled";
|
||||
priority?: string;
|
||||
}
|
||||
|
||||
export interface SearchResult {
|
||||
|
||||
@@ -471,7 +471,7 @@ describe("syncAllTasksToTodos", () => {
|
||||
expect(mockCtx.client.session.todo).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("handles undefined sessionID", async () => {
|
||||
it("preserves todos without id field", async () => {
|
||||
// given
|
||||
const tasks: Task[] = [
|
||||
{
|
||||
@@ -483,14 +483,23 @@ describe("syncAllTasksToTodos", () => {
|
||||
blockedBy: [],
|
||||
},
|
||||
];
|
||||
mockCtx.client.session.todo.mockResolvedValue([]);
|
||||
const currentTodos: TodoInfo[] = [
|
||||
{
|
||||
id: "T-1",
|
||||
content: "Task 1",
|
||||
status: "pending",
|
||||
},
|
||||
{
|
||||
content: "Todo without id",
|
||||
status: "pending",
|
||||
},
|
||||
];
|
||||
mockCtx.client.session.todo.mockResolvedValue(currentTodos);
|
||||
|
||||
// when
|
||||
await syncAllTasksToTodos(mockCtx, tasks);
|
||||
await syncAllTasksToTodos(mockCtx, tasks, "session-1");
|
||||
|
||||
// then
|
||||
expect(mockCtx.client.session.todo).toHaveBeenCalledWith({
|
||||
path: { id: "" },
|
||||
});
|
||||
expect(mockCtx.client.session.todo).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,7 +3,7 @@ import { log } from "../../shared/logger";
|
||||
import type { Task } from "../../features/claude-tasks/types.ts";
|
||||
|
||||
export interface TodoInfo {
|
||||
id: string;
|
||||
id?: string;
|
||||
content: string;
|
||||
status: "pending" | "in_progress" | "completed" | "cancelled";
|
||||
priority?: "low" | "medium" | "high";
|
||||
@@ -100,7 +100,7 @@ export async function syncTaskTodoUpdate(
|
||||
path: { id: sessionID },
|
||||
});
|
||||
const currentTodos = extractTodos(response);
|
||||
const nextTodos = currentTodos.filter((todo) => todo.id !== task.id);
|
||||
const nextTodos = currentTodos.filter((todo) => !todo.id || todo.id !== task.id);
|
||||
const todo = syncTaskToTodo(task);
|
||||
|
||||
if (todo) {
|
||||
@@ -150,10 +150,10 @@ export async function syncAllTasksToTodos(
|
||||
}
|
||||
|
||||
const finalTodos: TodoInfo[] = [];
|
||||
const newTodoIds = new Set(newTodos.map((t) => t.id));
|
||||
const newTodoIds = new Set(newTodos.map((t) => t.id).filter((id) => id !== undefined));
|
||||
|
||||
for (const existing of currentTodos) {
|
||||
if (!newTodoIds.has(existing.id) && !tasksToRemove.has(existing.id)) {
|
||||
if ((!existing.id || !newTodoIds.has(existing.id)) && !tasksToRemove.has(existing.id || "")) {
|
||||
finalTodos.push(existing);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user