Add adapter session codecs with cwd-aware resume and unknown-session retry
Introduce AdapterSessionCodec interface for structured session serialization, deserialization, and display ID extraction. Implement codecs for claude_local and codex_local adapters with cwd validation — sessions saved for a different working directory are not resumed. Both adapters now return sessionParams and sessionDisplayId alongside legacy sessionId. Add isCodexUnknownSessionError detection and automatic retry with fresh session for codex_local (matching existing claude_local behavior). Inject approval context env vars (PAPERCLIP_APPROVAL_ID, PAPERCLIP_APPROVAL_STATUS, PAPERCLIP_LINKED_ISSUE_IDS) into adapter environments. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
64
server/src/__tests__/adapter-session-codecs.test.ts
Normal file
64
server/src/__tests__/adapter-session-codecs.test.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { sessionCodec as claudeSessionCodec } from "@paperclip/adapter-claude-local/server";
|
||||
import { sessionCodec as codexSessionCodec, isCodexUnknownSessionError } from "@paperclip/adapter-codex-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",
|
||||
});
|
||||
expect(parsed).toEqual({
|
||||
sessionId: "claude-session-1",
|
||||
cwd: "/tmp/workspace",
|
||||
});
|
||||
|
||||
const serialized = claudeSessionCodec.serialize(parsed);
|
||||
expect(serialized).toEqual({
|
||||
sessionId: "claude-session-1",
|
||||
cwd: "/tmp/workspace",
|
||||
});
|
||||
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");
|
||||
});
|
||||
});
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
||||
@@ -4,6 +4,7 @@ export type {
|
||||
AdapterExecutionContext,
|
||||
AdapterExecutionResult,
|
||||
AdapterInvocationMeta,
|
||||
AdapterSessionCodec,
|
||||
UsageSummary,
|
||||
AdapterAgent,
|
||||
AdapterRuntime,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { ServerAdapterModule } from "./types.js";
|
||||
import { execute as claudeExecute } from "@paperclip/adapter-claude-local/server";
|
||||
import { execute as claudeExecute, sessionCodec as claudeSessionCodec } from "@paperclip/adapter-claude-local/server";
|
||||
import { agentConfigurationDoc as claudeAgentConfigurationDoc, models as claudeModels } from "@paperclip/adapter-claude-local";
|
||||
import { execute as codexExecute } from "@paperclip/adapter-codex-local/server";
|
||||
import { execute as codexExecute, sessionCodec as codexSessionCodec } from "@paperclip/adapter-codex-local/server";
|
||||
import { agentConfigurationDoc as codexAgentConfigurationDoc, models as codexModels } from "@paperclip/adapter-codex-local";
|
||||
import { processAdapter } from "./process/index.js";
|
||||
import { httpAdapter } from "./http/index.js";
|
||||
@@ -9,6 +9,7 @@ import { httpAdapter } from "./http/index.js";
|
||||
const claudeLocalAdapter: ServerAdapterModule = {
|
||||
type: "claude_local",
|
||||
execute: claudeExecute,
|
||||
sessionCodec: claudeSessionCodec,
|
||||
models: claudeModels,
|
||||
supportsLocalAgentJwt: true,
|
||||
agentConfigurationDoc: claudeAgentConfigurationDoc,
|
||||
@@ -17,6 +18,7 @@ const claudeLocalAdapter: ServerAdapterModule = {
|
||||
const codexLocalAdapter: ServerAdapterModule = {
|
||||
type: "codex_local",
|
||||
execute: codexExecute,
|
||||
sessionCodec: codexSessionCodec,
|
||||
models: codexModels,
|
||||
supportsLocalAgentJwt: true,
|
||||
agentConfigurationDoc: codexAgentConfigurationDoc,
|
||||
|
||||
@@ -8,5 +8,6 @@ export type {
|
||||
AdapterExecutionResult,
|
||||
AdapterInvocationMeta,
|
||||
AdapterExecutionContext,
|
||||
AdapterSessionCodec,
|
||||
ServerAdapterModule,
|
||||
} from "@paperclip/adapter-utils";
|
||||
|
||||
Reference in New Issue
Block a user