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:
+2
-1
@@ -37,7 +37,7 @@ export { resolveModelPipeline } from "./model-resolution-pipeline"
|
||||
export type {
|
||||
ModelResolutionRequest,
|
||||
ModelResolutionProvenance,
|
||||
ModelResolutionResult as ModelResolutionPipelineResult,
|
||||
ModelResolutionPipelineResult,
|
||||
} from "./model-resolution-types"
|
||||
export * from "./model-availability"
|
||||
export * from "./connected-providers-cache"
|
||||
@@ -49,3 +49,4 @@ export * from "./port-utils"
|
||||
export * from "./git-worktree"
|
||||
export * from "./safe-create-hook"
|
||||
export * from "./truncate-description"
|
||||
export * from "./opencode-message-dir"
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"
|
||||
import { existsSync, readdirSync } from "node:fs"
|
||||
import { join } from "node:path"
|
||||
import { getMessageDir } from "./opencode-message-dir"
|
||||
|
||||
// Mock the constants
|
||||
vi.mock("../tools/session-manager/constants", () => ({
|
||||
MESSAGE_STORAGE: "/mock/message/storage",
|
||||
}))
|
||||
|
||||
vi.mock("node:fs", () => ({
|
||||
existsSync: vi.fn(),
|
||||
readdirSync: vi.fn(),
|
||||
}))
|
||||
|
||||
vi.mock("node:path", () => ({
|
||||
join: vi.fn(),
|
||||
}))
|
||||
|
||||
const mockExistsSync = vi.mocked(existsSync)
|
||||
const mockReaddirSync = vi.mocked(readdirSync)
|
||||
const mockJoin = vi.mocked(join)
|
||||
|
||||
describe("getMessageDir", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
mockJoin.mockImplementation((...args) => args.join("/"))
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks()
|
||||
})
|
||||
|
||||
it("returns null when MESSAGE_STORAGE does not exist", () => {
|
||||
// given
|
||||
mockExistsSync.mockReturnValue(false)
|
||||
|
||||
// when
|
||||
const result = getMessageDir("session123")
|
||||
|
||||
// then
|
||||
expect(result).toBe(null)
|
||||
expect(mockExistsSync).toHaveBeenCalledWith("/mock/message/storage")
|
||||
})
|
||||
|
||||
it("returns direct path when session exists directly", () => {
|
||||
// given
|
||||
mockExistsSync.mockImplementation((path) => path === "/mock/message/storage" || path === "/mock/message/storage/session123")
|
||||
|
||||
// when
|
||||
const result = getMessageDir("session123")
|
||||
|
||||
// then
|
||||
expect(result).toBe("/mock/message/storage/session123")
|
||||
expect(mockExistsSync).toHaveBeenCalledWith("/mock/message/storage")
|
||||
expect(mockExistsSync).toHaveBeenCalledWith("/mock/message/storage/session123")
|
||||
})
|
||||
|
||||
it("returns subdirectory path when session exists in subdirectory", () => {
|
||||
// given
|
||||
mockExistsSync.mockImplementation((path) => {
|
||||
return path === "/mock/message/storage" || path === "/mock/message/storage/subdir/session123"
|
||||
})
|
||||
mockReaddirSync.mockReturnValue(["subdir"])
|
||||
|
||||
// when
|
||||
const result = getMessageDir("session123")
|
||||
|
||||
// then
|
||||
expect(result).toBe("/mock/message/storage/subdir/session123")
|
||||
expect(mockReaddirSync).toHaveBeenCalledWith("/mock/message/storage")
|
||||
})
|
||||
|
||||
it("returns null when session not found anywhere", () => {
|
||||
// given
|
||||
mockExistsSync.mockImplementation((path) => path === "/mock/message/storage")
|
||||
mockReaddirSync.mockReturnValue(["subdir1", "subdir2"])
|
||||
|
||||
// when
|
||||
const result = getMessageDir("session123")
|
||||
|
||||
// then
|
||||
expect(result).toBe(null)
|
||||
})
|
||||
|
||||
it("returns null when readdirSync throws", () => {
|
||||
// given
|
||||
mockExistsSync.mockImplementation((path) => path === "/mock/message/storage")
|
||||
mockReaddirSync.mockImplementation(() => {
|
||||
throw new Error("Permission denied")
|
||||
})
|
||||
|
||||
// when
|
||||
const result = getMessageDir("session123")
|
||||
|
||||
// then
|
||||
expect(result).toBe(null)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,25 @@
|
||||
import { existsSync, readdirSync } from "node:fs"
|
||||
import { join } from "node:path"
|
||||
import { MESSAGE_STORAGE } from "../tools/session-manager/constants"
|
||||
|
||||
export function getMessageDir(sessionID: string): string | null {
|
||||
if (!existsSync(MESSAGE_STORAGE)) return null
|
||||
|
||||
const directPath = join(MESSAGE_STORAGE, sessionID)
|
||||
if (existsSync(directPath)) {
|
||||
return directPath
|
||||
}
|
||||
|
||||
try {
|
||||
for (const dir of readdirSync(MESSAGE_STORAGE)) {
|
||||
const sessionPath = join(MESSAGE_STORAGE, dir, sessionID)
|
||||
if (existsSync(sessionPath)) {
|
||||
return sessionPath
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
@@ -3,20 +3,7 @@ import * as os from "node:os"
|
||||
import { existsSync, readdirSync } from "node:fs"
|
||||
import { join } from "node:path"
|
||||
import { findNearestMessageWithFields, MESSAGE_STORAGE } from "../features/hook-message-injector"
|
||||
|
||||
export function getMessageDir(sessionID: string): string | null {
|
||||
if (!existsSync(MESSAGE_STORAGE)) return null
|
||||
|
||||
const directPath = join(MESSAGE_STORAGE, sessionID)
|
||||
if (existsSync(directPath)) return directPath
|
||||
|
||||
for (const dir of readdirSync(MESSAGE_STORAGE)) {
|
||||
const sessionPath = join(MESSAGE_STORAGE, dir, sessionID)
|
||||
if (existsSync(sessionPath)) return sessionPath
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
import { getMessageDir } from "./opencode-message-dir"
|
||||
|
||||
export function isCallerOrchestrator(sessionID?: string): boolean {
|
||||
if (!sessionID) return false
|
||||
|
||||
Reference in New Issue
Block a user