From 82c988452f4687f8272b6481967c13d2ffd3f9f2 Mon Sep 17 00:00:00 2001 From: Daniil Shushakov <4shushakov@gmail.com> Date: Wed, 22 Jul 2026 14:03:16 +0300 Subject: [PATCH 1/4] fix: filter session list before pagination --- src/CodexAcpClient.ts | 1 + .../CodexACPAgent/list-sessions.test.ts | 71 +++++++++++++++++++ 2 files changed, 72 insertions(+) diff --git a/src/CodexAcpClient.ts b/src/CodexAcpClient.ts index 0c7af207..b475b061 100644 --- a/src/CodexAcpClient.ts +++ b/src/CodexAcpClient.ts @@ -806,6 +806,7 @@ export class CodexAcpClient { cursor: request.cursor ?? null, modelProviders: modelProviders, sourceKinds: sourceKinds, + cwd: requestedCwd, }); const mapThreadToSession = (thread: Thread) => ({ diff --git a/src/__tests__/CodexACPAgent/list-sessions.test.ts b/src/__tests__/CodexACPAgent/list-sessions.test.ts index 845ee0fd..4a629adb 100644 --- a/src/__tests__/CodexACPAgent/list-sessions.test.ts +++ b/src/__tests__/CodexACPAgent/list-sessions.test.ts @@ -4,6 +4,51 @@ import { createCodexMockTestFixture } from "../acp-test-utils"; import type { Thread } from "../../app-server/v2"; describe("CodexACPAgent - list sessions", () => { + it.each([ + { + serverCwdBehavior: "string-only", + acceptsRequestedCwd: (cwd: string | string[] | null | undefined) => cwd === "/repo/project", + }, + { + serverCwdBehavior: "string-or-array", + acceptsRequestedCwd: (cwd: string | string[] | null | undefined) => { + const cwds = Array.isArray(cwd) ? cwd : cwd ? [cwd] : []; + return cwds.includes("/repo/project"); + }, + }, + ])("forwards cwd before $serverCwdBehavior thread pagination", async ({acceptsRequestedCwd}) => { + const fixture = createCodexMockTestFixture(); + const codexAcpAgent = fixture.getCodexAcpAgent(); + const codexAcpClient = fixture.getCodexAcpClient(); + const codexAppServerClient = fixture.getCodexAppServerClient(); + + codexAcpClient.authRequired = vi.fn().mockResolvedValue(false); + + const matchingThread = createThread("sess-project", "/repo/project"); + const unrelatedThread = createThread("sess-other", "/repo/other"); + codexAppServerClient.threadList = vi.fn().mockImplementation(async (params) => { + if (acceptsRequestedCwd(params.cwd)) { + return {data: [matchingThread], nextCursor: null}; + } + return {data: [unrelatedThread], nextCursor: "unrelated-global-cursor"}; + }); + + const response = await codexAcpAgent.listSessions({ + cwd: "/repo/project", + cursor: null, + }); + + expect(response).toEqual({ + sessions: [{ + sessionId: "sess-project", + cwd: "/repo/project", + title: "Session sess-project", + updatedAt: "1970-01-01T00:03:20.000Z", + }], + nextCursor: null, + }); + }); + it("should list sessions filtered by cwd", async () => { const fixture = createCodexMockTestFixture(); const codexAcpAgent = fixture.getCodexAcpAgent(); @@ -215,3 +260,29 @@ describe("CodexACPAgent - list sessions", () => { expect(response.sessions[0]?.additionalDirectories).toEqual(["/repo/extra"]); }); }); + +function createThread(id: string, cwd: string): Thread { + return { + id, + sessionId: id, + parentThreadId: null, + threadSource: null, + forkedFromId: null, + preview: `Session ${id}`, + ephemeral: false, + modelProvider: "openai", + createdAt: 100, + updatedAt: 200, + recencyAt: null, + status: {type: "idle"}, + path: null, + cwd, + cliVersion: "0.0.0", + source: "cli", + agentNickname: null, + agentRole: null, + gitInfo: null, + name: null, + turns: [], + }; +} From 002b0e455036a99dfa7437cf0f398c92c0168315 Mon Sep 17 00:00:00 2001 From: Daniil Shushakov <4shushakov@gmail.com> Date: Wed, 22 Jul 2026 14:12:53 +0300 Subject: [PATCH 2/4] fix: match CLI session ordering --- src/CodexAcpClient.ts | 2 + .../CodexACPAgent/list-sessions.test.ts | 43 +++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/src/CodexAcpClient.ts b/src/CodexAcpClient.ts index b475b061..bfe19769 100644 --- a/src/CodexAcpClient.ts +++ b/src/CodexAcpClient.ts @@ -807,6 +807,8 @@ export class CodexAcpClient { modelProviders: modelProviders, sourceKinds: sourceKinds, cwd: requestedCwd, + sortKey: "updated_at", + sortDirection: "desc", }); const mapThreadToSession = (thread: Thread) => ({ diff --git a/src/__tests__/CodexACPAgent/list-sessions.test.ts b/src/__tests__/CodexACPAgent/list-sessions.test.ts index 4a629adb..2ae9286e 100644 --- a/src/__tests__/CodexACPAgent/list-sessions.test.ts +++ b/src/__tests__/CodexACPAgent/list-sessions.test.ts @@ -49,6 +49,49 @@ describe("CodexACPAgent - list sessions", () => { }); }); + it("forwards cwd, cursor, and CLI ordering across pagination", async () => { + const fixture = createCodexMockTestFixture(); + const codexAcpAgent = fixture.getCodexAcpAgent(); + const codexAcpClient = fixture.getCodexAcpClient(); + const codexAppServerClient = fixture.getCodexAppServerClient(); + + codexAcpClient.authRequired = vi.fn().mockResolvedValue(false); + codexAppServerClient.threadList = vi.fn() + .mockResolvedValueOnce({ + data: [createThread("sess-newer", "/repo/project")], + nextCursor: "project-page-2", + }) + .mockResolvedValueOnce({ + data: [createThread("sess-older", "/repo/project")], + nextCursor: null, + }); + + const firstPage = await codexAcpAgent.listSessions({ + cwd: "/repo/project", + cursor: null, + }); + await codexAcpAgent.listSessions({ + cwd: "/repo/project", + cursor: firstPage.nextCursor ?? null, + }); + + expect(codexAppServerClient.threadList).toHaveBeenNthCalledWith(1, expect.objectContaining({ + cwd: "/repo/project", + cursor: null, + sortKey: "updated_at", + sortDirection: "desc", + })); + expect(codexAppServerClient.threadList).toHaveBeenNthCalledWith(2, expect.objectContaining({ + cwd: "/repo/project", + cursor: "project-page-2", + sortKey: "updated_at", + sortDirection: "desc", + })); + for (const [params] of vi.mocked(codexAppServerClient.threadList).mock.calls) { + expect(params).not.toHaveProperty("limit"); + } + }); + it("should list sessions filtered by cwd", async () => { const fixture = createCodexMockTestFixture(); const codexAcpAgent = fixture.getCodexAcpAgent(); From 48bee78a73285ad84732e15dac3d45bfe3b9cd3e Mon Sep 17 00:00:00 2001 From: Daniil Shushakov <4shushakov@gmail.com> Date: Wed, 22 Jul 2026 14:14:39 +0300 Subject: [PATCH 3/4] refactor: rely on App Server session filtering --- src/CodexAcpClient.ts | 22 +------------------ .../CodexACPAgent/list-sessions.test.ts | 6 ++--- 2 files changed, 4 insertions(+), 24 deletions(-) diff --git a/src/CodexAcpClient.ts b/src/CodexAcpClient.ts index bfe19769..37f2c9fa 100644 --- a/src/CodexAcpClient.ts +++ b/src/CodexAcpClient.ts @@ -791,14 +791,6 @@ export class CodexAcpClient { "unknown", ]; const requestedCwd = request.cwd?.trim() ?? null; - const filterByCwd = (thread: Thread): boolean => { - if (!requestedCwd) return true; - if (path.isAbsolute(requestedCwd)) { - return thread.cwd === requestedCwd; - } - const requestedBase = path.basename(requestedCwd); - return path.basename(thread.cwd) === requestedBase; - }; const preferredProvider = this.getModelProvider(); const modelProviders = preferredProvider ? [preferredProvider] : []; @@ -823,20 +815,8 @@ export class CodexAcpClient { logger.log("Session list diagnostics", diagnostics); } - let sessions = listResponse.data.map(mapThreadToSession); - if (requestedCwd) { - const filtered = listResponse.data - .filter(filterByCwd) - .map(mapThreadToSession); - if (filtered.length > 0 || path.isAbsolute(requestedCwd)) { - sessions = filtered; - } else { - logger.log("Ignoring non-absolute cwd filter for session/list", {cwd: requestedCwd}); - } - } - return { - sessions, + sessions: listResponse.data.map(mapThreadToSession), nextCursor: listResponse.nextCursor ?? null, }; } diff --git a/src/__tests__/CodexACPAgent/list-sessions.test.ts b/src/__tests__/CodexACPAgent/list-sessions.test.ts index 2ae9286e..e7203100 100644 --- a/src/__tests__/CodexACPAgent/list-sessions.test.ts +++ b/src/__tests__/CodexACPAgent/list-sessions.test.ts @@ -147,10 +147,10 @@ describe("CodexACPAgent - list sessions", () => { turns: [], }; - codexAppServerClient.threadList = vi.fn().mockResolvedValue({ - data: [threadA, threadB], + codexAppServerClient.threadList = vi.fn().mockImplementation(async (params) => ({ + data: [threadA, threadB].filter(thread => thread.cwd === params.cwd), nextCursor: "next-cursor", - }); + })); codexAppServerClient.threadLoadedList = vi.fn().mockResolvedValue({ data: [], nextCursor: null, From 4f1cbe3aaf49fae3608315b25d80dc71774868af Mon Sep 17 00:00:00 2001 From: Daniil Shushakov <4shushakov@gmail.com> Date: Wed, 22 Jul 2026 14:29:51 +0300 Subject: [PATCH 4/4] fix: skip diagnostics for filtered sessions --- src/CodexAcpClient.ts | 2 +- .../CodexACPAgent/list-sessions.test.ts | 21 +++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/CodexAcpClient.ts b/src/CodexAcpClient.ts index 37f2c9fa..f7355219 100644 --- a/src/CodexAcpClient.ts +++ b/src/CodexAcpClient.ts @@ -810,7 +810,7 @@ export class CodexAcpClient { updatedAt: new Date(thread.updatedAt * 1000).toISOString(), }); - if (listResponse.data.length === 0) { + if (!requestedCwd && listResponse.data.length === 0) { const diagnostics = await this.runSessionListDiagnostics(); logger.log("Session list diagnostics", diagnostics); } diff --git a/src/__tests__/CodexACPAgent/list-sessions.test.ts b/src/__tests__/CodexACPAgent/list-sessions.test.ts index e7203100..63afd751 100644 --- a/src/__tests__/CodexACPAgent/list-sessions.test.ts +++ b/src/__tests__/CodexACPAgent/list-sessions.test.ts @@ -92,6 +92,27 @@ describe("CodexACPAgent - list sessions", () => { } }); + it("returns an empty filtered page without global diagnostics", async () => { + const fixture = createCodexMockTestFixture(); + const codexAcpAgent = fixture.getCodexAcpAgent(); + const codexAcpClient = fixture.getCodexAcpClient(); + const codexAppServerClient = fixture.getCodexAppServerClient(); + + codexAcpClient.authRequired = vi.fn().mockResolvedValue(false); + codexAppServerClient.threadList = vi.fn().mockResolvedValue({ + data: [], + nextCursor: null, + }); + + const response = await codexAcpAgent.listSessions({ + cwd: "/project/new-worktree", + cursor: null, + }); + + expect(response).toEqual({sessions: [], nextCursor: null}); + expect(codexAppServerClient.threadList).toHaveBeenCalledOnce(); + }); + it("should list sessions filtered by cwd", async () => { const fixture = createCodexMockTestFixture(); const codexAcpAgent = fixture.getCodexAcpAgent();