mirror of
https://github.com/moltbot/moltbot.git
synced 2026-05-15 17:05:34 +00:00
fix: canonicalize legacy codex binding import
This commit is contained in:
@@ -255,7 +255,7 @@ vi.mock("../terminal/ansi.js", () => ({
|
||||
vi.mock("../trajectory/runtime.js", () => ({
|
||||
createTrajectoryRuntimeRecorder: () => ({
|
||||
enabled: true,
|
||||
filePath: "/tmp/session.trajectory.jsonl",
|
||||
runtimeLocator: "sqlite:default:trajectory:session-1",
|
||||
recordEvent: (...args: unknown[]) => state.trajectoryRecordEventMock(...args),
|
||||
flush: () => state.trajectoryFlushMock(),
|
||||
}),
|
||||
|
||||
@@ -178,8 +178,12 @@ describe("doctor session transcript repair", () => {
|
||||
it("imports legacy Codex app-server binding sidecars during repair mode", async () => {
|
||||
const sessionsDir = path.join(root, "agents", "main", "sessions");
|
||||
await fs.mkdir(sessionsDir, { recursive: true });
|
||||
const sessionFile = path.join(sessionsDir, "session.jsonl");
|
||||
const sidecarPath = `${sessionFile}.codex-app-server.json`;
|
||||
const legacyTranscriptPath = path.join(sessionsDir, "session.jsonl");
|
||||
await fs.writeFile(
|
||||
legacyTranscriptPath,
|
||||
`${JSON.stringify({ type: "session", version: 3, id: "session-1", cwd: root })}\n`,
|
||||
);
|
||||
const sidecarPath = `${legacyTranscriptPath}.codex-app-server.json`;
|
||||
await fs.writeFile(
|
||||
sidecarPath,
|
||||
JSON.stringify({
|
||||
@@ -193,10 +197,10 @@ describe("doctor session transcript repair", () => {
|
||||
await noteSessionTranscriptHealth({ shouldRepair: true, sessionDirs: [sessionsDir] });
|
||||
|
||||
await expect(fs.access(sidecarPath)).rejects.toThrow();
|
||||
expect(readOpenClawStateKvJson("codex_app_server_thread_bindings", sessionFile)).toMatchObject({
|
||||
expect(readOpenClawStateKvJson("codex_app_server_thread_bindings", "session-1")).toMatchObject({
|
||||
schemaVersion: 1,
|
||||
threadId: "thread-123",
|
||||
sessionFile,
|
||||
sessionId: "session-1",
|
||||
cwd: root,
|
||||
model: "gpt-5.5",
|
||||
});
|
||||
|
||||
@@ -43,7 +43,8 @@ type TranscriptMigrationResult = TranscriptRepairResult & {
|
||||
|
||||
type CodexAppServerBindingMigrationResult = {
|
||||
filePath: string;
|
||||
sessionFile: string;
|
||||
legacyTranscriptPath: string;
|
||||
sessionId: string;
|
||||
imported: boolean;
|
||||
removedSource: boolean;
|
||||
reason?: string;
|
||||
@@ -296,12 +297,28 @@ async function listCodexAppServerBindingSidecars(sessionDirs: string[]): Promise
|
||||
return files.toSorted((a, b) => a.localeCompare(b));
|
||||
}
|
||||
|
||||
function resolveCodexAppServerBindingSessionFile(sidecarPath: string): string {
|
||||
function resolveCodexAppServerBindingTranscriptPath(sidecarPath: string): string {
|
||||
return sidecarPath.slice(0, -CODEX_APP_SERVER_BINDING_SIDECAR_SUFFIX.length);
|
||||
}
|
||||
|
||||
async function resolveCodexAppServerBindingSessionId(
|
||||
legacyTranscriptPath: string,
|
||||
): Promise<string> {
|
||||
try {
|
||||
const raw = await fs.readFile(legacyTranscriptPath, "utf-8");
|
||||
const sessionId = getSessionId(parseTranscriptEntries(raw));
|
||||
if (sessionId) {
|
||||
return sessionId;
|
||||
}
|
||||
} catch {
|
||||
// Fall back to the legacy filename when only the sidecar survived.
|
||||
}
|
||||
const basename = path.basename(legacyTranscriptPath);
|
||||
return basename.endsWith(".jsonl") ? basename.slice(0, -".jsonl".length) : basename;
|
||||
}
|
||||
|
||||
function normalizeCodexAppServerBindingPayload(
|
||||
sessionFile: string,
|
||||
sessionId: string,
|
||||
value: unknown,
|
||||
): OpenClawStateJsonValue | undefined {
|
||||
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
||||
@@ -317,7 +334,7 @@ function normalizeCodexAppServerBindingPayload(
|
||||
}
|
||||
return {
|
||||
schemaVersion: 1,
|
||||
sessionFile,
|
||||
sessionId,
|
||||
threadId: parsed.threadId,
|
||||
cwd: typeof parsed.cwd === "string" ? parsed.cwd : "",
|
||||
authProfileId: typeof parsed.authProfileId === "string" ? parsed.authProfileId : undefined,
|
||||
@@ -339,14 +356,16 @@ async function migrateCodexAppServerBindingSidecar(params: {
|
||||
filePath: string;
|
||||
shouldRepair: boolean;
|
||||
}): Promise<CodexAppServerBindingMigrationResult> {
|
||||
const sessionFile = resolveCodexAppServerBindingSessionFile(params.filePath);
|
||||
const legacyTranscriptPath = resolveCodexAppServerBindingTranscriptPath(params.filePath);
|
||||
const sessionId = await resolveCodexAppServerBindingSessionId(legacyTranscriptPath);
|
||||
try {
|
||||
const raw = await fs.readFile(params.filePath, "utf-8");
|
||||
const payload = normalizeCodexAppServerBindingPayload(sessionFile, JSON.parse(raw));
|
||||
const payload = normalizeCodexAppServerBindingPayload(sessionId, JSON.parse(raw));
|
||||
if (!payload) {
|
||||
return {
|
||||
filePath: params.filePath,
|
||||
sessionFile,
|
||||
legacyTranscriptPath,
|
||||
sessionId,
|
||||
imported: false,
|
||||
removedSource: false,
|
||||
reason: "invalid binding payload",
|
||||
@@ -355,23 +374,26 @@ async function migrateCodexAppServerBindingSidecar(params: {
|
||||
if (!params.shouldRepair) {
|
||||
return {
|
||||
filePath: params.filePath,
|
||||
sessionFile,
|
||||
legacyTranscriptPath,
|
||||
sessionId,
|
||||
imported: false,
|
||||
removedSource: false,
|
||||
};
|
||||
}
|
||||
writeOpenClawStateKvJson(CODEX_APP_SERVER_BINDING_KV_SCOPE, sessionFile, payload);
|
||||
writeOpenClawStateKvJson(CODEX_APP_SERVER_BINDING_KV_SCOPE, sessionId, payload);
|
||||
await fs.rm(params.filePath, { force: true });
|
||||
return {
|
||||
filePath: params.filePath,
|
||||
sessionFile,
|
||||
legacyTranscriptPath,
|
||||
sessionId,
|
||||
imported: true,
|
||||
removedSource: true,
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
filePath: params.filePath,
|
||||
sessionFile,
|
||||
legacyTranscriptPath,
|
||||
sessionId,
|
||||
imported: false,
|
||||
removedSource: false,
|
||||
reason: String(error),
|
||||
@@ -398,14 +420,14 @@ export async function noteSessionTranscriptHealth(params?: {
|
||||
return;
|
||||
}
|
||||
|
||||
const results: TranscriptMigrationResult[] = [];
|
||||
for (const filePath of files) {
|
||||
results.push(await migrateSessionTranscriptFileToSqlite({ filePath, shouldRepair }));
|
||||
}
|
||||
const codexBindingResults: CodexAppServerBindingMigrationResult[] = [];
|
||||
for (const filePath of codexBindingSidecars) {
|
||||
codexBindingResults.push(await migrateCodexAppServerBindingSidecar({ filePath, shouldRepair }));
|
||||
}
|
||||
const results: TranscriptMigrationResult[] = [];
|
||||
for (const filePath of files) {
|
||||
results.push(await migrateSessionTranscriptFileToSqlite({ filePath, shouldRepair }));
|
||||
}
|
||||
const broken = results.filter((result) => result.broken);
|
||||
const imported = results.filter((result) => result.imported);
|
||||
const failed = results.filter((result) => result.reason && !result.imported);
|
||||
|
||||
@@ -580,12 +580,6 @@ describe("doctor state integrity oauth dir checks", () => {
|
||||
it("moves a heartbeat-poisoned main session and clears stale TUI restore pointers", async () => {
|
||||
const cfg: OpenClawConfig = {};
|
||||
setupSessionState(process.env, tempHome);
|
||||
const sessionsDir = resolveLegacySessionTranscriptsDirForAgent(
|
||||
"main",
|
||||
process.env,
|
||||
() => tempHome,
|
||||
);
|
||||
const heartbeatTranscriptPath = path.join(sessionsDir, "heartbeat-session.jsonl");
|
||||
replaceSqliteSessionTranscriptEvents({
|
||||
agentId: "main",
|
||||
sessionId: "heartbeat-session",
|
||||
@@ -597,7 +591,6 @@ describe("doctor state integrity oauth dir checks", () => {
|
||||
await writeSessionStore(cfg, {
|
||||
"agent:main:main": {
|
||||
sessionId: "heartbeat-session",
|
||||
sessionFile: heartbeatTranscriptPath,
|
||||
updatedAt: Date.now(),
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user