codemem 0.43.2 → 0.44.0-beta.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
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { DEDUP_KEY_BACKFILL_JOB, DEFAULT_COORDINATOR_DB_PATH, DedupKeyBackfillRunner, DeviceIdentityError, MUTATING_TOOL_NAMES, MemoryStore, ObserverClient, PROMPT_TRANSPORT_PROTOCOL_RANGE, REF_BACKFILL_JOB, RawEventIngestValidationError, RawEventSweeper, RefBackfillRunner, SCOPE_BACKFILL_JOB, SESSION_CONTEXT_BACKFILL_JOB, SUMMARY_DEDUP_BACKFILL_JOB, ScopeBackfillRunner, SessionContextBackfillRunner, SummaryDedupBackfillRunner, SyncRetentionRunner, TRUSTED_HOOK_MAPPER_OPTIONS, VERSION, VectorModelMigrationRunner, aiBackfillStructuredContent, applyBootstrapSnapshot, applyDistillRule, arePromptTransportProtocolRangesCompatible, backfillMemoryDedupKeys, backfillNarrativeFromBody, backfillTagsText, backfillVectors, buildBaseUrl, buildDirectPeerAuthHeaders, buildDistillReport, buildRawEventEnvelopeFromCodexHook, buildRawEventEnvelopeFromHook, buildViewerIdentityTarget, classifyPromptTransportFailure, clonePromptPackAttempt, collectOperationalStatus, compareMemoryRoleReports, connect, connectReadOnly, coordinatorCreateGroupAction, coordinatorCreateInviteAction, coordinatorCreateScopeAction, coordinatorDisableDeviceAction, coordinatorEnrollDeviceAction, coordinatorGrantScopeMembershipAction, coordinatorImportInviteAction, coordinatorListBootstrapGrantsAction, coordinatorListDevicesAction, coordinatorListGroupsAction, coordinatorListJoinRequestsAction, coordinatorListScopeMembershipsAction, coordinatorListScopesAction, coordinatorRemoveDeviceAction, coordinatorRenameDeviceAction, coordinatorReviewJoinRequestAction, coordinatorRevokeBootstrapGrantAction, coordinatorRevokeScopeMembershipAction, coordinatorUpdateScopeAction, createBetterSqliteCoordinatorApp, deactivateLowSignalMemories, deactivateLowSignalObservations, dedupNearDuplicateMemories, detectInstallKind, draftDistillRule, ensureDeviceIdentity, ensureSchemaBootstrapped, estimateExtractionModelCost, exportMemories, extractApplyPatchPaths, fetchAllSnapshotPages, fingerprintPublicKey, flushRawEvents, formatHostPort, getAttributionDiagnostics, getExtractionBenchmarkProfile, getExtractionModelPricing, getInjectionEvalScenarioPack, getInjectionEvalScenarioPrompts, getMaintenanceJob, getMemoryArtifactReport, getMemoryRoleReport, getRawEventRelinkPlan, getRawEventRelinkReport, getRawEventStatus, getSemanticIndexDiagnostics, getSessionExtractionEval, getSessionExtractionEvalScenario, getUpdateStatus, getWorkspaceCodememConfigPath, hasPendingDedupKeyBackfill, hasPendingRefBackfill, hasPendingScopeBackfill, hasPendingSessionContextBackfill, hasPendingSummaryDedupBackfill, hasUnsyncedSharedMemoryChanges, importMemories, ingestRawEvents, initDatabase, isEmbeddingDisabled, isStableReleaseVersion, judgeDistillReport, listMaintenanceJobs, listPerPeerScopeSyncState, listRetentionScopeIds, loadObserverConfig, loadPublicKey, loadSqliteVec, mdnsEnabled, normalizePromptTransportProtocolRange, planReplicationOpsAgePrune, probeCodememViewerLiveness, probeCodememViewerLiveness as probeCodememViewerLiveness$1, projectMatchesFilter, promptPackArtifactFingerprint, pruneReplicationOpsUntilCaughtUp, rawEventsGate, readCodememConfigFile, readCodememConfigFileAtPath, readCoordinatorSyncConfig, readImportPayload, recordPromptPackArtifacts, recordPromptPackTerminal, recordRetrievalSurface, renderUnifiedDiff, replayBatchExtraction, replayBatchExtractionWithTierRouting, requestJson, resolveCodememConfigPath, resolveDbPath, resolveHookProject, resolveProject, resolveProjectRoot, resolveRetrievalSession, retryRawEventFailures, runSyncDaemon, runSyncPass, scanSecretsRetroactive, schema, scoreExtractionBenchmarkOutput, setPeerProjectFilter, stripJsonComments, stripTrailingCommas, syncPassPreflight, tryUpdateRetrievalDelivery, updatePeerAddresses, vacuumDatabase, viewerUrl, writeCodememConfigFile } from "@codemem/core";
2
+ import { DEDUP_KEY_BACKFILL_JOB, DEFAULT_COORDINATOR_DB_PATH, DedupKeyBackfillRunner, DeviceIdentityError, MUTATING_TOOL_NAMES, MemoryStore, ObserverClient, PROMPT_TRANSPORT_PROTOCOL_RANGE, REF_BACKFILL_JOB, RawEventIngestValidationError, RawEventSweeper, RefBackfillRunner, SCOPE_BACKFILL_JOB, SESSION_CONTEXT_BACKFILL_JOB, SUMMARY_DEDUP_BACKFILL_JOB, ScopeBackfillRunner, SessionContextBackfillRunner, SummaryDedupBackfillRunner, SyncRetentionRunner, TRUSTED_HOOK_MAPPER_OPTIONS, VERSION, VectorModelMigrationRunner, aiBackfillStructuredContent, applyBootstrapSnapshot, applyDistillRule, arePromptTransportProtocolRangesCompatible, backfillMemoryDedupKeys, backfillNarrativeFromBody, backfillTagsText, backfillVectors, buildBaseUrl, buildDirectPeerAuthHeaders, buildDistillReport, buildRawEventEnvelopeFromCodexHook, buildRawEventEnvelopeFromHook, buildViewerIdentityTarget, classifyPromptTransportFailure, clonePromptPackAttempt, collectOperationalStatus, compareMemoryRoleReports, connect, connectReadOnly, coordinatorCreateGroupAction, coordinatorCreateInviteAction, coordinatorCreateScopeAction, coordinatorDisableDeviceAction, coordinatorEnrollDeviceAction, coordinatorGrantScopeMembershipAction, coordinatorImportInviteAction, coordinatorListBootstrapGrantsAction, coordinatorListDevicesAction, coordinatorListGroupsAction, coordinatorListJoinRequestsAction, coordinatorListScopeMembershipsAction, coordinatorListScopesAction, coordinatorRemoveDeviceAction, coordinatorRenameDeviceAction, coordinatorReviewJoinRequestAction, coordinatorRevokeBootstrapGrantAction, coordinatorRevokeScopeMembershipAction, coordinatorUpdateScopeAction, createBetterSqliteCoordinatorApp, deactivateLowSignalMemories, deactivateLowSignalObservations, dedupNearDuplicateMemories, detectInstallKind, draftDistillRule, ensureDeviceIdentity, ensureSchemaBootstrapped, estimateExtractionModelCost, exportMemories, extractApplyPatchPaths, fetchAllSnapshotPages, fingerprintPublicKey, flushRawEvents, formatHostPort, getAttributionDiagnostics, getExtractionBenchmarkProfile, getExtractionModelPricing, getInjectionEvalScenarioPack, getInjectionEvalScenarioPrompts, getMaintenanceJob, getMemoryArtifactReport, getMemoryRoleReport, getRawEventRelinkPlan, getRawEventRelinkReport, getRawEventStatus, getSemanticIndexDiagnostics, getSessionExtractionEval, getSessionExtractionEvalScenario, getUpdateStatus, getWorkspaceCodememConfigPath, hasPendingDedupKeyBackfill, hasPendingRefBackfill, hasPendingScopeBackfill, hasPendingSessionContextBackfill, hasPendingSummaryDedupBackfill, hasUnsyncedSharedMemoryChanges, importMemories, ingestRawEvents, initDatabase, isEmbeddingDisabled, isReleaseVersionForChannel, judgeDistillReport, listMaintenanceJobs, listPerPeerScopeSyncState, listRetentionScopeIds, loadObserverConfig, loadPublicKey, loadSqliteVec, mdnsEnabled, normalizePromptTransportProtocolRange, planReplicationOpsAgePrune, probeCodememViewerLiveness, probeCodememViewerLiveness as probeCodememViewerLiveness$1, probeRequiredNativeRuntime, projectMatchesFilter, promptPackArtifactFingerprint, pruneReplicationOpsUntilCaughtUp, rawEventsGate, readCodememConfigFile, readCodememConfigFileAtPath, readCoordinatorSyncConfig, readImportPayload, recordPromptPackArtifacts, recordPromptPackTerminal, recordRetrievalSurface, renderUnifiedDiff, replayBatchExtraction, replayBatchExtractionWithTierRouting, requestJson, resolveCodememConfigPath, resolveDbPath, resolveHookProject, resolveProject, resolveProjectRoot, resolveRetrievalSession, retryRawEventFailures, runSyncDaemon, runSyncPass, scanSecretsRetroactive, schema, scoreExtractionBenchmarkOutput, setPeerProjectFilter, stripJsonComments, stripTrailingCommas, syncPassPreflight, tryUpdateRetrievalDelivery, updatePeerAddresses, vacuumDatabase, viewerUrl, writeCodememConfigFile } from "@codemem/core";
3
3
  import { Command, Option } from "commander";
4
4
  import omelette from "omelette";
5
5
  import { randomInt, randomUUID } from "node:crypto";
@@ -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,13 +4391,13 @@ 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,
4388
4398
  signal: options.signal,
4389
4399
  batchSize: 10,
4390
- idleIntervalMs: 5e3
4400
+ idleIntervalMs: 6e4
4391
4401
  }));
4392
4402
  runners.push(createSequentialBackfillCoordinator(store, [
4393
4403
  {
@@ -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"
@@ -6450,6 +6460,51 @@ function sqliteVecFailureDiagnostics(error, dbPath) {
6450
6460
  `error=${message}`
6451
6461
  ];
6452
6462
  }
6463
+ function requiredNativeRuntimeFailureDiagnostics(error, context = {
6464
+ version: VERSION,
6465
+ nodeVersion: process.version,
6466
+ platform: process.platform,
6467
+ arch: process.arch
6468
+ }) {
6469
+ const detail = error instanceof Error ? error.message : String(error);
6470
+ return [
6471
+ "Required SQLite native binding is unavailable.",
6472
+ `Runtime: Node ${context.nodeVersion} on ${context.platform}/${context.arch}.`,
6473
+ `Details: ${detail}`,
6474
+ `Reinstall this exact codemem version: npm install -g codemem@${context.version}`,
6475
+ "Use Node 24.15+ on a supported 64-bit macOS, Linux, or Windows target; see the native install matrix for current platform coverage.",
6476
+ "This required better-sqlite3 failure is separate from optional sqlite-vec loading, which can fall back to lexical search."
6477
+ ];
6478
+ }
6479
+ async function preflightServeNativeRuntime(invocation, dependencies = {
6480
+ isPortOpen,
6481
+ probe: probeRequiredNativeRuntime
6482
+ }) {
6483
+ if (invocation.mode === "start" && await dependencies.isPortOpen(invocation.host, invocation.port)) return { state: "already_running" };
6484
+ try {
6485
+ dependencies.probe();
6486
+ return { state: "ready" };
6487
+ } catch (error) {
6488
+ return {
6489
+ state: "unavailable",
6490
+ diagnostics: requiredNativeRuntimeFailureDiagnostics(error)
6491
+ };
6492
+ }
6493
+ }
6494
+ async function nativeRuntimeAllowsServeStart(invocation) {
6495
+ if (invocation.mode !== "start" && invocation.mode !== "restart") return true;
6496
+ const preflight = await preflightServeNativeRuntime(invocation);
6497
+ if (preflight.state === "already_running") {
6498
+ p.log.warn(`Viewer already running at http://${invocation.host}:${invocation.port}`);
6499
+ if (!invocation.background) process.exitCode = 1;
6500
+ return false;
6501
+ }
6502
+ if (preflight.state === "ready") return true;
6503
+ p.intro("codemem viewer");
6504
+ for (const line of preflight.diagnostics) p.log.error(line);
6505
+ process.exitCode = 1;
6506
+ return false;
6507
+ }
6453
6508
  async function runServeCoordinatorMaintenance(store, dependencies) {
6454
6509
  const projectShares = await dependencies.advancePendingProjectShares(store, { limit: 3 });
6455
6510
  const coordinatorEnrollment = await dependencies.reconcileConfiguredCoordinatorEnrollment(store);
@@ -6508,11 +6563,11 @@ async function startForegroundViewer(invocation) {
6508
6563
  if (invocation.dbPath) process.env.CODEMEM_DB = invocation.dbPath;
6509
6564
  if (invocation.configPath) process.env.CODEMEM_CONFIG = invocation.configPath;
6510
6565
  warnIfViewerExposed(invocation.host, invocation.port);
6511
- if (await isPortOpen(invocation.host, invocation.port)) {
6512
- p.log.warn(`Viewer already running at http://${invocation.host}:${invocation.port}`);
6513
- process.exitCode = 1;
6514
- return;
6515
- }
6566
+ const dbPath = resolveDbPath(invocation.dbPath ?? void 0);
6567
+ if (!await terminateTrustedMaintenanceWorker(dbPath, {
6568
+ gracefulMs: 1e3,
6569
+ forceMs: 5e3
6570
+ })) p.log.warn("Existing maintenance worker is not trusted or did not stop; starting viewer anyway");
6516
6571
  const preparedDb = prepareViewerDatabase(invocation.dbPath);
6517
6572
  const observer = new ObserverClient();
6518
6573
  let store;
@@ -6530,13 +6585,9 @@ async function startForegroundViewer(invocation) {
6530
6585
  const sweeper = new RawEventSweeper(store, { observer });
6531
6586
  sweeper.start();
6532
6587
  const syncAbort = new AbortController();
6533
- const syncConfig = readCoordinatorSyncConfig(invocation.configPath ? readCodememConfigFileAtPath(invocation.configPath) : readCodememConfigFile());
6588
+ const config = invocation.configPath ? readCodememConfigFileAtPath(invocation.configPath) : readCodememConfigFile();
6589
+ const syncConfig = readCoordinatorSyncConfig(config);
6534
6590
  const syncEnabled = syncConfig.syncEnabled;
6535
- const dbPath = resolveDbPath(invocation.dbPath ?? void 0);
6536
- if (!await terminateTrustedMaintenanceWorker(dbPath, {
6537
- gracefulMs: 1e3,
6538
- forceMs: 5e3
6539
- })) p.log.warn("Existing maintenance worker is not trusted or did not stop; starting viewer anyway");
6540
6591
  const syncRuntimeStatus = {
6541
6592
  phase: syncEnabled ? "starting" : "disabled",
6542
6593
  detail: syncEnabled ? "Waiting for viewer startup to finish" : "Sync is disabled"
@@ -6672,7 +6723,10 @@ async function startForegroundViewer(invocation) {
6672
6723
  shutdown();
6673
6724
  });
6674
6725
  }
6675
- async function runServeInvocation(invocation) {
6726
+ async function runServeInvocation(invocation, dependencies = {
6727
+ nativeRuntimeAllowsServeStart,
6728
+ stopExistingViewer
6729
+ }) {
6676
6730
  const dbPath = resolveDbPath(invocation.dbPath ?? void 0);
6677
6731
  const runtimeConflict = await findRuntimeViewerConflict(dbPath, {
6678
6732
  host: invocation.host,
@@ -6685,8 +6739,9 @@ async function runServeInvocation(invocation) {
6685
6739
  process.exitCode = 1;
6686
6740
  return;
6687
6741
  }
6742
+ if (!await dependencies.nativeRuntimeAllowsServeStart(invocation)) return;
6688
6743
  if (invocation.mode === "stop" || invocation.mode === "restart") {
6689
- const result = await stopExistingViewer(dbPath, {
6744
+ const result = await dependencies.stopExistingViewer(dbPath, {
6690
6745
  host: invocation.host,
6691
6746
  port: invocation.port
6692
6747
  });
@@ -6826,29 +6881,39 @@ function migrateLegacyOpencodePlugin() {
6826
6881
  p.log.step("Removed legacy compat lib: ~/.config/opencode/lib/compat.js");
6827
6882
  } catch {}
6828
6883
  }
6829
- /** Detect and upgrade legacy uvx/uv-based MCP entries in OpenCode config. */
6884
+ function isExactStringArray(value, expected) {
6885
+ return Array.isArray(value) && value.length === expected.length && value.every((entry, index) => entry === expected[index]);
6886
+ }
6887
+ /** Detect and upgrade legacy uvx/uv or managed single-package MCP entries in OpenCode config. */
6830
6888
  function migrateLegacyOpencodeMcp(config) {
6831
6889
  const mcpConfig = config.mcp;
6832
6890
  if (!mcpConfig || typeof mcpConfig !== "object") return false;
6833
6891
  const entry = mcpConfig.codemem;
6834
6892
  if (!entry || typeof entry !== "object") return false;
6835
6893
  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");
6894
+ const isLegacyUv = Array.isArray(command) && command.some((arg) => typeof arg === "string" && (arg === "uvx" || arg === "uv")) || typeof command === "string" && (command === "uvx" || command === "uv");
6895
+ const isManagedSinglePackageNpx = isExactStringArray(command, [
6896
+ "npx",
6897
+ "-y",
6898
+ "codemem",
6899
+ "mcp"
6900
+ ]) || isExactStringArray(command, [
6901
+ "npx",
6902
+ "codemem",
6903
+ "mcp"
6904
+ ]);
6905
+ if (isLegacyUv || isManagedSinglePackageNpx) {
6906
+ p.log.step("Upgrading managed MCP entry to the current npm launcher");
6907
+ const launcher = codememMcpLauncher();
6838
6908
  mcpConfig.codemem = {
6839
- type: "local",
6840
- command: [
6841
- "npx",
6842
- "codemem",
6843
- "mcp"
6844
- ],
6845
- enabled: true
6909
+ ...entry,
6910
+ command: [launcher.command, ...launcher.args]
6846
6911
  };
6847
6912
  return true;
6848
6913
  }
6849
6914
  return false;
6850
6915
  }
6851
- /** Detect and upgrade legacy uvx-based MCP entries in Claude settings. */
6916
+ /** Detect and upgrade legacy uvx or managed single-package MCP entries in Claude settings. */
6852
6917
  function migrateLegacyClaudeMcp(settings) {
6853
6918
  const mcpServers = settings.mcpServers;
6854
6919
  if (!mcpServers || typeof mcpServers !== "object") return false;
@@ -6856,15 +6921,19 @@ function migrateLegacyClaudeMcp(settings) {
6856
6921
  if (!entry || typeof entry !== "object") return false;
6857
6922
  const command = entry.command;
6858
6923
  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");
6924
+ const isLegacyUv = typeof command === "string" && (command === "uvx" || command === "uv") || Array.isArray(args) && args.some((arg) => typeof arg === "string" && (arg.startsWith("codemem==") || arg === "uvx"));
6925
+ const isManagedSinglePackageNpx = command === "npx" && isExactStringArray(args, [
6926
+ "-y",
6927
+ "codemem",
6928
+ "mcp"
6929
+ ]);
6930
+ if (isLegacyUv || isManagedSinglePackageNpx) {
6931
+ p.log.step("Upgrading managed Claude MCP entry to the current npm launcher");
6932
+ const launcher = codememMcpLauncher();
6861
6933
  mcpServers.codemem = {
6862
- command: "npx",
6863
- args: [
6864
- "-y",
6865
- "codemem",
6866
- "mcp"
6867
- ]
6934
+ ...entry,
6935
+ command: launcher.command,
6936
+ args: launcher.args
6868
6937
  };
6869
6938
  return true;
6870
6939
  }
@@ -6919,13 +6988,10 @@ function installMcp(force) {
6919
6988
  return true;
6920
6989
  }
6921
6990
  if (!migrated) {
6991
+ const launcher = codememMcpLauncher();
6922
6992
  mcpConfig.codemem = {
6923
6993
  type: "local",
6924
- command: [
6925
- "npx",
6926
- "codemem",
6927
- "mcp"
6928
- ],
6994
+ command: [launcher.command, ...launcher.args],
6929
6995
  enabled: true
6930
6996
  };
6931
6997
  config.mcp = mcpConfig;
@@ -6942,7 +7008,8 @@ function installMcp(force) {
6942
7008
  function isClaudeHooksPluginInstalled() {
6943
7009
  const pluginDir = join(claudeConfigDir(), "plugins", "codemem");
6944
7010
  if (existsSync(pluginDir)) return true;
6945
- if (existsSync(join(pluginDir, "hooks", "hooks.json"))) return true;
7011
+ const hooksJson = join(pluginDir, "hooks", "hooks.json");
7012
+ if (existsSync(hooksJson)) return true;
6946
7013
  return false;
6947
7014
  }
6948
7015
  function installClaudeMcp(force) {
@@ -6959,13 +7026,10 @@ function installClaudeMcp(force) {
6959
7026
  if ("codemem" in mcpServers && !force && !migrated) p.log.info(`Claude MCP entry already exists in ${settingsPath}`);
6960
7027
  else {
6961
7028
  if (!migrated) {
7029
+ const launcher = codememMcpLauncher();
6962
7030
  mcpServers.codemem = {
6963
- command: "npx",
6964
- args: [
6965
- "-y",
6966
- "codemem",
6967
- "mcp"
6968
- ]
7031
+ command: launcher.command,
7032
+ args: launcher.args
6969
7033
  };
6970
7034
  settings.mcpServers = mcpServers;
6971
7035
  }
@@ -6988,25 +7052,47 @@ function installClaudeMcp(force) {
6988
7052
  return true;
6989
7053
  }
6990
7054
  /** 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");
7055
+ function codexMcpBlock() {
7056
+ const launcher = codememMcpLauncher();
7057
+ const args = launcher.args.map((arg) => JSON.stringify(arg)).join(", ");
7058
+ return [
7059
+ "[mcp_servers.codemem]",
7060
+ `command = ${JSON.stringify(launcher.command)}`,
7061
+ `args = [${args}]`,
7062
+ "startup_timeout_sec = 30",
7063
+ "tool_timeout_sec = 60"
7064
+ ].join("\n");
7065
+ }
6998
7066
  var CODEX_MCP_TABLE_RE = /^[ \t]*\[[ \t]*mcp_servers[ \t]*\.[ \t]*("?)codemem\1[ \t]*\]/m;
7067
+ function migrateManagedCodexMcp(existing) {
7068
+ const tableMatch = CODEX_MCP_TABLE_RE.exec(existing);
7069
+ if (!tableMatch || tableMatch.index == null) return null;
7070
+ const blockStart = tableMatch.index;
7071
+ const nextTable = /^[ \t]*\[/gm;
7072
+ nextTable.lastIndex = blockStart + tableMatch[0].length;
7073
+ const blockEnd = nextTable.exec(existing)?.index ?? existing.length;
7074
+ const block = existing.slice(blockStart, blockEnd);
7075
+ const commandPattern = /^([ \t]*command[ \t]*=[ \t]*)"npx"([ \t]*(?:#.*)?)$/m;
7076
+ const argsPattern = /^([ \t]*args[ \t]*=[ \t]*)\[[ \t]*"-y"[ \t]*,[ \t]*"codemem"[ \t]*,[ \t]*"mcp"[ \t]*\]([ \t]*(?:#.*)?)$/m;
7077
+ if (!commandPattern.test(block) || !argsPattern.test(block)) return null;
7078
+ const launcher = codememMcpLauncher();
7079
+ const args = launcher.args.map((arg) => JSON.stringify(arg)).join(", ");
7080
+ const migrated = block.replace(commandPattern, `$1${JSON.stringify(launcher.command)}$2`).replace(argsPattern, `$1[${args}]$2`);
7081
+ return `${existing.slice(0, blockStart)}${migrated}${existing.slice(blockEnd)}`;
7082
+ }
6999
7083
  /** Marker substring identifying codemem-owned hook commands. */
7000
7084
  var CODEMEM_HOOK_MARKER = "codemem codex-hook-";
7001
7085
  /**
7002
7086
  * 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.
7087
+ * when it's on PATH (fast — no per-hook resolution); fall back to an `npx`
7088
+ * invocation that requests both `codemem` and `@codemem/embeddings` when codemem
7089
+ * isn't installed (e.g. setup was run via `npx codemem setup`). The paired
7090
+ * runtime keeps the local-store inject path (codex-hook-inject) able to embed
7091
+ * instead of degrading to FTS. Mirrors the MCP launcher's two-package model.
7007
7092
  */
7008
7093
  function codememCodexHookBase() {
7009
- return codememOnPath() ? "codemem" : "npx -y codemem";
7094
+ if (codememOnPath(true)) return "codemem";
7095
+ return "npx -y --package codemem --package @codemem/embeddings codemem";
7010
7096
  }
7011
7097
  /**
7012
7098
  * Build the codemem-owned hook groups keyed by Codex event name, given the
@@ -7048,28 +7134,127 @@ function isCodememHookGroup(group) {
7048
7134
  return hooks.some((h) => h != null && typeof h === "object" && typeof h.command === "string" && h.command.includes(CODEMEM_HOOK_MARKER));
7049
7135
  }
7050
7136
  /**
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.
7137
+ * True if a resolved bin path is a transient/project-local bin that will not be
7138
+ * on PATH for the globally-configured MCP hosts (OpenCode/Claude/Codex).
7139
+ *
7140
+ * - `_npx` / `.pnpm/dlx`: npx/dlx caches exposed only for the duration of a
7141
+ * `npx -y codemem setup` run, then removed.
7142
+ * - `node_modules/.bin`: a project-local install exposed only while setup runs
7143
+ * through `npm exec` / `pnpm exec` / a package script. The global hosts do not
7144
+ * inherit that project-local PATH, so baking a bare `codemem` command would
7145
+ * later fail to resolve.
7146
+ *
7147
+ * Such paths must NOT count as "on PATH" for launcher/hook command baking.
7055
7148
  */
7056
7149
  function isTransientNpxBinPath(resolved) {
7057
- return /[/\\]_npx[/\\]/.test(resolved) || /[/\\]\.pnpm[/\\]dlx[/\\]/.test(resolved);
7150
+ return /[/\\]_npx[/\\]/.test(resolved) || /[/\\]\.pnpm[/\\]dlx[/\\]/.test(resolved) || /[/\\]node_modules[/\\]\.bin[/\\]/.test(resolved);
7151
+ }
7152
+ /** Parse a semver core (major.minor.patch) plus optional prerelease tail. */
7153
+ function parseSetupSemver(version) {
7154
+ const match = /^(\d+)\.(\d+)\.(\d+)(?:-.+)?$/.exec(version.trim());
7155
+ if (!match) return null;
7156
+ return {
7157
+ core: [
7158
+ Number(match[1]),
7159
+ Number(match[2]),
7160
+ Number(match[3])
7161
+ ],
7162
+ prerelease: version.includes("-")
7163
+ };
7164
+ }
7165
+ /**
7166
+ * True when a durable `codemem --version` output should be trusted to launch
7167
+ * `codemem mcp` directly. Mirrors the OpenCode plugin's runner check: accept the
7168
+ * exact build version, or a clean (non-prerelease) release at least as new as
7169
+ * this build. This prevents baking a bare launcher that later resolves to an
7170
+ * older global install sitting alongside the transient npx bin.
7171
+ */
7172
+ function isDurableCodememVersionTrusted(versionOutput) {
7173
+ if (versionOutput === VERSION) return true;
7174
+ const candidate = parseSetupSemver(versionOutput);
7175
+ const current = parseSetupSemver(VERSION);
7176
+ if (!candidate || !current || candidate.prerelease) return false;
7177
+ for (let i = 0; i < 3; i++) {
7178
+ const c = candidate.core[i] ?? 0;
7179
+ const v = current.core[i] ?? 0;
7180
+ if (c > v) return true;
7181
+ if (c < v) return false;
7182
+ }
7183
+ return true;
7058
7184
  }
7059
7185
  /**
7060
7186
  * Detect whether a durable `codemem` resolves on PATH (excluding a transient
7061
- * npx/dlx bin that vanishes after this process exits).
7187
+ * npx/dlx bin that vanishes after this process exits). When `requireTrustedVersion`
7188
+ * is set, the durable binary must also report a version compatible with this
7189
+ * build before it is trusted, so setup never bakes a bare launcher that resolves
7190
+ * to a stale global install.
7062
7191
  */
7063
- function codememOnPath() {
7192
+ function codememOnPath(requireTrustedVersion = false) {
7064
7193
  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);
7194
+ const command = process.platform === "win32" ? "where" : "which";
7195
+ const args = process.platform === "win32" ? ["codemem"] : ["-a", "codemem"];
7196
+ const durable = execFileSync(command, args, { encoding: "utf-8" }).split(/\r?\n/).map((line) => line.trim()).filter(Boolean).filter((candidate) => !isTransientNpxBinPath(candidate));
7197
+ if (durable.length === 0) return false;
7198
+ if (!requireTrustedVersion) return true;
7199
+ const [firstDurable] = durable;
7200
+ if (firstDurable == null) return false;
7201
+ const version = probeDurableCodememVersion(firstDurable);
7202
+ return version != null && isDurableCodememVersionTrusted(version);
7068
7203
  } catch {
7069
7204
  return false;
7070
7205
  }
7071
7206
  }
7072
7207
  /**
7208
+ * Run `<binary> --version`, returning trimmed stdout or null on failure. On
7209
+ * Windows a global npm install resolves to a `.cmd`/`.bat` shim that Node's
7210
+ * shell-less execFileSync cannot execute directly, so invoke it through
7211
+ * `cmd.exe /c` there; POSIX binaries run directly.
7212
+ */
7213
+ function probeDurableCodememVersion(binaryPath) {
7214
+ try {
7215
+ return (process.platform === "win32" ? execFileSync("cmd.exe", [
7216
+ "/c",
7217
+ binaryPath,
7218
+ "--version"
7219
+ ], {
7220
+ encoding: "utf-8",
7221
+ timeout: 3e3,
7222
+ stdio: [
7223
+ "ignore",
7224
+ "pipe",
7225
+ "ignore"
7226
+ ]
7227
+ }) : execFileSync(binaryPath, ["--version"], {
7228
+ encoding: "utf-8",
7229
+ timeout: 3e3,
7230
+ stdio: [
7231
+ "ignore",
7232
+ "pipe",
7233
+ "ignore"
7234
+ ]
7235
+ })).trim();
7236
+ } catch {
7237
+ return null;
7238
+ }
7239
+ }
7240
+ function codememMcpLauncher(onPath = codememOnPath(true)) {
7241
+ return onPath ? {
7242
+ command: "codemem",
7243
+ args: ["mcp"]
7244
+ } : {
7245
+ command: "npx",
7246
+ args: [
7247
+ "-y",
7248
+ "--package",
7249
+ "codemem",
7250
+ "--package",
7251
+ "@codemem/embeddings",
7252
+ "codemem",
7253
+ "mcp"
7254
+ ]
7255
+ };
7256
+ }
7257
+ /**
7073
7258
  * Append the codemem MCP server table to Codex config.toml without rewriting
7074
7259
  * unrelated content. Returns true on success.
7075
7260
  */
@@ -7077,6 +7262,20 @@ function installCodexMcp(codexHome, force) {
7077
7262
  const configPath = join(codexHome, "config.toml");
7078
7263
  const existing = existsSync(configPath) ? readFileSync(configPath, "utf-8") : "";
7079
7264
  if (CODEX_MCP_TABLE_RE.test(existing)) {
7265
+ const migrated = migrateManagedCodexMcp(existing);
7266
+ if (migrated != null) {
7267
+ try {
7268
+ copyFileSync(configPath, `${configPath}.codemem.bak`);
7269
+ } catch {}
7270
+ try {
7271
+ writeFileSync(configPath, migrated, "utf-8");
7272
+ p.log.success(`Codex MCP entry upgraded: ${configPath}`);
7273
+ return true;
7274
+ } catch (err) {
7275
+ p.log.error(`Failed to write ${configPath}: ${err instanceof Error ? err.message : String(err)}`);
7276
+ return false;
7277
+ }
7278
+ }
7080
7279
  if (force) p.log.info(`Codex MCP entry already exists in ${configPath} — left as-is (TOML is not rewritten in place)`);
7081
7280
  else p.log.info(`Codex MCP entry already exists in ${configPath}`);
7082
7281
  return true;
@@ -7086,7 +7285,7 @@ function installCodexMcp(codexHome, force) {
7086
7285
  } catch {}
7087
7286
  let next = existing;
7088
7287
  if (next.length > 0 && !next.endsWith("\n\n")) next += next.endsWith("\n") ? "\n" : "\n\n";
7089
- next += `${CODEX_MCP_BLOCK}\n`;
7288
+ next += `${codexMcpBlock()}\n`;
7090
7289
  try {
7091
7290
  writeFileSync(configPath, next, "utf-8");
7092
7291
  p.log.success(`Codex MCP entry installed: ${configPath}`);
@@ -7113,12 +7312,38 @@ function installCodexHooks(codexHome, force) {
7113
7312
  let hooks = config.hooks;
7114
7313
  if (hooks == null || typeof hooks !== "object" || Array.isArray(hooks)) hooks = {};
7115
7314
  const ours = buildCodememCodexHookGroups(codememCodexHookBase());
7315
+ const currentCommands = new Set(Object.values(ours).flatMap((groups) => groups.flatMap((group) => group.hooks.map((hook) => hook.command))));
7316
+ const groupNeedsMigration = (group) => {
7317
+ if (!isCodememHookGroup(group)) return false;
7318
+ const hooksList = group.hooks;
7319
+ if (!Array.isArray(hooksList)) return false;
7320
+ return hooksList.some((hook) => {
7321
+ if (hook == null || typeof hook !== "object") return false;
7322
+ const command = hook.command;
7323
+ if (typeof command !== "string") return false;
7324
+ return command.includes(CODEMEM_HOOK_MARKER) && !currentCommands.has(command);
7325
+ });
7326
+ };
7116
7327
  let changed = false;
7328
+ const isCodememOwnedHook = (hook) => hook != null && typeof hook === "object" && typeof hook.command === "string" && hook.command.includes(CODEMEM_HOOK_MARKER);
7329
+ const stripCodememHooks = (group) => {
7330
+ if (!isCodememHookGroup(group)) return group;
7331
+ const hooksList = group.hooks;
7332
+ if (!Array.isArray(hooksList)) return null;
7333
+ const preservedHooks = hooksList.filter((hook) => !isCodememOwnedHook(hook));
7334
+ if (preservedHooks.length === 0) return null;
7335
+ return {
7336
+ ...group,
7337
+ hooks: preservedHooks
7338
+ };
7339
+ };
7117
7340
  for (const [event, ourGroups] of Object.entries(ours)) {
7118
7341
  const current = hooks[event];
7119
7342
  const existingGroups = Array.isArray(current) ? [...current] : [];
7120
- if (existingGroups.some(isCodememHookGroup) && !force) continue;
7121
- const preserved = existingGroups.filter((g) => !isCodememHookGroup(g));
7343
+ const hasCodemem = existingGroups.some(isCodememHookGroup);
7344
+ const needsMigration = existingGroups.some(groupNeedsMigration);
7345
+ if (hasCodemem && !force && !needsMigration) continue;
7346
+ const preserved = existingGroups.map((group) => stripCodememHooks(group)).filter((group) => group != null);
7122
7347
  hooks[event] = [...preserved, ...ourGroups];
7123
7348
  changed = true;
7124
7349
  }
@@ -7154,8 +7379,11 @@ function installCodex(force) {
7154
7379
  p.log.error(`Failed to create Codex home ${codexHome}: ${err instanceof Error ? err.message : String(err)}`);
7155
7380
  return false;
7156
7381
  }
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");
7382
+ if (codememOnPath(true)) p.log.info("Codex hooks will call `codemem` directly (found on PATH).");
7383
+ else {
7384
+ const globalInstallCommand = process.platform === "linux" ? "env ONNXRUNTIME_NODE_INSTALL=skip npm install -g codemem" : "npm install -g codemem";
7385
+ 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}`);
7386
+ }
7159
7387
  let ok = true;
7160
7388
  ok = installCodexMcp(codexHome, force) && ok;
7161
7389
  ok = installCodexHooks(codexHome, force) && ok;
@@ -7285,7 +7513,7 @@ var statsCommand = statsCmd.action((opts) => {
7285
7513
  //#region src/commands/status.ts
7286
7514
  var MAX_ATTENTION = 20;
7287
7515
  var DEFAULT_VIEWER_PORT = 38888;
7288
- var RECENT_FAILURE_WINDOW_MS = 1440 * 60 * 1e3;
7516
+ var RECENT_FAILURE_WINDOW_MS = 864e5;
7289
7517
  var OBSERVER_IDENTITY_KEYS = [
7290
7518
  "observer_provider",
7291
7519
  "observer_model",
@@ -8432,7 +8660,8 @@ bootstrapCmd.action(async (peerArg, opts) => {
8432
8660
  process.exitCode = 2;
8433
8661
  return;
8434
8662
  }
8435
- const store = new MemoryStore(resolveDbPath(resolveDbOpt(opts)));
8663
+ const dbPath = resolveDbPath(resolveDbOpt(opts));
8664
+ const store = new MemoryStore(dbPath);
8436
8665
  try {
8437
8666
  const pageSize = Math.max(1, Number.parseInt(opts.pageSize, 10) || 2e3);
8438
8667
  const keysDir = opts.keysDir ?? void 0;
@@ -8603,7 +8832,7 @@ syncCoordinatorAlias.hook("preAction", (_thisCmd, actionCmd) => {
8603
8832
  syncCommand.addCommand(syncCoordinatorAlias, { hidden: true });
8604
8833
  //#endregion
8605
8834
  //#region src/commands/update.ts
8606
- var INSTALL_TIMEOUT_MS = 300 * 1e3;
8835
+ var INSTALL_TIMEOUT_MS = 42e4;
8607
8836
  var VERIFY_TIMEOUT_MS = 3e4;
8608
8837
  var PUBLIC_NPM_REGISTRY = "https://registry.npmjs.org/";
8609
8838
  var UPDATE_LOCK_FILE = "update-install.lock";
@@ -8625,7 +8854,7 @@ async function removeStaleInstallLock(lockPath) {
8625
8854
  const pid = Number.parseInt(rawPid.trim(), 10);
8626
8855
  if (Number.isSafeInteger(pid) && pid > 0) {
8627
8856
  if (isProcessAlive(pid)) return false;
8628
- } else if (Date.now() - lockStat.mtimeMs <= 33e4) return false;
8857
+ } else if (Date.now() - lockStat.mtimeMs <= 45e4) return false;
8629
8858
  await unlink(lockPath);
8630
8859
  return true;
8631
8860
  } catch {
@@ -8656,6 +8885,7 @@ function runCommand(command, args, timeoutMs, options = {}) {
8656
8885
  const child = spawn(command, args, {
8657
8886
  cwd: options.cwd,
8658
8887
  detached: process.platform !== "win32",
8888
+ env: options.env,
8659
8889
  shell: false,
8660
8890
  stdio: [
8661
8891
  "ignore",
@@ -8784,17 +9014,18 @@ function renderStatusWarning(error) {
8784
9014
  function renderHumanStatus(status) {
8785
9015
  const warning = renderStatusWarning(status.error);
8786
9016
  if (status.update_available) return `Update available${cacheQualifier(status.stale)}: ${status.current_version} → ${status.latest_version}. ${status.recommended_action}${warning}`;
8787
- if (!isStableReleaseVersion(status.current_version)) return `Unable to compare current version ${status.current_version} with ${status.latest_version}. ${status.recommended_action}${warning}`;
9017
+ if (!status.channel || !isReleaseVersionForChannel(status.current_version, status.channel)) return `Unable to compare current version ${status.current_version} with ${status.latest_version}. ${status.recommended_action}${warning}`;
8788
9018
  return `${status.current_version} is up to date${cacheQualifier(status.stale)}.${warning}`;
8789
9019
  }
8790
- 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) => {
9020
+ var checkCommand = addJsonOption(new Command("check").description("Check for a newer codemem release on the installed channel")).addOption(new Option("-r, --refresh", "bypass the six-hour release cache")).configureHelp(helpStyle).action(async (options) => {
8791
9021
  try {
9022
+ const installKind = detectInstallKind({
9023
+ entryPath: process.argv[1] ?? "",
9024
+ env: process.env
9025
+ });
8792
9026
  const status = await getUpdateStatus({
8793
9027
  currentVersion: VERSION,
8794
- installKind: detectInstallKind({
8795
- entryPath: process.argv[1] ?? "",
8796
- env: process.env
8797
- }),
9028
+ installKind,
8798
9029
  refresh: options.refresh
8799
9030
  });
8800
9031
  if (status.latest_version === null) {
@@ -8820,15 +9051,29 @@ var checkCommand = addJsonOption(new Command("check").description("Check for a n
8820
9051
  }
8821
9052
  }
8822
9053
  });
8823
- var installCommand = addJsonOption(new Command("install").description("Install an eligible stable codemem update")).configureHelp(helpStyle).action(async (options) => {
9054
+ function updateInstallArgs(npmArgs, targetVersion) {
9055
+ const installArgs = [
9056
+ "install",
9057
+ "-g",
9058
+ "--registry",
9059
+ PUBLIC_NPM_REGISTRY,
9060
+ `--@codemem:registry=${PUBLIC_NPM_REGISTRY}`,
9061
+ `codemem@${targetVersion}`,
9062
+ `@codemem/embeddings@${targetVersion}`
9063
+ ];
9064
+ if (process.platform !== "win32") return installArgs;
9065
+ return [...npmArgs.slice(0, 3), windowsCommandLine(npmArgs[3] ?? "", installArgs)];
9066
+ }
9067
+ async function installUpdate(options) {
8824
9068
  let releaseInstallLock = null;
8825
9069
  try {
9070
+ const installKind = detectInstallKind({
9071
+ entryPath: process.argv[1] ?? "",
9072
+ env: process.env
9073
+ });
8826
9074
  const status = await getUpdateStatus({
8827
9075
  currentVersion: VERSION,
8828
- installKind: detectInstallKind({
8829
- entryPath: process.argv[1] ?? "",
8830
- env: process.env
8831
- }),
9076
+ installKind,
8832
9077
  refresh: true
8833
9078
  });
8834
9079
  if (!status.auto_update_eligible || !status.latest_version) {
@@ -8836,26 +9081,18 @@ var installCommand = addJsonOption(new Command("install").description("Install a
8836
9081
  return;
8837
9082
  }
8838
9083
  const targetVersion = status.latest_version;
8839
- if (!isStableReleaseVersion(targetVersion)) {
8840
- failInstall(options, "update_install_refused", "release version is not a stable semantic version");
9084
+ if (!status.channel || !isReleaseVersionForChannel(targetVersion, status.channel)) {
9085
+ failInstall(options, "update_install_refused", "release version does not match the installed channel");
8841
9086
  return;
8842
9087
  }
8843
9088
  releaseInstallLock = await acquireInstallLock();
8844
9089
  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, {
9090
+ const installation = await runCommand(npm.command, updateInstallArgs(npm.args, targetVersion), INSTALL_TIMEOUT_MS, {
8858
9091
  cwd: npm.cwd,
9092
+ env: process.platform === "linux" ? {
9093
+ ...process.env,
9094
+ ONNXRUNTIME_NODE_INSTALL: "skip"
9095
+ } : void 0,
8859
9096
  windowsVerbatimArguments: process.platform === "win32"
8860
9097
  });
8861
9098
  if (installation.exitCode !== 0) {
@@ -8882,8 +9119,9 @@ var installCommand = addJsonOption(new Command("install").description("Install a
8882
9119
  } finally {
8883
9120
  await releaseInstallLock?.();
8884
9121
  }
8885
- });
8886
- var updateCommand = new Command("update").description("Inspect and manage codemem updates").configureHelp(helpStyle).addCommand(checkCommand).addCommand(installCommand);
9122
+ }
9123
+ var installCommand = addJsonOption(new Command("install").description("Install an eligible codemem update on the installed channel")).configureHelp(helpStyle).action(installUpdate);
9124
+ var updateCommand = new Command("update").description("Inspect and manage codemem updates").enablePositionalOptions().configureHelp(helpStyle).addCommand(checkCommand).addCommand(installCommand);
8887
9125
  //#endregion
8888
9126
  //#region src/commands/version.ts
8889
9127
  var versionCommand = new Command("version").configureHelp(helpStyle).description("Print codemem version").action(() => {