codemem 0.43.2 → 0.44.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -91,7 +91,7 @@ function emitJsonError(errorCode, message, exitCode = 1) {
91
91
  * and empty) are treated as toggles, not paths, so the default is used.
92
92
  * - All I/O is best-effort: failures are swallowed.
93
93
  */
94
- var BOOLEAN_TOGGLE_VALUES = new Set([
94
+ var BOOLEAN_TOGGLE_VALUES = /* @__PURE__ */ new Set([
95
95
  "",
96
96
  "0",
97
97
  "false",
@@ -131,7 +131,7 @@ function logHookEvent(message) {
131
131
  var FILE_GATE_MIN_BYTES = 1500;
132
132
  var FETCH_LIMIT = 40;
133
133
  var DISPLAY_LIMIT = 15;
134
- var MTIME_FRESH_TOLERANCE_MS = 300 * 1e3;
134
+ var MTIME_FRESH_TOLERANCE_MS = 3e5;
135
135
  var SMALL_FILE_BYPASS_PATTERNS = [
136
136
  /\.(json|jsonc|toml|ya?ml)$/i,
137
137
  /\.env(\.|$)/i,
@@ -286,7 +286,8 @@ function queryByFile(dbPath, relativePath, project, limit) {
286
286
  }
287
287
  }
288
288
  function resolveProject$1(payload) {
289
- return resolveHookProject(typeof payload.cwd === "string" ? payload.cwd : null, payload.project);
289
+ const cwd = typeof payload.cwd === "string" ? payload.cwd : null;
290
+ return resolveHookProject(cwd, payload.project);
290
291
  }
291
292
  function sourceSessionId(payload) {
292
293
  const value = payload.session_id;
@@ -758,14 +759,16 @@ function createHookIngestSpool(cfg) {
758
759
  return false;
759
760
  }
760
761
  const payloadText = JSON.stringify(payload);
761
- const tmpPath = join(dir, `.hook-tmp-${process.pid}-${Date.now()}-${randomInt(1e3, 1e4)}.json`);
762
+ const tmpName = `.hook-tmp-${process.pid}-${Date.now()}-${randomInt(1e3, 1e4)}.json`;
763
+ const tmpPath = join(dir, tmpName);
762
764
  try {
763
765
  writeFileSync(tmpPath, payloadText, { encoding: "utf8" });
764
766
  } catch {
765
767
  logHookEvent(`${cfg.logPrefix} failed to allocate spool temp file`);
766
768
  return false;
767
769
  }
768
- const finalPath = join(dir, `hook-${Math.floor(Date.now() / 1e3)}-${process.pid}-${randomInt(1e3, 1e4)}.json`);
770
+ const finalName = `hook-${Math.floor(Date.now() / 1e3)}-${process.pid}-${randomInt(1e3, 1e4)}.json`;
771
+ const finalPath = join(dir, finalName);
769
772
  try {
770
773
  renameSync(tmpPath, finalPath);
771
774
  } catch {
@@ -809,7 +812,8 @@ function createHookIngestSpool(cfg) {
809
812
  continue;
810
813
  }
811
814
  if (nowS - mtimeS <= ttlSeconds) continue;
812
- const recoveredPath = join(dir, `hook-recovered-${Math.floor(nowS)}-${process.pid}-${randomInt(1e3, 1e4)}.json`);
815
+ const recoveredName = `hook-recovered-${Math.floor(nowS)}-${process.pid}-${randomInt(1e3, 1e4)}.json`;
816
+ const recoveredPath = join(dir, recoveredName);
813
817
  try {
814
818
  renameSync(tmpPath, recoveredPath);
815
819
  logHookEvent(`${cfg.logPrefix} recovered stale temp spool payload: ${recoveredPath}`);
@@ -1049,7 +1053,8 @@ function nowIso() {
1049
1053
  return (/* @__PURE__ */ new Date()).toISOString();
1050
1054
  }
1051
1055
  function saveSessionState(sessionId, state) {
1052
- mkdirSync(contextDir(), { recursive: true });
1056
+ const dir = contextDir();
1057
+ mkdirSync(dir, { recursive: true });
1053
1058
  const path = statePathForSession(sessionId);
1054
1059
  const tmpPath = `${path}.tmp`;
1055
1060
  const payload = {
@@ -1571,7 +1576,8 @@ function extractInjectContext(payload) {
1571
1576
  return normalizePromptText(payload.prompt) || null;
1572
1577
  }
1573
1578
  function resolveInjectProject$1(payload) {
1574
- return resolveHookProject(typeof payload.cwd === "string" ? payload.cwd : null, payload.project);
1579
+ const cwd = typeof payload.cwd === "string" ? payload.cwd : null;
1580
+ return resolveHookProject(cwd, payload.project);
1575
1581
  }
1576
1582
  async function buildLocalPack$1(context, project, dbPath, workingSetPaths = []) {
1577
1583
  const store = new MemoryStore(dbPath);
@@ -2194,7 +2200,8 @@ function formatCodexAdditionalContext(packText, maxChars) {
2194
2200
  return `${CODEMEM_CONTEXT_HEADER}${truncateAdditionalContext(normalized, bodyMaxChars)}`;
2195
2201
  }
2196
2202
  function resolveInjectProject(payload) {
2197
- return resolveHookProject(typeof payload.cwd === "string" ? payload.cwd : null, payload.project);
2203
+ const cwd = typeof payload.cwd === "string" ? payload.cwd : null;
2204
+ return resolveHookProject(cwd, payload.project);
2198
2205
  }
2199
2206
  function buildCodexInjectQuery(prompt, project) {
2200
2207
  return [prompt, project ?? ""].filter((part) => part.trim().length > 0).join(" ").slice(0, 500) || "recent work";
@@ -3232,8 +3239,8 @@ function escapeSqlLikePattern(value) {
3232
3239
  }
3233
3240
  function formatBytes(bytes) {
3234
3241
  if (bytes < 1024) return `${bytes} B`;
3235
- if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
3236
- return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
3242
+ if (bytes < 1048576) return `${(bytes / 1024).toFixed(1)} KB`;
3243
+ return `${(bytes / 1048576).toFixed(1)} MB`;
3237
3244
  }
3238
3245
  function parseOptionalPositiveInt$1(value) {
3239
3246
  if (!value) return void 0;
@@ -3922,8 +3929,9 @@ function resolveCandidateTarget(candidate) {
3922
3929
  path: join(homedir(), ".config", "opencode", "AGENTS.md"),
3923
3930
  display: "~/.config/opencode/AGENTS.md"
3924
3931
  };
3932
+ const root = resolveProjectRoot(process.cwd()) ?? process.cwd();
3925
3933
  return {
3926
- path: join(resolveProjectRoot(process.cwd()) ?? process.cwd(), "AGENTS.md"),
3934
+ path: join(root, "AGENTS.md"),
3927
3935
  display: "AGENTS.md"
3928
3936
  };
3929
3937
  }
@@ -4164,7 +4172,8 @@ function emitStructuredError(errorCode, message) {
4164
4172
  async function runEnqueueRawEvent(opts, dependencies = {}) {
4165
4173
  try {
4166
4174
  const payload = await (dependencies.readPayload ?? readStdinJson)();
4167
- const store = new MemoryStore(resolveDbPath(resolveDbOpt(opts)));
4175
+ const dbPath = resolveDbPath(resolveDbOpt(opts));
4176
+ const store = new MemoryStore(dbPath);
4168
4177
  try {
4169
4178
  ingestRawEvents(store, payload);
4170
4179
  } finally {
@@ -4286,7 +4295,8 @@ var defaultLogger = {
4286
4295
  error: (message) => console.error(`Error: ${message}`)
4287
4296
  };
4288
4297
  function readWorkerSyncConfig(configPath) {
4289
- return readCoordinatorSyncConfig(configPath ? readCodememConfigFileAtPath(configPath) : readCodememConfigFile());
4298
+ const config = configPath ? readCodememConfigFileAtPath(configPath) : readCodememConfigFile();
4299
+ return readCoordinatorSyncConfig(config);
4290
4300
  }
4291
4301
  function createSequentialBackfillCoordinator(store, jobPlans, options) {
4292
4302
  const pollIntervalMs = 1e3;
@@ -4381,7 +4391,7 @@ function startMaintenanceWorkerRuntime(options = {}) {
4381
4391
  } catch (error) {
4382
4392
  logger.warn(`WAL checkpoint failed: ${error instanceof Error ? error.message : String(error)}`);
4383
4393
  }
4384
- }, 300 * 1e3);
4394
+ }, 3e5);
4385
4395
  walCheckpointTimer.unref();
4386
4396
  if (!isEmbeddingDisabled()) runners.push(new VectorModelMigrationRunner({
4387
4397
  dbPath,
@@ -5125,7 +5135,7 @@ function createMemoryExtractionReplayCommand() {
5125
5135
  observerReasoningEffort: opts.reasoningEffort === void 0 ? observerConfig.observerReasoningEffort : opts.reasoningEffort.trim() || null,
5126
5136
  observerReasoningSummary: opts.reasoningSummary === void 0 ? observerConfig.observerReasoningSummary : opts.reasoningSummary.trim() || null,
5127
5137
  observerMaxOutputTokens: maxOutputTokens ?? observerConfig.observerMaxOutputTokens ?? observerConfig.observerMaxTokens,
5128
- observerExplicitConfigKeys: maxOutputTokens === null ? observerConfig.observerExplicitConfigKeys : [...new Set([...observerConfig.observerExplicitConfigKeys ?? [], "observerMaxOutputTokens"])]
5138
+ observerExplicitConfigKeys: maxOutputTokens === null ? observerConfig.observerExplicitConfigKeys : [.../* @__PURE__ */ new Set([...observerConfig.observerExplicitConfigKeys ?? [], "observerMaxOutputTokens"])]
5129
5139
  };
5130
5140
  const observer = new ObserverClient(observerConfigWithOverrides);
5131
5141
  const result = opts.observerTierRouting === true ? await replayBatchExtractionWithTierRouting(resolveDbOpt(opts), observerConfigWithOverrides, {
@@ -5242,7 +5252,7 @@ function createMemoryExtractionBenchmarkCommand() {
5242
5252
  observerReasoningEffort: opts.reasoningEffort === void 0 ? observerConfig.observerReasoningEffort : opts.reasoningEffort.trim() || null,
5243
5253
  observerReasoningSummary: opts.reasoningSummary === void 0 ? observerConfig.observerReasoningSummary : opts.reasoningSummary.trim() || null,
5244
5254
  observerMaxOutputTokens: maxOutputTokens ?? observerConfig.observerMaxOutputTokens ?? observerConfig.observerMaxTokens,
5245
- observerExplicitConfigKeys: maxOutputTokens === null ? observerConfig.observerExplicitConfigKeys : [...new Set([...observerConfig.observerExplicitConfigKeys ?? [], "observerMaxOutputTokens"])]
5255
+ observerExplicitConfigKeys: maxOutputTokens === null ? observerConfig.observerExplicitConfigKeys : [.../* @__PURE__ */ new Set([...observerConfig.observerExplicitConfigKeys ?? [], "observerMaxOutputTokens"])]
5246
5256
  };
5247
5257
  const observer = new ObserverClient(observerConfigWithOverrides);
5248
5258
  const runs = [];
@@ -5584,9 +5594,9 @@ memoryCommand.addCommand(createMemoryRelinkReportCommand());
5584
5594
  memoryCommand.addCommand(createMemoryRelinkPlanCommand());
5585
5595
  //#endregion
5586
5596
  //#region src/commands/pack.ts
5587
- var MAX_INTERNAL_LEDGER_INPUT_BYTES = 16 * 1024;
5597
+ var MAX_INTERNAL_LEDGER_INPUT_BYTES = 16384;
5588
5598
  var INTERNAL_LEDGER_INPUT_TIMEOUT_MS = 1e3;
5589
- var FORBIDDEN_INTERNAL_KEYS = new Set([
5599
+ var FORBIDDEN_INTERNAL_KEYS = /* @__PURE__ */ new Set([
5590
5600
  "body",
5591
5601
  "context",
5592
5602
  "pack",
@@ -5598,7 +5608,7 @@ var FORBIDDEN_INTERNAL_KEYS = new Set([
5598
5608
  "raw_prompt",
5599
5609
  "title"
5600
5610
  ]);
5601
- var INTERNAL_LEDGER_KEYS = new Set([
5611
+ var INTERNAL_LEDGER_KEYS = /* @__PURE__ */ new Set([
5602
5612
  "action",
5603
5613
  "attempt_id",
5604
5614
  "started_at",
@@ -5613,13 +5623,13 @@ var INTERNAL_LEDGER_KEYS = new Set([
5613
5623
  "failure_stage",
5614
5624
  "original_attempt_id"
5615
5625
  ]);
5616
- var INTERNAL_LEDGER_ACTIONS = new Set([
5626
+ var INTERNAL_LEDGER_ACTIONS = /* @__PURE__ */ new Set([
5617
5627
  "record",
5618
5628
  "delivery",
5619
5629
  "cache_reuse"
5620
5630
  ]);
5621
- var INTERNAL_RETRIEVAL_STATUSES = new Set(["skipped", "failed"]);
5622
- var INTERNAL_DELIVERY_STATUSES = new Set([
5631
+ var INTERNAL_RETRIEVAL_STATUSES = /* @__PURE__ */ new Set(["skipped", "failed"]);
5632
+ var INTERNAL_DELIVERY_STATUSES = /* @__PURE__ */ new Set([
5623
5633
  "handed_off",
5624
5634
  "failed",
5625
5635
  "unknown"
@@ -6530,7 +6540,8 @@ async function startForegroundViewer(invocation) {
6530
6540
  const sweeper = new RawEventSweeper(store, { observer });
6531
6541
  sweeper.start();
6532
6542
  const syncAbort = new AbortController();
6533
- const syncConfig = readCoordinatorSyncConfig(invocation.configPath ? readCodememConfigFileAtPath(invocation.configPath) : readCodememConfigFile());
6543
+ const config = invocation.configPath ? readCodememConfigFileAtPath(invocation.configPath) : readCodememConfigFile();
6544
+ const syncConfig = readCoordinatorSyncConfig(config);
6534
6545
  const syncEnabled = syncConfig.syncEnabled;
6535
6546
  const dbPath = resolveDbPath(invocation.dbPath ?? void 0);
6536
6547
  if (!await terminateTrustedMaintenanceWorker(dbPath, {
@@ -6826,29 +6837,39 @@ function migrateLegacyOpencodePlugin() {
6826
6837
  p.log.step("Removed legacy compat lib: ~/.config/opencode/lib/compat.js");
6827
6838
  } catch {}
6828
6839
  }
6829
- /** Detect and upgrade legacy uvx/uv-based MCP entries in OpenCode config. */
6840
+ function isExactStringArray(value, expected) {
6841
+ return Array.isArray(value) && value.length === expected.length && value.every((entry, index) => entry === expected[index]);
6842
+ }
6843
+ /** Detect and upgrade legacy uvx/uv or managed single-package MCP entries in OpenCode config. */
6830
6844
  function migrateLegacyOpencodeMcp(config) {
6831
6845
  const mcpConfig = config.mcp;
6832
6846
  if (!mcpConfig || typeof mcpConfig !== "object") return false;
6833
6847
  const entry = mcpConfig.codemem;
6834
6848
  if (!entry || typeof entry !== "object") return false;
6835
6849
  const command = entry.command;
6836
- if (Array.isArray(command) && command.some((arg) => typeof arg === "string" && (arg === "uvx" || arg === "uv")) || typeof command === "string" && (command === "uvx" || command === "uv")) {
6837
- p.log.step("Upgrading legacy uvx MCP entry to npx");
6850
+ const isLegacyUv = Array.isArray(command) && command.some((arg) => typeof arg === "string" && (arg === "uvx" || arg === "uv")) || typeof command === "string" && (command === "uvx" || command === "uv");
6851
+ const isManagedSinglePackageNpx = isExactStringArray(command, [
6852
+ "npx",
6853
+ "-y",
6854
+ "codemem",
6855
+ "mcp"
6856
+ ]) || isExactStringArray(command, [
6857
+ "npx",
6858
+ "codemem",
6859
+ "mcp"
6860
+ ]);
6861
+ if (isLegacyUv || isManagedSinglePackageNpx) {
6862
+ p.log.step("Upgrading managed MCP entry to the current npm launcher");
6863
+ const launcher = codememMcpLauncher();
6838
6864
  mcpConfig.codemem = {
6839
- type: "local",
6840
- command: [
6841
- "npx",
6842
- "codemem",
6843
- "mcp"
6844
- ],
6845
- enabled: true
6865
+ ...entry,
6866
+ command: [launcher.command, ...launcher.args]
6846
6867
  };
6847
6868
  return true;
6848
6869
  }
6849
6870
  return false;
6850
6871
  }
6851
- /** Detect and upgrade legacy uvx-based MCP entries in Claude settings. */
6872
+ /** Detect and upgrade legacy uvx or managed single-package MCP entries in Claude settings. */
6852
6873
  function migrateLegacyClaudeMcp(settings) {
6853
6874
  const mcpServers = settings.mcpServers;
6854
6875
  if (!mcpServers || typeof mcpServers !== "object") return false;
@@ -6856,15 +6877,19 @@ function migrateLegacyClaudeMcp(settings) {
6856
6877
  if (!entry || typeof entry !== "object") return false;
6857
6878
  const command = entry.command;
6858
6879
  const args = entry.args;
6859
- if (typeof command === "string" && (command === "uvx" || command === "uv") || Array.isArray(args) && args.some((arg) => typeof arg === "string" && (arg.startsWith("codemem==") || arg === "uvx"))) {
6860
- p.log.step("Upgrading legacy uvx Claude MCP entry to npx");
6880
+ const isLegacyUv = typeof command === "string" && (command === "uvx" || command === "uv") || Array.isArray(args) && args.some((arg) => typeof arg === "string" && (arg.startsWith("codemem==") || arg === "uvx"));
6881
+ const isManagedSinglePackageNpx = command === "npx" && isExactStringArray(args, [
6882
+ "-y",
6883
+ "codemem",
6884
+ "mcp"
6885
+ ]);
6886
+ if (isLegacyUv || isManagedSinglePackageNpx) {
6887
+ p.log.step("Upgrading managed Claude MCP entry to the current npm launcher");
6888
+ const launcher = codememMcpLauncher();
6861
6889
  mcpServers.codemem = {
6862
- command: "npx",
6863
- args: [
6864
- "-y",
6865
- "codemem",
6866
- "mcp"
6867
- ]
6890
+ ...entry,
6891
+ command: launcher.command,
6892
+ args: launcher.args
6868
6893
  };
6869
6894
  return true;
6870
6895
  }
@@ -6919,13 +6944,10 @@ function installMcp(force) {
6919
6944
  return true;
6920
6945
  }
6921
6946
  if (!migrated) {
6947
+ const launcher = codememMcpLauncher();
6922
6948
  mcpConfig.codemem = {
6923
6949
  type: "local",
6924
- command: [
6925
- "npx",
6926
- "codemem",
6927
- "mcp"
6928
- ],
6950
+ command: [launcher.command, ...launcher.args],
6929
6951
  enabled: true
6930
6952
  };
6931
6953
  config.mcp = mcpConfig;
@@ -6942,7 +6964,8 @@ function installMcp(force) {
6942
6964
  function isClaudeHooksPluginInstalled() {
6943
6965
  const pluginDir = join(claudeConfigDir(), "plugins", "codemem");
6944
6966
  if (existsSync(pluginDir)) return true;
6945
- if (existsSync(join(pluginDir, "hooks", "hooks.json"))) return true;
6967
+ const hooksJson = join(pluginDir, "hooks", "hooks.json");
6968
+ if (existsSync(hooksJson)) return true;
6946
6969
  return false;
6947
6970
  }
6948
6971
  function installClaudeMcp(force) {
@@ -6959,13 +6982,10 @@ function installClaudeMcp(force) {
6959
6982
  if ("codemem" in mcpServers && !force && !migrated) p.log.info(`Claude MCP entry already exists in ${settingsPath}`);
6960
6983
  else {
6961
6984
  if (!migrated) {
6985
+ const launcher = codememMcpLauncher();
6962
6986
  mcpServers.codemem = {
6963
- command: "npx",
6964
- args: [
6965
- "-y",
6966
- "codemem",
6967
- "mcp"
6968
- ]
6987
+ command: launcher.command,
6988
+ args: launcher.args
6969
6989
  };
6970
6990
  settings.mcpServers = mcpServers;
6971
6991
  }
@@ -6988,25 +7008,47 @@ function installClaudeMcp(force) {
6988
7008
  return true;
6989
7009
  }
6990
7010
  /** The MCP server table appended to Codex config.toml. */
6991
- var CODEX_MCP_BLOCK = [
6992
- "[mcp_servers.codemem]",
6993
- "command = \"npx\"",
6994
- "args = [\"-y\", \"codemem\", \"mcp\"]",
6995
- "startup_timeout_sec = 30",
6996
- "tool_timeout_sec = 60"
6997
- ].join("\n");
7011
+ function codexMcpBlock() {
7012
+ const launcher = codememMcpLauncher();
7013
+ const args = launcher.args.map((arg) => JSON.stringify(arg)).join(", ");
7014
+ return [
7015
+ "[mcp_servers.codemem]",
7016
+ `command = ${JSON.stringify(launcher.command)}`,
7017
+ `args = [${args}]`,
7018
+ "startup_timeout_sec = 30",
7019
+ "tool_timeout_sec = 60"
7020
+ ].join("\n");
7021
+ }
6998
7022
  var CODEX_MCP_TABLE_RE = /^[ \t]*\[[ \t]*mcp_servers[ \t]*\.[ \t]*("?)codemem\1[ \t]*\]/m;
7023
+ function migrateManagedCodexMcp(existing) {
7024
+ const tableMatch = CODEX_MCP_TABLE_RE.exec(existing);
7025
+ if (!tableMatch || tableMatch.index == null) return null;
7026
+ const blockStart = tableMatch.index;
7027
+ const nextTable = /^[ \t]*\[/gm;
7028
+ nextTable.lastIndex = blockStart + tableMatch[0].length;
7029
+ const blockEnd = nextTable.exec(existing)?.index ?? existing.length;
7030
+ const block = existing.slice(blockStart, blockEnd);
7031
+ const commandPattern = /^([ \t]*command[ \t]*=[ \t]*)"npx"([ \t]*(?:#.*)?)$/m;
7032
+ const argsPattern = /^([ \t]*args[ \t]*=[ \t]*)\[[ \t]*"-y"[ \t]*,[ \t]*"codemem"[ \t]*,[ \t]*"mcp"[ \t]*\]([ \t]*(?:#.*)?)$/m;
7033
+ if (!commandPattern.test(block) || !argsPattern.test(block)) return null;
7034
+ const launcher = codememMcpLauncher();
7035
+ const args = launcher.args.map((arg) => JSON.stringify(arg)).join(", ");
7036
+ const migrated = block.replace(commandPattern, `$1${JSON.stringify(launcher.command)}$2`).replace(argsPattern, `$1[${args}]$2`);
7037
+ return `${existing.slice(0, blockStart)}${migrated}${existing.slice(blockEnd)}`;
7038
+ }
6999
7039
  /** Marker substring identifying codemem-owned hook commands. */
7000
7040
  var CODEMEM_HOOK_MARKER = "codemem codex-hook-";
7001
7041
  /**
7002
7042
  * Resolve how Codex hooks should invoke codemem. Prefer a direct `codemem` call
7003
- * when it's on PATH (fast — no per-hook resolution); fall back to `npx -y codemem`
7004
- * only when codemem isn't installed (e.g. setup was run via `npx codemem setup`),
7005
- * so capture/recall still work without a global install. Mirrors the plugin
7006
- * wrapper's `codemem`-first / `npx` fallback model.
7043
+ * when it's on PATH (fast — no per-hook resolution); fall back to an `npx`
7044
+ * invocation that requests both `codemem` and `@codemem/embeddings` when codemem
7045
+ * isn't installed (e.g. setup was run via `npx codemem setup`). The paired
7046
+ * runtime keeps the local-store inject path (codex-hook-inject) able to embed
7047
+ * instead of degrading to FTS. Mirrors the MCP launcher's two-package model.
7007
7048
  */
7008
7049
  function codememCodexHookBase() {
7009
- return codememOnPath() ? "codemem" : "npx -y codemem";
7050
+ if (codememOnPath(true)) return "codemem";
7051
+ return "npx -y --package codemem --package @codemem/embeddings codemem";
7010
7052
  }
7011
7053
  /**
7012
7054
  * Build the codemem-owned hook groups keyed by Codex event name, given the
@@ -7048,28 +7090,127 @@ function isCodememHookGroup(group) {
7048
7090
  return hooks.some((h) => h != null && typeof h === "object" && typeof h.command === "string" && h.command.includes(CODEMEM_HOOK_MARKER));
7049
7091
  }
7050
7092
  /**
7051
- * True if a resolved bin path is a transient npx/dlx cache bin. When setup runs
7052
- * via `npx -y codemem setup --codex-only`, npx exposes this package's bin on PATH for
7053
- * the duration of the run, then removes it — so Codex would later fail to find a
7054
- * bare `codemem`. Such paths must NOT count as "on PATH" for hook command baking.
7093
+ * True if a resolved bin path is a transient/project-local bin that will not be
7094
+ * on PATH for the globally-configured MCP hosts (OpenCode/Claude/Codex).
7095
+ *
7096
+ * - `_npx` / `.pnpm/dlx`: npx/dlx caches exposed only for the duration of a
7097
+ * `npx -y codemem setup` run, then removed.
7098
+ * - `node_modules/.bin`: a project-local install exposed only while setup runs
7099
+ * through `npm exec` / `pnpm exec` / a package script. The global hosts do not
7100
+ * inherit that project-local PATH, so baking a bare `codemem` command would
7101
+ * later fail to resolve.
7102
+ *
7103
+ * Such paths must NOT count as "on PATH" for launcher/hook command baking.
7055
7104
  */
7056
7105
  function isTransientNpxBinPath(resolved) {
7057
- return /[/\\]_npx[/\\]/.test(resolved) || /[/\\]\.pnpm[/\\]dlx[/\\]/.test(resolved);
7106
+ return /[/\\]_npx[/\\]/.test(resolved) || /[/\\]\.pnpm[/\\]dlx[/\\]/.test(resolved) || /[/\\]node_modules[/\\]\.bin[/\\]/.test(resolved);
7107
+ }
7108
+ /** Parse a semver core (major.minor.patch) plus optional prerelease tail. */
7109
+ function parseSetupSemver(version) {
7110
+ const match = /^(\d+)\.(\d+)\.(\d+)(?:-.+)?$/.exec(version.trim());
7111
+ if (!match) return null;
7112
+ return {
7113
+ core: [
7114
+ Number(match[1]),
7115
+ Number(match[2]),
7116
+ Number(match[3])
7117
+ ],
7118
+ prerelease: version.includes("-")
7119
+ };
7120
+ }
7121
+ /**
7122
+ * True when a durable `codemem --version` output should be trusted to launch
7123
+ * `codemem mcp` directly. Mirrors the OpenCode plugin's runner check: accept the
7124
+ * exact build version, or a clean (non-prerelease) release at least as new as
7125
+ * this build. This prevents baking a bare launcher that later resolves to an
7126
+ * older global install sitting alongside the transient npx bin.
7127
+ */
7128
+ function isDurableCodememVersionTrusted(versionOutput) {
7129
+ if (versionOutput === VERSION) return true;
7130
+ const candidate = parseSetupSemver(versionOutput);
7131
+ const current = parseSetupSemver(VERSION);
7132
+ if (!candidate || !current || candidate.prerelease) return false;
7133
+ for (let i = 0; i < 3; i++) {
7134
+ const c = candidate.core[i] ?? 0;
7135
+ const v = current.core[i] ?? 0;
7136
+ if (c > v) return true;
7137
+ if (c < v) return false;
7138
+ }
7139
+ return true;
7058
7140
  }
7059
7141
  /**
7060
7142
  * Detect whether a durable `codemem` resolves on PATH (excluding a transient
7061
- * npx/dlx bin that vanishes after this process exits).
7143
+ * npx/dlx bin that vanishes after this process exits). When `requireTrustedVersion`
7144
+ * is set, the durable binary must also report a version compatible with this
7145
+ * build before it is trusted, so setup never bakes a bare launcher that resolves
7146
+ * to a stale global install.
7062
7147
  */
7063
- function codememOnPath() {
7148
+ function codememOnPath(requireTrustedVersion = false) {
7064
7149
  try {
7065
- const resolved = execFileSync(process.platform === "win32" ? "where" : "which", ["codemem"], { encoding: "utf-8" }).split(/\r?\n/).map((line) => line.trim()).find(Boolean);
7066
- if (!resolved) return false;
7067
- return !isTransientNpxBinPath(resolved);
7150
+ const command = process.platform === "win32" ? "where" : "which";
7151
+ const args = process.platform === "win32" ? ["codemem"] : ["-a", "codemem"];
7152
+ const durable = execFileSync(command, args, { encoding: "utf-8" }).split(/\r?\n/).map((line) => line.trim()).filter(Boolean).filter((candidate) => !isTransientNpxBinPath(candidate));
7153
+ if (durable.length === 0) return false;
7154
+ if (!requireTrustedVersion) return true;
7155
+ const [firstDurable] = durable;
7156
+ if (firstDurable == null) return false;
7157
+ const version = probeDurableCodememVersion(firstDurable);
7158
+ return version != null && isDurableCodememVersionTrusted(version);
7068
7159
  } catch {
7069
7160
  return false;
7070
7161
  }
7071
7162
  }
7072
7163
  /**
7164
+ * Run `<binary> --version`, returning trimmed stdout or null on failure. On
7165
+ * Windows a global npm install resolves to a `.cmd`/`.bat` shim that Node's
7166
+ * shell-less execFileSync cannot execute directly, so invoke it through
7167
+ * `cmd.exe /c` there; POSIX binaries run directly.
7168
+ */
7169
+ function probeDurableCodememVersion(binaryPath) {
7170
+ try {
7171
+ return (process.platform === "win32" ? execFileSync("cmd.exe", [
7172
+ "/c",
7173
+ binaryPath,
7174
+ "--version"
7175
+ ], {
7176
+ encoding: "utf-8",
7177
+ timeout: 3e3,
7178
+ stdio: [
7179
+ "ignore",
7180
+ "pipe",
7181
+ "ignore"
7182
+ ]
7183
+ }) : execFileSync(binaryPath, ["--version"], {
7184
+ encoding: "utf-8",
7185
+ timeout: 3e3,
7186
+ stdio: [
7187
+ "ignore",
7188
+ "pipe",
7189
+ "ignore"
7190
+ ]
7191
+ })).trim();
7192
+ } catch {
7193
+ return null;
7194
+ }
7195
+ }
7196
+ function codememMcpLauncher(onPath = codememOnPath(true)) {
7197
+ return onPath ? {
7198
+ command: "codemem",
7199
+ args: ["mcp"]
7200
+ } : {
7201
+ command: "npx",
7202
+ args: [
7203
+ "-y",
7204
+ "--package",
7205
+ "codemem",
7206
+ "--package",
7207
+ "@codemem/embeddings",
7208
+ "codemem",
7209
+ "mcp"
7210
+ ]
7211
+ };
7212
+ }
7213
+ /**
7073
7214
  * Append the codemem MCP server table to Codex config.toml without rewriting
7074
7215
  * unrelated content. Returns true on success.
7075
7216
  */
@@ -7077,6 +7218,20 @@ function installCodexMcp(codexHome, force) {
7077
7218
  const configPath = join(codexHome, "config.toml");
7078
7219
  const existing = existsSync(configPath) ? readFileSync(configPath, "utf-8") : "";
7079
7220
  if (CODEX_MCP_TABLE_RE.test(existing)) {
7221
+ const migrated = migrateManagedCodexMcp(existing);
7222
+ if (migrated != null) {
7223
+ try {
7224
+ copyFileSync(configPath, `${configPath}.codemem.bak`);
7225
+ } catch {}
7226
+ try {
7227
+ writeFileSync(configPath, migrated, "utf-8");
7228
+ p.log.success(`Codex MCP entry upgraded: ${configPath}`);
7229
+ return true;
7230
+ } catch (err) {
7231
+ p.log.error(`Failed to write ${configPath}: ${err instanceof Error ? err.message : String(err)}`);
7232
+ return false;
7233
+ }
7234
+ }
7080
7235
  if (force) p.log.info(`Codex MCP entry already exists in ${configPath} — left as-is (TOML is not rewritten in place)`);
7081
7236
  else p.log.info(`Codex MCP entry already exists in ${configPath}`);
7082
7237
  return true;
@@ -7086,7 +7241,7 @@ function installCodexMcp(codexHome, force) {
7086
7241
  } catch {}
7087
7242
  let next = existing;
7088
7243
  if (next.length > 0 && !next.endsWith("\n\n")) next += next.endsWith("\n") ? "\n" : "\n\n";
7089
- next += `${CODEX_MCP_BLOCK}\n`;
7244
+ next += `${codexMcpBlock()}\n`;
7090
7245
  try {
7091
7246
  writeFileSync(configPath, next, "utf-8");
7092
7247
  p.log.success(`Codex MCP entry installed: ${configPath}`);
@@ -7113,12 +7268,38 @@ function installCodexHooks(codexHome, force) {
7113
7268
  let hooks = config.hooks;
7114
7269
  if (hooks == null || typeof hooks !== "object" || Array.isArray(hooks)) hooks = {};
7115
7270
  const ours = buildCodememCodexHookGroups(codememCodexHookBase());
7271
+ const currentCommands = new Set(Object.values(ours).flatMap((groups) => groups.flatMap((group) => group.hooks.map((hook) => hook.command))));
7272
+ const groupNeedsMigration = (group) => {
7273
+ if (!isCodememHookGroup(group)) return false;
7274
+ const hooksList = group.hooks;
7275
+ if (!Array.isArray(hooksList)) return false;
7276
+ return hooksList.some((hook) => {
7277
+ if (hook == null || typeof hook !== "object") return false;
7278
+ const command = hook.command;
7279
+ if (typeof command !== "string") return false;
7280
+ return command.includes(CODEMEM_HOOK_MARKER) && !currentCommands.has(command);
7281
+ });
7282
+ };
7116
7283
  let changed = false;
7284
+ const isCodememOwnedHook = (hook) => hook != null && typeof hook === "object" && typeof hook.command === "string" && hook.command.includes(CODEMEM_HOOK_MARKER);
7285
+ const stripCodememHooks = (group) => {
7286
+ if (!isCodememHookGroup(group)) return group;
7287
+ const hooksList = group.hooks;
7288
+ if (!Array.isArray(hooksList)) return null;
7289
+ const preservedHooks = hooksList.filter((hook) => !isCodememOwnedHook(hook));
7290
+ if (preservedHooks.length === 0) return null;
7291
+ return {
7292
+ ...group,
7293
+ hooks: preservedHooks
7294
+ };
7295
+ };
7117
7296
  for (const [event, ourGroups] of Object.entries(ours)) {
7118
7297
  const current = hooks[event];
7119
7298
  const existingGroups = Array.isArray(current) ? [...current] : [];
7120
- if (existingGroups.some(isCodememHookGroup) && !force) continue;
7121
- const preserved = existingGroups.filter((g) => !isCodememHookGroup(g));
7299
+ const hasCodemem = existingGroups.some(isCodememHookGroup);
7300
+ const needsMigration = existingGroups.some(groupNeedsMigration);
7301
+ if (hasCodemem && !force && !needsMigration) continue;
7302
+ const preserved = existingGroups.map((group) => stripCodememHooks(group)).filter((group) => group != null);
7122
7303
  hooks[event] = [...preserved, ...ourGroups];
7123
7304
  changed = true;
7124
7305
  }
@@ -7154,8 +7335,11 @@ function installCodex(force) {
7154
7335
  p.log.error(`Failed to create Codex home ${codexHome}: ${err instanceof Error ? err.message : String(err)}`);
7155
7336
  return false;
7156
7337
  }
7157
- if (codememOnPath()) p.log.info("Codex hooks will call `codemem` directly (found on PATH).");
7158
- else p.log.info("`codemem` is not on PATH, so Codex hooks will run via `npx -y codemem` (works without a global install). For lower hook latency: npm i -g codemem");
7338
+ if (codememOnPath(true)) p.log.info("Codex hooks will call `codemem` directly (found on PATH).");
7339
+ else {
7340
+ const globalInstallCommand = process.platform === "linux" ? "env ONNXRUNTIME_NODE_INSTALL=skip npm install -g codemem @codemem/embeddings" : "npm install -g codemem @codemem/embeddings";
7341
+ p.log.info(`\`codemem\` is not on PATH, so Codex hooks will run via \`npx -y --package codemem --package @codemem/embeddings codemem\` (works without a global install). For lower hook latency: ${globalInstallCommand}`);
7342
+ }
7159
7343
  let ok = true;
7160
7344
  ok = installCodexMcp(codexHome, force) && ok;
7161
7345
  ok = installCodexHooks(codexHome, force) && ok;
@@ -7285,7 +7469,7 @@ var statsCommand = statsCmd.action((opts) => {
7285
7469
  //#region src/commands/status.ts
7286
7470
  var MAX_ATTENTION = 20;
7287
7471
  var DEFAULT_VIEWER_PORT = 38888;
7288
- var RECENT_FAILURE_WINDOW_MS = 1440 * 60 * 1e3;
7472
+ var RECENT_FAILURE_WINDOW_MS = 864e5;
7289
7473
  var OBSERVER_IDENTITY_KEYS = [
7290
7474
  "observer_provider",
7291
7475
  "observer_model",
@@ -8432,7 +8616,8 @@ bootstrapCmd.action(async (peerArg, opts) => {
8432
8616
  process.exitCode = 2;
8433
8617
  return;
8434
8618
  }
8435
- const store = new MemoryStore(resolveDbPath(resolveDbOpt(opts)));
8619
+ const dbPath = resolveDbPath(resolveDbOpt(opts));
8620
+ const store = new MemoryStore(dbPath);
8436
8621
  try {
8437
8622
  const pageSize = Math.max(1, Number.parseInt(opts.pageSize, 10) || 2e3);
8438
8623
  const keysDir = opts.keysDir ?? void 0;
@@ -8603,7 +8788,7 @@ syncCoordinatorAlias.hook("preAction", (_thisCmd, actionCmd) => {
8603
8788
  syncCommand.addCommand(syncCoordinatorAlias, { hidden: true });
8604
8789
  //#endregion
8605
8790
  //#region src/commands/update.ts
8606
- var INSTALL_TIMEOUT_MS = 300 * 1e3;
8791
+ var INSTALL_TIMEOUT_MS = 42e4;
8607
8792
  var VERIFY_TIMEOUT_MS = 3e4;
8608
8793
  var PUBLIC_NPM_REGISTRY = "https://registry.npmjs.org/";
8609
8794
  var UPDATE_LOCK_FILE = "update-install.lock";
@@ -8625,7 +8810,7 @@ async function removeStaleInstallLock(lockPath) {
8625
8810
  const pid = Number.parseInt(rawPid.trim(), 10);
8626
8811
  if (Number.isSafeInteger(pid) && pid > 0) {
8627
8812
  if (isProcessAlive(pid)) return false;
8628
- } else if (Date.now() - lockStat.mtimeMs <= 33e4) return false;
8813
+ } else if (Date.now() - lockStat.mtimeMs <= 45e4) return false;
8629
8814
  await unlink(lockPath);
8630
8815
  return true;
8631
8816
  } catch {
@@ -8656,6 +8841,7 @@ function runCommand(command, args, timeoutMs, options = {}) {
8656
8841
  const child = spawn(command, args, {
8657
8842
  cwd: options.cwd,
8658
8843
  detached: process.platform !== "win32",
8844
+ env: options.env,
8659
8845
  shell: false,
8660
8846
  stdio: [
8661
8847
  "ignore",
@@ -8789,12 +8975,13 @@ function renderHumanStatus(status) {
8789
8975
  }
8790
8976
  var checkCommand = addJsonOption(new Command("check").description("Check for a newer stable codemem release")).addOption(new Option("-r, --refresh", "bypass the six-hour release cache")).configureHelp(helpStyle).action(async (options) => {
8791
8977
  try {
8978
+ const installKind = detectInstallKind({
8979
+ entryPath: process.argv[1] ?? "",
8980
+ env: process.env
8981
+ });
8792
8982
  const status = await getUpdateStatus({
8793
8983
  currentVersion: VERSION,
8794
- installKind: detectInstallKind({
8795
- entryPath: process.argv[1] ?? "",
8796
- env: process.env
8797
- }),
8984
+ installKind,
8798
8985
  refresh: options.refresh
8799
8986
  });
8800
8987
  if (status.latest_version === null) {
@@ -8820,15 +9007,29 @@ var checkCommand = addJsonOption(new Command("check").description("Check for a n
8820
9007
  }
8821
9008
  }
8822
9009
  });
8823
- var installCommand = addJsonOption(new Command("install").description("Install an eligible stable codemem update")).configureHelp(helpStyle).action(async (options) => {
9010
+ function updateInstallArgs(npmArgs, targetVersion) {
9011
+ const installArgs = [
9012
+ "install",
9013
+ "-g",
9014
+ "--registry",
9015
+ PUBLIC_NPM_REGISTRY,
9016
+ `--@codemem:registry=${PUBLIC_NPM_REGISTRY}`,
9017
+ `codemem@${targetVersion}`,
9018
+ `@codemem/embeddings@${targetVersion}`
9019
+ ];
9020
+ if (process.platform !== "win32") return installArgs;
9021
+ return [...npmArgs.slice(0, 3), windowsCommandLine(npmArgs[3] ?? "", installArgs)];
9022
+ }
9023
+ async function installUpdate(options) {
8824
9024
  let releaseInstallLock = null;
8825
9025
  try {
9026
+ const installKind = detectInstallKind({
9027
+ entryPath: process.argv[1] ?? "",
9028
+ env: process.env
9029
+ });
8826
9030
  const status = await getUpdateStatus({
8827
9031
  currentVersion: VERSION,
8828
- installKind: detectInstallKind({
8829
- entryPath: process.argv[1] ?? "",
8830
- env: process.env
8831
- }),
9032
+ installKind,
8832
9033
  refresh: true
8833
9034
  });
8834
9035
  if (!status.auto_update_eligible || !status.latest_version) {
@@ -8842,20 +9043,12 @@ var installCommand = addJsonOption(new Command("install").description("Install a
8842
9043
  }
8843
9044
  releaseInstallLock = await acquireInstallLock();
8844
9045
  const npm = await resolveInstallCommand();
8845
- const installation = await runCommand(npm.command, process.platform === "win32" ? [...npm.args.slice(0, 3), windowsCommandLine(npm.args[3] ?? "", [
8846
- "install",
8847
- "-g",
8848
- "--registry",
8849
- PUBLIC_NPM_REGISTRY,
8850
- `codemem@${targetVersion}`
8851
- ])] : [
8852
- "install",
8853
- "-g",
8854
- "--registry",
8855
- PUBLIC_NPM_REGISTRY,
8856
- `codemem@${targetVersion}`
8857
- ], INSTALL_TIMEOUT_MS, {
9046
+ const installation = await runCommand(npm.command, updateInstallArgs(npm.args, targetVersion), INSTALL_TIMEOUT_MS, {
8858
9047
  cwd: npm.cwd,
9048
+ env: process.platform === "linux" ? {
9049
+ ...process.env,
9050
+ ONNXRUNTIME_NODE_INSTALL: "skip"
9051
+ } : void 0,
8859
9052
  windowsVerbatimArguments: process.platform === "win32"
8860
9053
  });
8861
9054
  if (installation.exitCode !== 0) {
@@ -8882,8 +9075,9 @@ var installCommand = addJsonOption(new Command("install").description("Install a
8882
9075
  } finally {
8883
9076
  await releaseInstallLock?.();
8884
9077
  }
8885
- });
8886
- var updateCommand = new Command("update").description("Inspect and manage codemem updates").configureHelp(helpStyle).addCommand(checkCommand).addCommand(installCommand);
9078
+ }
9079
+ var installCommand = addJsonOption(new Command("install").description("Install an eligible stable codemem update")).configureHelp(helpStyle).action(installUpdate);
9080
+ var updateCommand = new Command("update").description("Inspect and manage codemem updates").enablePositionalOptions().configureHelp(helpStyle).addCommand(checkCommand).addCommand(installCommand);
8887
9081
  //#endregion
8888
9082
  //#region src/commands/version.ts
8889
9083
  var versionCommand = new Command("version").configureHelp(helpStyle).description("Print codemem version").action(() => {