Skip to content
Closed
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
19 changes: 16 additions & 3 deletions src/codex/inject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1194,7 +1194,10 @@ export async function restoreNativeCodexAsync(): Promise<{
success: boolean;
message: string;
}> {
const inline = restoreNativeCodex({ skipHistory: true });
const inline = restoreNativeCodexCore({ skipHistory: true });
if (!inline.restoreHistory) {
return { success: inline.success, message: inline.message };
}
const outcome = await runCodexHistoryJob({
...resolveCodexHistoryJobTarget(),
operation: deriveCodexHistoryOperation({
Expand All @@ -1217,16 +1220,18 @@ export async function restoreNativeCodexAsync(): Promise<{
return { success: inline.success, message: `${inline.message}${historyMsg}` };
}

export function restoreNativeCodex(options: { skipHistory?: boolean } = {}): {
function restoreNativeCodexCore(options: { skipHistory?: boolean } = {}): {
success: boolean;
message: string;
restoreHistory: boolean;
} {
const activeProvider = currentExternalCodexModelProvider();
if (activeProvider) {
removeJournal();
return {
success: true,
message: `External Codex provider ${tomlString(activeProvider)} preserved; no native restore was needed.`,
restoreHistory: false,
};
}
const journal = restoreJournalState();
Expand Down Expand Up @@ -1272,7 +1277,15 @@ export function restoreNativeCodex(options: { skipHistory?: boolean } = {}): {
: history.ejectedRows
? ` ${history.ejectedRows} opencodex history thread(s) were ejected to openai so native Codex can resume them.`
: "";
return { success: cfg.success, message: `${msg}${historyMsg}` };
return { success: cfg.success, message: `${msg}${historyMsg}`, restoreHistory: true };
}

export function restoreNativeCodex(options: { skipHistory?: boolean } = {}): {
success: boolean;
message: string;
} {
const result = restoreNativeCodexCore(options);
return { success: result.success, message: result.message };
}

export function getCodexConfigPath(): string {
Expand Down
4 changes: 2 additions & 2 deletions tests/codex-history-job.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ test("an overrun Worker returns a typed timeout rather than hanging", async () =
*/
test("the synchronous restore body is gated on skipHistory", () => {
const source = readFileSync(join(import.meta.dir, "..", "src", "codex", "inject.ts"), "utf8");
const body = source.slice(source.indexOf("export function restoreNativeCodex("));
const body = source.slice(source.indexOf("function restoreNativeCodexCore("));
const historyCall = body.indexOf("syncCodexHistoryProvider(\"openai\"");
expect(historyCall).toBeGreaterThan(-1);

Expand All @@ -157,5 +157,5 @@ test("the synchronous restore body is gated on skipHistory", () => {
expect(gate).toBeLessThan(historyCall);

// And the async wrapper is the thing that sets it.
expect(source).toContain("restoreNativeCodex({ skipHistory: true })");
expect(source).toContain("restoreNativeCodexCore({ skipHistory: true })");
});
42 changes: 42 additions & 0 deletions tests/codex-inject-integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,19 @@ function runRestore(codexHome: string, ocxHome: string): { stdout: string; statu
return { stdout: result.stdout?.trim() ?? "", status: result.status ?? 1 };
}

function runRestoreAsync(codexHome: string, ocxHome: string): { stdout: string; status: number } {
const script = `
const { restoreNativeCodexAsync } = require("./src/codex/inject");
restoreNativeCodexAsync().then(result => console.log(JSON.stringify(result)));
`;
const result = spawnSync(process.execPath, ["--eval", script], {
cwd: repoRoot,
env: { ...process.env, CODEX_HOME: codexHome, OPENCODEX_HOME: ocxHome },
encoding: "utf8",
});
return { stdout: result.stdout?.trim() ?? "", status: result.status ?? 1 };
}

describe("injectCodexConfig integration (Design B)", () => {
let codexHome: string;
let ocxHome: string;
Expand Down Expand Up @@ -505,6 +518,35 @@ describe("injectCodexConfig integration (Design B)", () => {
expect(existsSync(journalPath)).toBe(false);
});

test("async restore preserves external provider history", () => {
const configPath = join(codexHome, "config.toml");
writeFileSync(configPath, 'model_provider = "custom"\n', "utf8");
const rolloutPath = join(codexHome, "rollout-custom.jsonl");
const rollout = `${JSON.stringify({
type: "session_meta",
payload: { id: "thread-custom", model_provider: "opencodex", source: "cli" },
})}\n`;
writeFileSync(rolloutPath, rollout, "utf8");
const dbPath = join(codexHome, "state_5.sqlite");
const db = new Database(dbPath);
db.run(`CREATE TABLE threads (
id TEXT PRIMARY KEY, rollout_path TEXT NOT NULL, model_provider TEXT NOT NULL,
source TEXT NOT NULL, first_user_message TEXT NOT NULL, has_user_event INTEGER NOT NULL
)`);
db.run(`INSERT INTO threads VALUES ('thread-custom', ?, 'opencodex', 'cli', 'hello', 1)`, rolloutPath);
db.close();

const result = runRestoreAsync(codexHome, ocxHome);

expect(result.status).toBe(0);
expect(JSON.parse(result.stdout).message).toContain('External Codex provider "custom" preserved');
const restored = new Database(dbPath, { readonly: true });
expect(restored.query("SELECT model_provider FROM threads WHERE id = 'thread-custom'").get())
.toEqual({ model_provider: "opencodex" });
restored.close();
expect(readFileSync(rolloutPath, "utf8")).toBe(rollout);
});

test("provider selected through a legacy root profile is also preserved", () => {
const original = [
'profile = "work"',
Expand Down
Loading