Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion src/session-runtime/connect-load.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ export type ConnectAndLoadSessionResult = {
loadError?: string;
};

// JSON-RPC codes that indicate the agent does not support session/load.
// -32601 = Method not found, -32602 = Invalid params.
const SESSION_LOAD_UNSUPPORTED_CODES = new Set([-32601, -32602]);

function shouldFallbackToNewSession(error: unknown, record: SessionRecord): boolean {
if (error instanceof TimeoutError || error instanceof InterruptedError) {
return false;
Expand All @@ -45,14 +49,18 @@ function shouldFallbackToNewSession(error: unknown, record: SessionRecord): bool
return true;
}

const acp = extractAcpError(error);
if (acp && SESSION_LOAD_UNSUPPORTED_CODES.has(acp.code)) {
return true;
}

// Some adapters return JSON-RPC internal errors when trying to
// load sessions that have never produced an agent turn yet.
if (!sessionHasAgentMessages(record)) {
if (isAcpQueryClosedBeforeResponseError(error)) {
return true;
}

const acp = extractAcpError(error);
if (acp?.code === -32603) {
return true;
}
Expand Down
108 changes: 108 additions & 0 deletions test/connect-load.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,114 @@ test("connectAndLoadSession falls back to createSession for empty sessions on ad
});
});

test("connectAndLoadSession falls back to session/new on -32602 Invalid params", async () => {
await withTempHome(async (homeDir) => {
const cwd = path.join(homeDir, "workspace");
await fs.mkdir(cwd, { recursive: true });

const record = makeSessionRecord({
acpxRecordId: "invalid-params-record",
acpSessionId: "invalid-params-session",
agentCommand: "agent",
cwd,
messages: [
{
Agent: {
content: [{ Text: "has history" }],
tool_results: {},
},
},
],
});

const client: FakeClient = {
hasReusableSession: () => false,
start: async () => {},
getAgentLifecycleSnapshot: () => ({
running: true,
}),
supportsLoadSession: () => true,
loadSessionWithOptions: async () => {
throw {
error: {
code: -32602,
message: "Invalid params",
},
};
},
createSession: async () => ({
sessionId: "fallback-from-32602",
agentSessionId: "fallback-runtime",
}),
setSessionMode: async () => {},
};

const result = await connectAndLoadSession({
client: client as never,
record,
activeController: ACTIVE_CONTROLLER,
});

assert.equal(result.sessionId, "fallback-from-32602");
assert.equal(result.resumed, false);
assert.equal(record.acpSessionId, "fallback-from-32602");
});
});

test("connectAndLoadSession falls back to session/new on -32601 Method not found", async () => {
await withTempHome(async (homeDir) => {
const cwd = path.join(homeDir, "workspace");
await fs.mkdir(cwd, { recursive: true });

const record = makeSessionRecord({
acpxRecordId: "method-not-found-record",
acpSessionId: "method-not-found-session",
agentCommand: "agent",
cwd,
messages: [
{
Agent: {
content: [{ Text: "has history" }],
tool_results: {},
},
},
],
});

const client: FakeClient = {
hasReusableSession: () => false,
start: async () => {},
getAgentLifecycleSnapshot: () => ({
running: true,
}),
supportsLoadSession: () => true,
loadSessionWithOptions: async () => {
throw {
error: {
code: -32601,
message: "Method not found",
},
};
},
createSession: async () => ({
sessionId: "fallback-from-32601",
agentSessionId: "fallback-runtime",
}),
setSessionMode: async () => {},
};

const result = await connectAndLoadSession({
client: client as never,
record,
activeController: ACTIVE_CONTROLLER,
});

assert.equal(result.sessionId, "fallback-from-32601");
assert.equal(result.resumed, false);
assert.equal(record.acpSessionId, "fallback-from-32601");
});
});

test("connectAndLoadSession rethrows load failures that should not create a new session", async () => {
await withTempHome(async (homeDir) => {
const cwd = path.join(homeDir, "workspace");
Expand Down