Speed up session import

This commit is contained in:
Mohamed Boudra
2026-06-03 23:09:45 +07:00
parent 4c730611e6
commit eba8de6ab5
4 changed files with 62 additions and 54 deletions
@@ -212,7 +212,7 @@ test("listImportableProviderSessions filters, sorts, limits, and projects import
});
expect(listImportablePersistedAgents).toHaveBeenCalledWith({
limit: 200,
limit: 2,
providerFilter: new Set(["codex"]),
cwd,
});
@@ -109,7 +109,7 @@ export async function listImportableProviderSessions(
const importedHandles = await collectImportedProviderSessionHandles(agentManager, agentStorage);
const descriptors = await agentManager.listImportablePersistedAgents({
limit: 200,
limit,
providerFilter,
cwd: request.cwd,
});
@@ -2638,12 +2638,13 @@ describe("Codex app-server provider", () => {
});
describe("Codex persisted sessions", () => {
test("listPersistedAgents returns only sessions whose cwd matches the requested cwd", async () => {
test("listPersistedAgents uses thread list metadata without hydrating thread history", async () => {
const allThreads = [
{
id: "thread-a1",
cwd: "/workspace/project-a",
preview: "First A session",
name: "Named first A session",
createdAt: 1000,
updatedAt: 2000,
},
@@ -2662,11 +2663,12 @@ describe("Codex persisted sessions", () => {
updatedAt: 4000,
},
];
const calls: Array<{ method: string; params?: unknown }> = [];
const fakeClient = {
request: async (method: string) => {
request: async (method: string, params?: unknown) => {
calls.push({ method, params });
if (method === "thread/list") return { data: allThreads };
if (method === "thread/read") return { thread: { turns: [] } };
return {};
},
notify: () => {},
@@ -2693,5 +2695,16 @@ describe("Codex persisted sessions", () => {
expect(descriptors.map((d) => d.sessionId).sort()).toEqual(["thread-a1", "thread-a2"]);
expect(descriptors.every((d) => d.cwd === "/workspace/project-a")).toBe(true);
expect(descriptors[0]).toEqual(
expect.objectContaining({
sessionId: "thread-a1",
title: "Named first A session",
timeline: [{ type: "user_message", text: "First A session" }],
}),
);
expect(calls).toEqual([
{ method: "initialize", params: expect.any(Object) },
{ method: "thread/list", params: { limit: 50, cwd: "/workspace/project-a" } },
]);
});
});
@@ -831,6 +831,11 @@ function filterCodexThreadsByCwd(
return threads.filter((thread) => typeof thread.cwd === "string" && matchesCwd(thread.cwd));
}
function buildCodexThreadListTimeline(thread: Record<string, unknown>): AgentTimelineItem[] {
const preview = typeof thread.preview === "string" ? thread.preview.trim() : "";
return preview ? [{ type: "user_message", text: preview }] : [];
}
export function toAgentUsage(tokenUsage: unknown): AgentUsage | undefined {
const usage = toObjectRecord(tokenUsage);
if (!usage) return undefined;
@@ -5486,58 +5491,48 @@ export class CodexAppServerAgentClient implements AgentClient {
client.notify("initialized", {});
const limit = options?.limit ?? 20;
// thread/list returns the cheap `cwd` field. When the caller supplied
// a cwd hint we filter here so the per-thread `thread/read includeTurns`
// hydration below only runs for matching threads. Fetch a wider window
// when filtering since most threads will be from other cwds.
// thread/list returns the cheap `cwd` field. Fetch a wider window when
// filtering since most threads will be from other cwds, then keep the
// local realpath-aware filter for symlink-equivalent workspace paths.
const listLimit = options?.cwd ? Math.max(limit, 50) : limit;
const response = toObjectRecord(await client.request("thread/list", { limit: listLimit }));
const allThreads = Array.isArray(response?.data) ? response.data.filter(isRecord) : [];
const threads = filterCodexThreadsByCwd(allThreads, options?.cwd);
const descriptors: PersistedAgentDescriptor[] = await Promise.all(
threads.slice(0, limit).map(async (thread) => {
const threadId = typeof thread.id === "string" ? thread.id : "";
const cwd = typeof thread.cwd === "string" ? thread.cwd : process.cwd();
const title = typeof thread.preview === "string" ? thread.preview : null;
let timeline: PersistedTimelineEntry[] = [];
try {
timeline = await loadCodexThreadHistoryTimeline({
threadId,
cwd,
requestThread: (threadIdToRead) => {
return readCodexThread(client, threadIdToRead);
},
});
} catch {
timeline = [];
}
return {
provider: CODEX_PROVIDER,
sessionId: threadId,
cwd,
title,
lastActivityAt: new Date(
((typeof thread.updatedAt === "number" ? thread.updatedAt : undefined) ??
(typeof thread.createdAt === "number" ? thread.createdAt : undefined) ??
0) * 1000,
),
persistence: {
provider: CODEX_PROVIDER,
sessionId: threadId,
nativeHandle: threadId,
metadata: {
provider: CODEX_PROVIDER,
cwd,
title,
threadId,
},
},
timeline: timeline.map((entry) => entry.item),
};
const response = toObjectRecord(
await client.request("thread/list", {
limit: listLimit,
...(options?.cwd ? { cwd: options.cwd } : {}),
}),
);
const allThreads = Array.isArray(response?.data) ? response.data.filter(isRecord) : [];
const threads = filterCodexThreadsByCwd(allThreads, options?.cwd);
const descriptors: PersistedAgentDescriptor[] = threads.slice(0, limit).map((thread) => {
const threadId = typeof thread.id === "string" ? thread.id : "";
const cwd = typeof thread.cwd === "string" ? thread.cwd : process.cwd();
const preview = typeof thread.preview === "string" ? thread.preview : null;
const title = typeof thread.name === "string" && thread.name.trim() ? thread.name : preview;
return {
provider: CODEX_PROVIDER,
sessionId: threadId,
cwd,
title,
lastActivityAt: new Date(
((typeof thread.updatedAt === "number" ? thread.updatedAt : undefined) ??
(typeof thread.createdAt === "number" ? thread.createdAt : undefined) ??
0) * 1000,
),
persistence: {
provider: CODEX_PROVIDER,
sessionId: threadId,
nativeHandle: threadId,
metadata: {
provider: CODEX_PROVIDER,
cwd,
title,
threadId,
},
},
timeline: buildCodexThreadListTimeline(thread),
};
});
return descriptors;
} finally {