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:
Forgotten
2026-02-19 14:01:58 -06:00
parent 4e3da49116
commit d56618e9fe
11 changed files with 300 additions and 65 deletions

View 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);
});
});

View File

@@ -4,6 +4,7 @@ export type {
AdapterExecutionContext,
AdapterExecutionResult,
AdapterInvocationMeta,
AdapterSessionCodec,
UsageSummary,
AdapterAgent,
AdapterRuntime,

View File

@@ -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,

View File

@@ -8,5 +8,6 @@ export type {
AdapterExecutionResult,
AdapterInvocationMeta,
AdapterExecutionContext,
AdapterSessionCodec,
ServerAdapterModule,
} from "@paperclip/adapter-utils";