Files
openclaw/extensions/codex/src/session-catalog-listing.test.ts
Vito Cappello 6d7bc062e3 fix(session-catalog): hide OpenClaw-managed provider sessions (#125424)
* fix(session-catalog): hide OpenClaw-managed upstream sessions

* fix(codex): filter managed paired-node sessions

* fix(codex): classify legacy managed sessions

* fix(session-catalog): classify managed provider sessions

* fix(session-catalog): backfill inter-session ownership

* fix(session-catalog): classify Claude internal prompts

* fix(session-catalog): retain durable provenance

* fix(codex): keep rollout home derivation private

* fix(anthropic): declare catalog schema dependency

* fix(anthropic): avoid catalog schema dependency

* fix(session-catalog): scope managed ownership to Codex

* fix(codex): contain catalog provenance reads

* fix(codex): bind managed threads to catalog home

---------

Co-authored-by: VACInc <3279061+VACInc@users.noreply.github.com>
Co-authored-by: Josh Lehman <550978+jalehman@users.noreply.github.com>
2026-08-20 19:51:55 -07:00

764 lines
26 KiB
TypeScript

// Codex supervision tests cover passive listing and safe local session takeover.
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { MAX_HOST_COUNT } from "./session-catalog-parsing.js";
import {
tempDirs,
createCodexSessionCatalogControl,
listCodexSessionCatalog,
registerCodexSessionCatalog,
config,
compatibilityOwnerConfig,
normalizeCodexManifestConfig,
idleThread,
createControl,
adoptedEntry,
supervisionSessionKey,
seedSupervisionBinding,
createRuntime,
createGatewayApi,
fs,
fsSync,
os,
path,
resolveAgentDir,
resolveSessionAgentIds,
resolveCodexAppServerHomeDir,
resolveCodexAppServerLocalHomeDir,
resolveDefaultAgentDir,
withEnvAsync,
createCodexCatalogHomeResolver,
createCodexTestBindingStore,
buildCodexAppServerConnectionFingerprint,
catalogError,
parseCatalogPage,
CODEX_LOCAL_SESSION_HOST_ID,
createCodexSessionCatalogControlFactory,
type CodexCatalogHome,
type OpenClawConfig,
originalPath,
} from "./session-catalog.test-helpers.js";
const commandRpcMocks = vi.hoisted(() => ({
codexControlRequest: vi.fn(),
}));
const pinnedConnectionMocks = vi.hoisted(() => ({
client: { connectionId: "pinned-catalog-client" },
getClient: vi.fn(),
releaseClient: vi.fn(),
request: vi.fn(),
}));
const transcriptMirrorMocks = vi.hoisted(() => ({
importCodexThreadHistoryToTranscript: vi.fn(async () => ({
importedMessages: 0,
omittedMessages: 0,
})),
}));
const nodeHostMocks = vi.hoisted(() => ({
runNodePtyCommand: vi.fn(async () => ({ exitCode: 0 })),
userShellPaths: new Map<string, string>(),
}));
vi.mock("./command-rpc.js", () => ({
codexControlRequest: commandRpcMocks.codexControlRequest,
}));
vi.mock("./app-server/request.js", () => ({
requestCodexAppServerClientJson: pinnedConnectionMocks.request,
}));
vi.mock("./app-server/shared-client.js", () => ({
getLeasedSharedCodexAppServerClient: pinnedConnectionMocks.getClient,
releaseLeasedSharedCodexAppServerClient: pinnedConnectionMocks.releaseClient,
}));
vi.mock("./app-server/transcript-mirror.js", () => ({
importCodexThreadHistoryToTranscript: transcriptMirrorMocks.importCodexThreadHistoryToTranscript,
}));
vi.mock("./session-catalog-pty.runtime.js", async (importOriginal) => {
const actual = await importOriginal<typeof import("./session-catalog-pty.runtime.js")>();
return {
...actual,
runNodePtyCommand: nodeHostMocks.runNodePtyCommand,
resolveNodeHostExecutable: (
command: string,
options: {
env?: NodeJS.ProcessEnv;
pathEnv?: string;
includeExtensionless?: boolean;
strategy: "direct" | "fallback" | "prefer";
},
) => {
const env = options.env ?? process.env;
const pathEnv = options.pathEnv ?? env.PATH ?? env.Path ?? "";
const direct = actual.resolveNodeHostExecutable(command, {
env,
pathEnv,
includeExtensionless: options.includeExtensionless,
strategy: "direct",
});
if (direct && options.strategy !== "prefer") {
return direct;
}
const shellPath = nodeHostMocks.userShellPaths.get(command);
if (!shellPath) {
return direct;
}
const shellExecutable = actual.resolveNodeHostExecutable(command, {
env,
pathEnv: shellPath,
includeExtensionless: options.includeExtensionless,
strategy: "direct",
});
return shellExecutable
? { executable: shellExecutable.executable, pathEnv: shellPath }
: direct;
},
};
});
beforeEach(() => {
nodeHostMocks.runNodePtyCommand.mockClear();
nodeHostMocks.userShellPaths.clear();
commandRpcMocks.codexControlRequest.mockReset();
pinnedConnectionMocks.getClient.mockReset();
pinnedConnectionMocks.getClient.mockResolvedValue(pinnedConnectionMocks.client);
pinnedConnectionMocks.releaseClient.mockReset();
pinnedConnectionMocks.request.mockReset();
transcriptMirrorMocks.importCodexThreadHistoryToTranscript.mockReset();
transcriptMirrorMocks.importCodexThreadHistoryToTranscript.mockResolvedValue({
importedMessages: 0,
omittedMessages: 0,
});
});
afterEach(async () => {
process.env.PATH = originalPath;
await Promise.all(tempDirs.splice(0).map((dir) => fs.rm(dir, { recursive: true, force: true })));
});
describe("Codex session catalog errors", () => {
it("preserves fallback names returned by paired nodes", () => {
expect(
parseCatalogPage({
sessions: [
{
threadId: "thread-1",
fallbackName: "Readable fallback",
status: "idle",
archived: false,
},
],
}),
).toEqual({
sessions: [
{
threadId: "thread-1",
fallbackName: "Readable fallback",
status: "idle",
archived: false,
},
],
});
});
it("keeps the underlying paired-node list failure", () => {
expect(catalogError("NODE_LIST_FAILED", new Error("paired store is unreadable"))).toEqual({
code: "NODE_LIST_FAILED",
message: "Paired nodes could not be listed: paired store is unreadable",
});
});
});
describe("Codex supervision catalog", () => {
it("lists non-archived interactive threads without probing transcript previews", async () => {
const pluginConfig = await normalizeCodexManifestConfig({
supervision: { enabled: true },
appServer: { command: "codex-catalog" },
});
expect((pluginConfig.appServer as Record<string, unknown>).homeScope).toBeUndefined();
commandRpcMocks.codexControlRequest.mockResolvedValue({
data: [
{
id: "thread-title",
name: "Match title",
preview: "private\ntranscript preview",
cwd: "/workspace/one",
status: { type: "idle" },
source: "vscode",
},
{
id: "thread-preview",
preview: "Match appears only in private preview text",
status: { type: "idle" },
source: "cli",
},
],
});
const control = createCodexSessionCatalogControl({
getPluginConfig: () => pluginConfig,
getRuntimeConfig: () => config,
});
await expect(
control.listPage({ limit: 25, searchTerm: "mAtCh", cwd: " /workspace/one " }),
).resolves.toEqual({
sessions: [
{
threadId: "thread-title",
name: "Match title",
cwd: "/workspace/one",
status: "idle",
source: "vscode",
archived: false,
},
],
});
expect(commandRpcMocks.codexControlRequest).toHaveBeenCalledOnce();
expect(commandRpcMocks.codexControlRequest).toHaveBeenCalledWith(
pluginConfig,
"thread/list",
{
archived: false,
limit: 25,
modelProviders: [],
sortKey: "updated_at",
sortDirection: "desc",
cwd: "/workspace/one",
},
{
agentDir: resolveDefaultAgentDir(config),
config,
startOptions: expect.objectContaining({ transport: "stdio", homeScope: "user" }),
timeoutMs: expect.any(Number),
},
);
expect(JSON.stringify(await control.listPage({ searchTerm: "mAtCh" }))).not.toContain(
"private",
);
expect(commandRpcMocks.codexControlRequest.mock.calls.map((call) => call[1])).not.toContain(
"thread/resume",
);
});
it("preserves the retained owner directory across normal cloned requests", async () => {
const runtimeConfig = compatibilityOwnerConfig();
const expectedAgentDir = resolveDefaultAgentDir(runtimeConfig);
commandRpcMocks.codexControlRequest.mockImplementation(
async (
_pluginConfig: unknown,
_method: string,
_params: unknown,
options: { agentDir?: string; config?: OpenClawConfig },
) => {
if (!options.agentDir) {
try {
resolveSessionAgentIds({ config: options.config });
} catch (error) {
throw new Error((error as { code?: string }).code ?? String(error), { cause: error });
}
}
return { data: [] };
},
);
const control = createCodexSessionCatalogControl({
getPluginConfig: () => ({ supervision: { enabled: true } }),
getRuntimeConfig: () => runtimeConfig,
});
await expect(control.listPage({})).resolves.toEqual({ sessions: [] });
const requestOptions = commandRpcMocks.codexControlRequest.mock.calls[0]?.[3];
expect(requestOptions?.config).not.toBe(runtimeConfig);
expect(requestOptions?.agentDir).toBe(expectedAgentDir);
});
it("uses the Gateway-selected owner directory for an explicit multi-agent catalog", async () => {
const runtimeConfig = {
agents: { ownership: "explicit", entries: { alpha: {}, beta: {} } },
} as OpenClawConfig;
commandRpcMocks.codexControlRequest.mockResolvedValue({ data: [] });
const control = createCodexSessionCatalogControlFactory({
getPluginConfig: () => ({ supervision: { enabled: true } }),
getRuntimeConfig: () => runtimeConfig,
}).forRequest("beta");
await expect(control.listPage({})).resolves.toEqual({ sessions: [] });
expect(commandRpcMocks.codexControlRequest.mock.calls[0]?.[3]).toMatchObject({
agentDir: resolveAgentDir(runtimeConfig, "beta"),
config: { agents: { ownership: "explicit", entries: { alpha: {}, beta: {} } } },
});
});
it("discovers configured and automatic Codex homes while retaining the route owner", async () => {
const root = await fs.realpath(
await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-codex-catalog-homes-")),
);
tempDirs.push(root);
const alphaAgentDir = path.join(root, "agents", "alpha", "agent");
const betaAgentDir = path.join(root, "agents", "beta", "agent");
const fileAgentDir = path.join(root, "agents", "file", "agent");
const processCodexHome = path.join(root, "process-codex-home");
const alphaCodexHome = resolveCodexAppServerHomeDir(alphaAgentDir);
const betaCodexHome = resolveCodexAppServerHomeDir(betaAgentDir);
const fileAgentCodexHome = resolveCodexAppServerHomeDir(fileAgentDir);
const configuredCodexHomes = Array.from({ length: MAX_HOST_COUNT }, (_, index) =>
path.join(root, "configured-codex-home", String(index)),
);
const configuredCodexHome = configuredCodexHomes[0]!;
const configuredCodexHomeAlias = path.join(root, "configured-codex-home-alias");
const configuredFile = path.join(root, "not-a-codex-home");
await Promise.all(
[processCodexHome, alphaCodexHome, betaCodexHome, ...configuredCodexHomes].map((dir) =>
fs.mkdir(dir, { recursive: true }),
),
);
await Promise.all([
fs.symlink(configuredCodexHome, configuredCodexHomeAlias, "dir"),
fs.writeFile(configuredFile, "not a directory"),
fs
.mkdir(fileAgentDir, { recursive: true })
.then(() => fs.writeFile(fileAgentCodexHome, "not a directory")),
]);
const runtimeConfig = {
agents: {
ownership: "explicit",
list: [
{ id: "alpha", agentDir: alphaAgentDir },
{ id: "beta", agentDir: betaAgentDir },
{ id: "file", agentDir: fileAgentDir },
],
},
} as OpenClawConfig;
const env = { ...process.env, CODEX_HOME: processCodexHome };
const control = createCodexSessionCatalogControlFactory({
config: runtimeConfig,
env,
getRuntimeConfig: () => runtimeConfig,
getPluginConfig: () => ({
supervision: { enabled: true },
sessionCatalog: {
homes: [
configuredCodexHome,
{ path: configuredCodexHomeAlias, label: "Duplicate alias" },
{ path: configuredCodexHomes[1]!, label: "Named store" },
{ path: configuredCodexHomes[2]! },
alphaCodexHome,
path.join(root, "missing-codex-home"),
configuredFile,
...configuredCodexHomes.slice(3),
],
},
}),
});
const homes = control.homesForAgent("beta");
const resolvedHomes = homes.map((home) =>
resolveCodexAppServerLocalHomeDir(home.appServer.start, home.agentDir, env),
);
expect(homes).toHaveLength(MAX_HOST_COUNT);
expect(resolvedHomes.slice(0, 3)).toEqual([processCodexHome, betaCodexHome, alphaCodexHome]);
expect(resolvedHomes.filter((home) => configuredCodexHomes.includes(home))).toHaveLength(
MAX_HOST_COUNT - 3,
);
expect(homes.slice(3, 6).map((home) => home.label)).toEqual([
"Local Codex · 0",
"Local Codex · Named store",
"Local Codex · 2",
]);
expect(homes.map((home) => home.agentDir)).toEqual(Array(MAX_HOST_COUNT).fill(betaAgentDir));
expect(homes[0]?.hostId).toBe(CODEX_LOCAL_SESSION_HOST_ID);
expect(homes.slice(1).every((home) => home.hostId.startsWith("gateway:local:"))).toBe(true);
expect(new Set(homes.map((home) => home.sourceHomeId)).size).toBe(MAX_HOST_COUNT);
expect(
JSON.stringify(homes.map(({ hostId, sourceHomeId }) => ({ hostId, sourceHomeId }))),
).not.toContain(root);
commandRpcMocks.codexControlRequest.mockResolvedValue({ data: [] });
pinnedConnectionMocks.request.mockResolvedValue({
thread: idleThread({ id: "thread-source" }),
});
const configuredSource = homes.find(
(home) =>
resolveCodexAppServerLocalHomeDir(home.appServer.start, home.agentDir, env) ===
configuredCodexHome,
);
expect(configuredSource).toBeDefined();
const configuredFingerprint = buildCodexAppServerConnectionFingerprint(
configuredSource!.appServer,
configuredSource!.agentDir,
);
const boundControl = control.forUpstream("beta", configuredFingerprint);
expect(boundControl).toBeDefined();
expect(control.forUpstream("beta", "unknown-fingerprint")).toBeUndefined();
await boundControl!.listPage({});
await boundControl!.withPinnedConnection(
async (pinned) => await pinned.readThread("thread-source", false),
);
expect(commandRpcMocks.codexControlRequest.mock.calls[0]?.[3]).toMatchObject({
agentDir: betaAgentDir,
startOptions: { env: { CODEX_HOME: configuredCodexHome } },
});
expect(pinnedConnectionMocks.getClient).toHaveBeenCalledWith(
expect.objectContaining({
agentDir: betaAgentDir,
startOptions: expect.objectContaining({
env: expect.objectContaining({ CODEX_HOME: configuredCodexHome }),
}),
}),
);
});
it("refreshes Codex homes once for each hot-reloaded config generation", async () => {
const root = await fs.realpath(
await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-codex-catalog-reload-")),
);
tempDirs.push(root);
const alphaAgentDir = path.join(root, "agents", "alpha", "agent");
const betaAgentDir = path.join(root, "agents", "beta", "agent");
const processCodexHome = path.join(root, "process-codex-home");
const alphaCodexHome = resolveCodexAppServerHomeDir(alphaAgentDir);
const betaCodexHome = resolveCodexAppServerHomeDir(betaAgentDir);
await Promise.all(
[processCodexHome, alphaCodexHome, betaCodexHome].map((dir) =>
fs.mkdir(dir, { recursive: true }),
),
);
const configA = {
agents: { ownership: "explicit", list: [{ id: "alpha", agentDir: alphaAgentDir }] },
} as OpenClawConfig;
const configB = {
agents: {
ownership: "explicit",
list: [
{ id: "alpha", agentDir: alphaAgentDir },
{ id: "beta", agentDir: betaAgentDir },
],
},
} as OpenClawConfig;
let runtimeConfig = configA;
const statSync = vi.spyOn(fsSync, "statSync");
try {
const resolver = createCodexCatalogHomeResolver({
config: configA,
getRuntimeConfig: () => runtimeConfig,
getPluginConfig: () => ({ supervision: { enabled: true } }),
env: { ...process.env, CODEX_HOME: processCodexHome },
});
const seedDiscoveryCount = statSync.mock.calls.length;
expect(resolver.forAgent("alpha")).not.toHaveLength(0);
expect(resolver.forAgent("alpha")).not.toHaveLength(0);
expect(statSync).toHaveBeenCalledTimes(seedDiscoveryCount);
runtimeConfig = configB;
const betaHomes = resolver.forAgent("beta");
expect(
betaHomes.some(
(home) =>
resolveCodexAppServerLocalHomeDir(home.appServer.start, home.agentDir, process.env) ===
betaCodexHome,
),
).toBe(true);
const reloadedDiscoveryCount = statSync.mock.calls.length;
expect(resolver.forAgent("beta")).toEqual(betaHomes);
expect(statSync).toHaveBeenCalledTimes(reloadedDiscoveryCount);
} finally {
statSync.mockRestore();
}
});
it("exposes every local source as an actionable host for the selected owner", async () => {
const root = await fs.realpath(
await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-codex-catalog-hosts-")),
);
tempDirs.push(root);
const alphaAgentDir = path.join(root, "agents", "alpha", "agent");
const betaAgentDir = path.join(root, "agents", "beta", "agent");
const processCodexHome = path.join(root, "process-codex-home");
await Promise.all(
[
processCodexHome,
resolveCodexAppServerHomeDir(alphaAgentDir),
resolveCodexAppServerHomeDir(betaAgentDir),
].map((dir) => fs.mkdir(dir, { recursive: true })),
);
const runtimeConfig = {
agents: {
ownership: "explicit",
list: [
{ id: "alpha", agentDir: alphaAgentDir },
{ id: "beta", agentDir: betaAgentDir },
],
},
} as OpenClawConfig;
const { runtime } = createRuntime();
const { api, getProvider } = createGatewayApi(runtime, runtimeConfig);
const listPage = vi.fn(async (source?: { agentDir: string; sourceHomeId: string }) => ({
sessions: [
{
threadId: `thread-${source?.sourceHomeId ?? "missing"}`,
status: "idle",
source: "cli",
archived: false as const,
},
],
}));
const forRequest = vi.fn((agentId: string, source?: CodexCatalogHome) =>
createControl({ listPage: async () => await listPage(source) }),
);
registerCodexSessionCatalog({
api,
bindingStore: createCodexTestBindingStore(),
control: { forRequest },
getPluginConfig: () => ({ supervision: { enabled: true } }),
getRuntimeConfig: () => runtimeConfig,
});
await withEnvAsync({ CODEX_HOME: processCodexHome }, async () => {
const hosts = await getProvider()?.list({
agentId: "beta",
listNodes: async () => ({ nodes: [] }),
});
expect(hosts).toHaveLength(3);
expect(hosts?.every((host) => host.hostId.startsWith("gateway:local"))).toBe(true);
expect(
hosts?.every(
(host) =>
host.sessions[0]?.sourceHomeId &&
host.sessions[0]?.canContinue &&
host.sessions[0]?.canArchive,
),
).toBe(true);
expect(forRequest.mock.calls.every(([agentId]) => agentId === "beta")).toBe(true);
expect(forRequest.mock.calls.every(([, source]) => source?.agentDir === betaAgentDir)).toBe(
true,
);
});
});
it("does not project an adopted session onto another local home with the same thread id", async () => {
const source = (sourceHomeId: string, hostId: string): CodexCatalogHome => ({
sourceHomeId,
hostId,
label: sourceHomeId,
agentDir: `/agents/${sourceHomeId}`,
appServer: {} as CodexCatalogHome["appServer"],
usesProcessHomeFallback: false,
});
const homeA = source("home-a", CODEX_LOCAL_SESSION_HOST_ID);
const homeB = source("home-b", `${CODEX_LOCAL_SESSION_HOST_ID}:home-b`);
const sessionKey = supervisionSessionKey("thread-1", homeA.sourceHomeId);
const sessionId = "openclaw-session-home-a";
const { runtime } = createRuntime({
entries: [
{
sessionKey,
entry: adoptedEntry({ sourceThreadId: "thread-1", sourceHomeId: "home-a", sessionId }),
},
],
});
const bindingStore = createCodexTestBindingStore();
await seedSupervisionBinding({
bindingStore,
sessionId,
sessionKey,
sourceThreadId: "thread-1",
});
const control = createControl({
listPage: vi.fn(async () => ({
sessions: [
{ threadId: "thread-1", status: "idle", source: "cli", archived: false as const },
],
})),
});
const result = await listCodexSessionCatalog({
agentId: "main",
bindingStore,
config,
runtime,
control,
localHomes: [homeA, homeB],
listNodes: async () => ({ nodes: [] }),
});
const sessions = new Map(result.hosts.map((host) => [host.hostId, host.sessions[0]]));
expect(sessions.get(homeA.hostId)).toMatchObject({ sessionKey, sourceHomeId: "home-a" });
expect(sessions.get(homeB.hostId)).toMatchObject({ sourceHomeId: "home-b" });
expect(sessions.get(homeB.hostId)).not.toHaveProperty("sessionKey");
});
it("bulk-loads managed thread ids once and backfills visible catalog pages", async () => {
const homeA: CodexCatalogHome = {
sourceHomeId: "home-a",
hostId: CODEX_LOCAL_SESSION_HOST_ID,
label: "home-a",
agentDir: "/agents/main",
appServer: {} as CodexCatalogHome["appServer"],
usesProcessHomeFallback: false,
};
const homeB: CodexCatalogHome = {
...homeA,
sourceHomeId: "home-b",
hostId: `${CODEX_LOCAL_SESSION_HOST_ID}:home-b`,
label: "home-b",
};
const snapshot = vi.fn(
async () => new Map<string, ReadonlySet<string>>([["home-a", new Set(["thread-managed"])]]),
);
const bindingStore = Object.assign(createCodexTestBindingStore(), {
managedThreads: { mark: vi.fn(), snapshot },
});
const listPage = vi.fn(async (params?: { cursor?: string; limit?: number }) =>
params?.cursor === "next"
? {
sessions: [
{ threadId: "thread-visible-2", status: "idle", source: "cli", archived: false },
],
}
: {
sessions: [
{ threadId: "thread-managed", status: "idle", source: "vscode", archived: false },
{ threadId: "thread-visible-1", status: "idle", source: "cli", archived: false },
],
nextCursor: "next",
},
);
const control = createControl({ listPage });
const result = await listCodexSessionCatalog({
agentId: "main",
bindingStore,
config,
runtime: createRuntime().runtime,
control,
query: { limitPerHost: 2 },
localHomes: [homeA, homeB],
listNodes: async () => ({ nodes: [] }),
});
expect(snapshot).toHaveBeenCalledOnce();
expect(listPage).toHaveBeenCalledTimes(3);
expect(result.hosts[0]?.sessions.map((session) => session.threadId)).toEqual([
"thread-visible-1",
"thread-visible-2",
]);
expect(result.hosts[1]?.sessions.map((session) => session.threadId)).toEqual([
"thread-managed",
"thread-visible-1",
]);
});
it("backfills a provenance-filtered first page through the real listing path", async () => {
const root = await fs.realpath(
await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-codex-provenance-page-")),
);
tempDirs.push(root);
const sessionsRoot = path.join(root, "sessions");
await fs.mkdir(sessionsRoot);
const rolloutPath = path.join(sessionsRoot, "managed.jsonl");
await fs.writeFile(
rolloutPath,
`${JSON.stringify({
type: "session_meta",
payload: { id: "thread-managed", originator: "openclaw" },
})}\n`,
);
commandRpcMocks.codexControlRequest.mockImplementation(
async (_pluginConfig: unknown, method: string, params: { cursor?: string }) => {
expect(method).toBe("thread/list");
return params.cursor === "native-page"
? { data: [idleThread({ id: "thread-native", source: "cli" })] }
: {
data: [
idleThread({
id: "thread-managed",
path: rolloutPath,
source: "vscode",
}),
],
nextCursor: "native-page",
};
},
);
const runtimeConfig = config;
const control = createCodexSessionCatalogControlFactory({
env: { ...process.env, CODEX_HOME: root },
getPluginConfig: () => ({ supervision: { enabled: true } }),
getRuntimeConfig: () => runtimeConfig,
});
const home = control.homesForAgent("main")[0]!;
const mark = vi.fn(async () => true);
const bindingStore = Object.assign(createCodexTestBindingStore(), {
managedThreads: {
mark,
snapshot: vi.fn(async () => new Map<string, ReadonlySet<string>>()),
},
});
const result = await listCodexSessionCatalog({
agentId: "main",
bindingStore,
config: runtimeConfig,
runtime: createRuntime().runtime,
control,
query: { limitPerHost: 1 },
localHomes: [home],
listNodes: async () => ({ nodes: [] }),
});
expect(result.hosts[0]?.sessions.map((session) => session.threadId)).toEqual(["thread-native"]);
expect(mark).toHaveBeenCalledWith({
sourceHomeId: home.sourceHomeId,
threadId: "thread-managed",
rolloutPath,
});
});
it("uses a sanitized preview only when Codex has no thread name", async () => {
const pluginConfig = { supervision: { enabled: true } };
commandRpcMocks.codexControlRequest.mockResolvedValue({
data: [
{
id: "thread-named",
name: "Explicit title",
preview: "must stay private",
status: { type: "idle" },
source: "cli",
},
{
id: "thread-fallback",
preview: "Investigate\nfailed Rosita run",
status: { type: "idle" },
source: "cli",
},
],
});
const control = createCodexSessionCatalogControl({
getPluginConfig: () => pluginConfig,
getRuntimeConfig: () => config,
});
await expect(control.listPage({ limit: 25 })).resolves.toEqual({
sessions: [
{
threadId: "thread-named",
name: "Explicit title",
status: "idle",
source: "cli",
archived: false,
},
{
threadId: "thread-fallback",
fallbackName: "Investigate failed Rosita run",
status: "idle",
source: "cli",
archived: false,
},
],
});
});
});