Files
openclaw/extensions/codex/src/migration/memory-plan.ts
Peter Steinberger 4319ddbe8c feat(control-ui): import Codex and Claude Code memory (#106406)
* feat(control-ui): import coding assistant memory

* test(migrate): clean memory import temp dirs

* chore: move memory import note to PR body

* build: refresh memory import generated artifacts

* fix(control-ui): complete memory import checks

* build(control-ui): localize memory recovery labels

* fix(control-ui): harden memory import recovery

* refactor(migrate): keep memory import surfaces bounded

* fix(control-ui): use canonical agent row type

* fix(codex): drop dead migration type exports

* fix(migrate): restrict imported memory permissions

* fix(control-ui): preserve memory import recovery state

* fix(control-ui): retain memory import results

* build(control-ui): preserve translation memory history

* fix(control-ui): bind memory import state to agent

* fix(control-ui): unlock memory import after refresh

* test(gateway): preserve memory method suffix

* fix(migrate): bind reviewed memory imports

* fix(migrate): make memory imports retry-safe

* perf(ui): keep memory import lazy

* build: refresh generated artifacts after rebase

* chore: keep migration runtime within LOC guard

* build: refresh Swift protocol model
2026-07-13 15:30:06 -07:00

76 lines
2.5 KiB
TypeScript

// Codex memory plans validate source/destination separation before exposing copy items.
import fs from "node:fs/promises";
import path from "node:path";
import { createMigrationItem, MIGRATION_REASON_TARGET_EXISTS } from "openclaw/plugin-sdk/migration";
import type { MigrationItem } from "openclaw/plugin-sdk/plugin-entry";
import {
canonicalPathFromExistingAncestor,
isPathInside,
} from "openclaw/plugin-sdk/security-runtime";
import { exists } from "./helpers.js";
import type { CodexMemorySource } from "./source-files.js";
async function assertSafeMemoryDestination(params: {
source: string;
workspaceDir: string;
target: string;
}): Promise<void> {
const [canonicalSource, canonicalWorkspace, canonicalTarget] = await Promise.all([
fs.realpath(path.dirname(params.source)),
canonicalPathFromExistingAncestor(params.workspaceDir),
canonicalPathFromExistingAncestor(params.target),
]);
if (!isPathInside(canonicalWorkspace, canonicalTarget)) {
throw new Error("Codex memory import destination must stay in the selected workspace.");
}
if (
isPathInside(canonicalSource, canonicalTarget) ||
isPathInside(canonicalTarget, canonicalSource)
) {
throw new Error("Codex memory source and OpenClaw import destination must be separate paths.");
}
}
export async function buildCodexMemoryItems(params: {
memoryFiles: readonly CodexMemorySource[];
workspaceDir: string;
overwrite?: boolean;
}): Promise<MigrationItem[]> {
const items: MigrationItem[] = [];
for (const memory of params.memoryFiles) {
const target = path.join(
params.workspaceDir,
"memory",
"imports",
"codex",
path.basename(memory.path),
);
await assertSafeMemoryDestination({
source: memory.path,
workspaceDir: params.workspaceDir,
target,
});
const targetExists = await exists(target);
items.push(
createMigrationItem({
id: memory.id,
kind: "memory",
action: "copy",
source: memory.path,
target,
status: targetExists && !params.overwrite ? "conflict" : "planned",
reason: targetExists && !params.overwrite ? MIGRATION_REASON_TARGET_EXISTS : undefined,
message: "Copy consolidated Codex memory into the OpenClaw memory index.",
details: {
sourceType: "codex-memory",
sourceLabel: memory.label,
collectionId: "codex",
collectionLabel: "Codex",
relativePath: path.basename(memory.path),
},
}),
);
}
return items;
}