| import { describe, expect, it } from "vitest"; |
| import { sessionCodec as claudeSessionCodec } from "@penclipai/adapter-claude-local/server"; |
| import { |
| sessionCodec as codeBuddySessionCodec, |
| isCodeBuddyUnknownSessionError, |
| } from "@penclipai/adapter-codebuddy-local/server"; |
| import { sessionCodec as codexSessionCodec, isCodexUnknownSessionError } from "@penclipai/adapter-codex-local/server"; |
| import { |
| sessionCodec as cursorSessionCodec, |
| isCursorUnknownSessionError, |
| } from "@penclipai/adapter-cursor-local/server"; |
| import { |
| sessionCodec as geminiSessionCodec, |
| isGeminiUnknownSessionError, |
| } from "@penclipai/adapter-gemini-local/server"; |
| import { |
| sessionCodec as opencodeSessionCodec, |
| isOpenCodeUnknownSessionError, |
| } from "@penclipai/adapter-opencode-local/server"; |
| import { |
| sessionCodec as qwenSessionCodec, |
| isQwenUnknownSessionError, |
| } from "@penclipai/adapter-qwen-local/server"; |
|
|
| describe("adapter session codecs", () => { |
| it("normalizes claude session params with cwd", () => { |
| const parsed = claudeSessionCodec.deserialize({ |
| session_id: "claude-session-1", |
| folder: "/tmp/workspace", |
| prompt_bundle_key: "bundle-1", |
| }); |
| expect(parsed).toEqual({ |
| sessionId: "claude-session-1", |
| cwd: "/tmp/workspace", |
| promptBundleKey: "bundle-1", |
| }); |
|
|
| const serialized = claudeSessionCodec.serialize(parsed); |
| expect(serialized).toEqual({ |
| sessionId: "claude-session-1", |
| cwd: "/tmp/workspace", |
| promptBundleKey: "bundle-1", |
| }); |
| expect(claudeSessionCodec.getDisplayId?.(serialized ?? null)).toBe("claude-session-1"); |
| }); |
|
|
| it("normalizes codex session params with cwd", () => { |
| const parsed = codexSessionCodec.deserialize({ |
| sessionId: "codex-session-1", |
| cwd: "/tmp/codex", |
| }); |
| expect(parsed).toEqual({ |
| sessionId: "codex-session-1", |
| cwd: "/tmp/codex", |
| }); |
|
|
| const serialized = codexSessionCodec.serialize(parsed); |
| expect(serialized).toEqual({ |
| sessionId: "codex-session-1", |
| cwd: "/tmp/codex", |
| }); |
| expect(codexSessionCodec.getDisplayId?.(serialized ?? null)).toBe("codex-session-1"); |
| }); |
|
|
| it("normalizes codebuddy session params with cwd", () => { |
| const parsed = codeBuddySessionCodec.deserialize({ |
| session_id: "codebuddy-session-1", |
| cwd: "/tmp/codebuddy", |
| }); |
| expect(parsed).toEqual({ |
| sessionId: "codebuddy-session-1", |
| cwd: "/tmp/codebuddy", |
| }); |
|
|
| const serialized = codeBuddySessionCodec.serialize(parsed); |
| expect(serialized).toEqual({ |
| sessionId: "codebuddy-session-1", |
| cwd: "/tmp/codebuddy", |
| }); |
| expect(codeBuddySessionCodec.getDisplayId?.(serialized ?? null)).toBe("codebuddy-session-1"); |
| }); |
|
|
| it("normalizes opencode session params with cwd", () => { |
| const parsed = opencodeSessionCodec.deserialize({ |
| sessionID: "opencode-session-1", |
| cwd: "/tmp/opencode", |
| }); |
| expect(parsed).toEqual({ |
| sessionId: "opencode-session-1", |
| cwd: "/tmp/opencode", |
| }); |
|
|
| const serialized = opencodeSessionCodec.serialize(parsed); |
| expect(serialized).toEqual({ |
| sessionId: "opencode-session-1", |
| cwd: "/tmp/opencode", |
| }); |
| expect(opencodeSessionCodec.getDisplayId?.(serialized ?? null)).toBe("opencode-session-1"); |
| }); |
|
|
| it("normalizes cursor session params with cwd", () => { |
| const parsed = cursorSessionCodec.deserialize({ |
| session_id: "cursor-session-1", |
| cwd: "/tmp/cursor", |
| }); |
| expect(parsed).toEqual({ |
| sessionId: "cursor-session-1", |
| cwd: "/tmp/cursor", |
| }); |
|
|
| const serialized = cursorSessionCodec.serialize(parsed); |
| expect(serialized).toEqual({ |
| sessionId: "cursor-session-1", |
| cwd: "/tmp/cursor", |
| }); |
| expect(cursorSessionCodec.getDisplayId?.(serialized ?? null)).toBe("cursor-session-1"); |
| }); |
|
|
| it("normalizes gemini session params with cwd", () => { |
| const parsed = geminiSessionCodec.deserialize({ |
| session_id: "gemini-session-1", |
| cwd: "/tmp/gemini", |
| }); |
| expect(parsed).toEqual({ |
| sessionId: "gemini-session-1", |
| cwd: "/tmp/gemini", |
| }); |
|
|
| const serialized = geminiSessionCodec.serialize(parsed); |
| expect(serialized).toEqual({ |
| sessionId: "gemini-session-1", |
| cwd: "/tmp/gemini", |
| }); |
| expect(geminiSessionCodec.getDisplayId?.(serialized ?? null)).toBe("gemini-session-1"); |
| }); |
|
|
| it("normalizes qwen session params with cwd", () => { |
| const parsed = qwenSessionCodec.deserialize({ |
| session_id: "qwen-session-1", |
| cwd: "/tmp/qwen", |
| }); |
| expect(parsed).toEqual({ |
| sessionId: "qwen-session-1", |
| cwd: "/tmp/qwen", |
| }); |
|
|
| const serialized = qwenSessionCodec.serialize(parsed); |
| expect(serialized).toEqual({ |
| sessionId: "qwen-session-1", |
| cwd: "/tmp/qwen", |
| }); |
| expect(qwenSessionCodec.getDisplayId?.(serialized ?? null)).toBe("qwen-session-1"); |
| }); |
| }); |
|
|
| describe("codebuddy resume recovery detection", () => { |
| it("detects unknown session errors from codebuddy output", () => { |
| expect( |
| isCodeBuddyUnknownSessionError( |
| "{\"type\":\"error\",\"message\":\"No conversation found with session ID: stale-session\"}", |
| "", |
| ), |
| ).toBe(true); |
| expect( |
| isCodeBuddyUnknownSessionError( |
| "", |
| "resume session not found", |
| ), |
| ).toBe(true); |
| expect( |
| isCodeBuddyUnknownSessionError( |
| "{\"type\":\"result\",\"subtype\":\"success\",\"result\":\"ok\"}", |
| "", |
| ), |
| ).toBe(false); |
| }); |
| }); |
|
|
| describe("codebuddy resume recovery detection", () => { |
| it("detects unknown session errors from codebuddy output", () => { |
| expect( |
| isCodeBuddyUnknownSessionError( |
| "{\"type\":\"error\",\"message\":\"No conversation found with session ID: stale-session\"}", |
| "", |
| ), |
| ).toBe(true); |
| expect( |
| isCodeBuddyUnknownSessionError( |
| "", |
| "resume session not found", |
| ), |
| ).toBe(true); |
| expect( |
| isCodeBuddyUnknownSessionError( |
| "{\"type\":\"result\",\"subtype\":\"success\",\"result\":\"ok\"}", |
| "", |
| ), |
| ).toBe(false); |
| }); |
| }); |
|
|
| describe("codex resume recovery detection", () => { |
| it("detects unknown session errors from codex output", () => { |
| expect( |
| isCodexUnknownSessionError( |
| '{"type":"error","message":"Unknown session id abc"}', |
| "", |
| ), |
| ).toBe(true); |
| expect( |
| isCodexUnknownSessionError( |
| "", |
| "thread 123 not found", |
| ), |
| ).toBe(true); |
| expect( |
| isCodexUnknownSessionError( |
| '{"type":"result","ok":true}', |
| "", |
| ), |
| ).toBe(false); |
| }); |
| }); |
|
|
| describe("opencode resume recovery detection", () => { |
| it("detects unknown session errors from opencode output", () => { |
| expect( |
| isOpenCodeUnknownSessionError( |
| "", |
| "NotFoundError: Resource not found: /Users/test/.local/share/opencode/storage/session/proj/ses_missing.json", |
| ), |
| ).toBe(true); |
| expect( |
| isOpenCodeUnknownSessionError( |
| "{\"type\":\"step_finish\",\"part\":{\"reason\":\"stop\"}}", |
| "", |
| ), |
| ).toBe(false); |
| }); |
| }); |
|
|
| describe("cursor resume recovery detection", () => { |
| it("detects unknown session errors from cursor output", () => { |
| expect( |
| isCursorUnknownSessionError( |
| "", |
| "Error: unknown session id abc", |
| ), |
| ).toBe(true); |
| expect( |
| isCursorUnknownSessionError( |
| "", |
| "chat abc not found", |
| ), |
| ).toBe(true); |
| expect( |
| isCursorUnknownSessionError( |
| "{\"type\":\"result\",\"subtype\":\"success\"}", |
| "", |
| ), |
| ).toBe(false); |
| }); |
| }); |
|
|
| describe("gemini resume recovery detection", () => { |
| it("detects unknown session errors from gemini output", () => { |
| expect( |
| isGeminiUnknownSessionError( |
| "", |
| "unknown session id abc", |
| ), |
| ).toBe(true); |
| expect( |
| isGeminiUnknownSessionError( |
| "", |
| "checkpoint latest not found", |
| ), |
| ).toBe(true); |
| expect( |
| isGeminiUnknownSessionError( |
| "{\"type\":\"result\",\"subtype\":\"success\"}", |
| "", |
| ), |
| ).toBe(false); |
| }); |
| }); |
|
|
| describe("qwen resume recovery detection", () => { |
| it("detects unknown session errors from qwen output", () => { |
| expect( |
| isQwenUnknownSessionError( |
| "", |
| "No saved session found with ID stale-session", |
| ), |
| ).toBe(true); |
| expect( |
| isQwenUnknownSessionError( |
| "", |
| "cannot resume previous run", |
| ), |
| ).toBe(true); |
| expect( |
| isQwenUnknownSessionError( |
| "{\"type\":\"result\",\"subtype\":\"success\"}", |
| "", |
| ), |
| ).toBe(false); |
| }); |
| }); |
|
|