@remnic/core 9.7.6 → 9.7.8

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.
Files changed (45) hide show
  1. package/dist/access-boundary.d.ts +2 -0
  2. package/dist/access-boundary.js +1 -1
  3. package/dist/access-cli.js +7 -7
  4. package/dist/access-http.js +5 -5
  5. package/dist/access-mcp.d.ts +2 -0
  6. package/dist/access-mcp.js +4 -4
  7. package/dist/access-operations-batch.js +2 -2
  8. package/dist/access-operations.d.ts +3 -3
  9. package/dist/access-operations.js +3 -3
  10. package/dist/access-schema.d.ts +64 -64
  11. package/dist/{chunk-TBQ4CFIP.js → chunk-57INMZ6F.js} +1 -1
  12. package/dist/chunk-57INMZ6F.js.map +1 -0
  13. package/dist/{chunk-BFCQPJ5B.js → chunk-CHVU4RE5.js} +24 -6
  14. package/dist/chunk-CHVU4RE5.js.map +1 -0
  15. package/dist/{chunk-3CRHW42H.js → chunk-FYKIEOG6.js} +2 -2
  16. package/dist/{chunk-ESE55PZJ.js → chunk-NMMKRVUF.js} +10 -6
  17. package/dist/chunk-NMMKRVUF.js.map +1 -0
  18. package/dist/{chunk-3UPBVNBX.js → chunk-Q6JPMCPO.js} +3 -3
  19. package/dist/{chunk-P2UA6XQG.js → chunk-UHGHTOR5.js} +5 -5
  20. package/dist/chunk-UHGHTOR5.js.map +1 -0
  21. package/dist/{chunk-H67QFYUK.js → chunk-UJBESW6X.js} +918 -146
  22. package/dist/chunk-UJBESW6X.js.map +1 -0
  23. package/dist/cli.js +6 -6
  24. package/dist/connectors/index.d.ts +7 -0
  25. package/dist/connectors/index.js +1 -1
  26. package/dist/index.js +7 -7
  27. package/dist/orchestrator.js +7 -7
  28. package/dist/schemas.d.ts +74 -74
  29. package/dist/shared-context/manager.d.ts +8 -8
  30. package/dist/transfer/types.d.ts +66 -66
  31. package/package.json +2 -2
  32. package/src/access-boundary.ts +2 -0
  33. package/src/access-http.ts +15 -2
  34. package/src/access-mcp-cancellation.test.ts +405 -0
  35. package/src/access-mcp.ts +25 -1
  36. package/src/access-operations-batch.ts +3 -3
  37. package/src/connectors/hermes-shim.ts +523 -0
  38. package/src/connectors/index.ts +769 -15
  39. package/dist/chunk-BFCQPJ5B.js.map +0 -1
  40. package/dist/chunk-ESE55PZJ.js.map +0 -1
  41. package/dist/chunk-H67QFYUK.js.map +0 -1
  42. package/dist/chunk-P2UA6XQG.js.map +0 -1
  43. package/dist/chunk-TBQ4CFIP.js.map +0 -1
  44. /package/dist/{chunk-3CRHW42H.js.map → chunk-FYKIEOG6.js.map} +0 -0
  45. /package/dist/{chunk-3UPBVNBX.js.map → chunk-Q6JPMCPO.js.map} +0 -0
@@ -32,15 +32,310 @@ import {
32
32
  } from "./chunk-37CTGERM.js";
33
33
 
34
34
  // src/connectors/index.ts
35
- import fs2 from "fs";
36
- import path2 from "path";
35
+ import fs3 from "fs";
36
+ import path3 from "path";
37
37
  import os from "os";
38
38
  import { createRequire } from "module";
39
39
  import { fileURLToPath } from "url";
40
40
 
41
- // src/connectors/codex-marketplace.ts
41
+ // src/connectors/hermes-shim.ts
42
42
  import fs from "fs";
43
43
  import path from "path";
44
+ var HERMES_SHIM_MARKER = "generated by `remnic connectors install hermes`";
45
+ function resolveHermesRoot() {
46
+ const envHome = readEnvVar("HERMES_HOME");
47
+ if (typeof envHome === "string" && envHome.trim().length > 0) {
48
+ const expanded = path.resolve(expandTildePath(envHome.trim()));
49
+ try {
50
+ const stat = fs.lstatSync(expanded);
51
+ if (stat.isSymbolicLink()) {
52
+ throw new Error(`HERMES_HOME must not be a symbolic link: ${expanded}`);
53
+ }
54
+ if (!stat.isDirectory()) {
55
+ throw new Error(`HERMES_HOME is not a directory: ${expanded}`);
56
+ }
57
+ } catch (err) {
58
+ if (err.code !== "ENOENT") {
59
+ throw err;
60
+ }
61
+ }
62
+ return expanded;
63
+ }
64
+ if (process.platform === "win32") {
65
+ const localAppData = (readEnvVar("LOCALAPPDATA") ?? "").trim();
66
+ const base = localAppData.length > 0 ? path.resolve(localAppData) : path.join(resolveHomeDir(), "AppData", "Local");
67
+ return resolveDefaultRoot(path.join(base, "hermes"));
68
+ }
69
+ return resolveDefaultRoot(path.resolve(resolveHomeDir(), ".hermes"));
70
+ }
71
+ function resolveDefaultRoot(candidate) {
72
+ try {
73
+ return fs.realpathSync.native(candidate);
74
+ } catch {
75
+ return candidate;
76
+ }
77
+ }
78
+ function hermesShimPath() {
79
+ return path.join(resolveHermesRoot(), "plugins", "remnic", "__init__.py");
80
+ }
81
+ function writePlainFileAtomicSync(filePath, data) {
82
+ const dir = path.dirname(filePath);
83
+ const base = path.basename(filePath);
84
+ const tmpPath = path.join(dir, `.${base}.${process.pid}.${Date.now()}.${Math.random().toString(16).slice(2)}.tmp`);
85
+ let wroteTemp = false;
86
+ try {
87
+ fs.writeFileSync(tmpPath, data, { mode: 420, flag: "wx" });
88
+ wroteTemp = true;
89
+ fs.renameSync(tmpPath, filePath);
90
+ try {
91
+ fs.chmodSync(filePath, 420);
92
+ } catch {
93
+ }
94
+ } catch (err) {
95
+ if (wroteTemp) {
96
+ try {
97
+ fs.unlinkSync(tmpPath);
98
+ } catch {
99
+ }
100
+ }
101
+ throw err;
102
+ }
103
+ }
104
+ function assertShimComponentsNotSymlinked(shimPath) {
105
+ const remnicDir = path.dirname(shimPath);
106
+ const pluginsDir = path.dirname(remnicDir);
107
+ for (const component of [pluginsDir, remnicDir, shimPath]) {
108
+ let isLink = false;
109
+ try {
110
+ isLink = fs.lstatSync(component).isSymbolicLink();
111
+ } catch {
112
+ continue;
113
+ }
114
+ if (isLink) {
115
+ throw new Error(`refusing to operate through a symbolic link: ${component}`);
116
+ }
117
+ }
118
+ }
119
+ function materializeHermesShim(shimPath) {
120
+ assertShimComponentsNotSymlinked(shimPath);
121
+ if (fs.existsSync(shimPath)) {
122
+ let existing;
123
+ try {
124
+ existing = fs.readFileSync(shimPath, "utf8");
125
+ } catch (readErr) {
126
+ throw new Error(
127
+ `cannot read existing shim at ${shimPath}: ${readErr instanceof Error ? readErr.message : String(readErr)}`
128
+ );
129
+ }
130
+ if (!existing.includes(HERMES_SHIM_MARKER)) {
131
+ return {
132
+ note: `Hermes plugin shim already exists and was NOT generated by Remnic \u2014 left untouched: ${shimPath}. If the provider is not discovered, ensure that file imports remnic_hermes.register AND contains the literal text register_memory_provider (or MemoryProvider) \u2014 Hermes' discovery text-scan skips the directory without it.`,
133
+ wrote: false
134
+ };
135
+ }
136
+ }
137
+ fs.mkdirSync(path.dirname(shimPath), { recursive: true });
138
+ const content = [
139
+ `"""Remnic memory provider shim for Hermes Agent (${HERMES_SHIM_MARKER}).`,
140
+ "",
141
+ "Hermes memory-provider discovery calls register(collector);",
142
+ "collector.register_memory_provider() receives the provider.",
143
+ `"""`,
144
+ "",
145
+ "from remnic_hermes import register # noqa: F401 (register() loads Hermes config itself)",
146
+ ""
147
+ ].join("\n");
148
+ writePlainFileAtomicSync(shimPath, content);
149
+ return { note: `Materialized Hermes plugin shim: ${shimPath}`, wrote: true };
150
+ }
151
+ function reconcileHermesShim(priorPersistedPath) {
152
+ const notes = [];
153
+ let target = null;
154
+ let materialized = false;
155
+ let createdNew = false;
156
+ try {
157
+ target = hermesShimPath();
158
+ const existedBefore = fs.existsSync(target);
159
+ const result = materializeHermesShim(target);
160
+ notes.push(result.note);
161
+ materialized = result.wrote;
162
+ createdNew = result.wrote && !existedBefore;
163
+ } catch (shimErr) {
164
+ const shimPathHint = target ?? "<hermesRoot>/plugins/remnic/__init__.py";
165
+ notes.push(
166
+ `Note: could not materialize the Hermes plugin shim (${shimErr instanceof Error ? shimErr.message : String(shimErr)}). Create ${shimPathHint} manually with exactly these two lines:
167
+ """Remnic memory provider shim. Calls collector.register_memory_provider()."""
168
+ from remnic_hermes import register`
169
+ );
170
+ }
171
+ if (!materialized || target === null) {
172
+ return {
173
+ notes,
174
+ persistPath: priorPersistedPath,
175
+ materializedAt: null,
176
+ createdNew: false,
177
+ priorCleanedAt: null
178
+ };
179
+ }
180
+ const confirmedTarget = target;
181
+ let priorCleanedAt = null;
182
+ if (priorPersistedPath !== null && !sameShimTarget(priorPersistedPath, confirmedTarget)) {
183
+ try {
184
+ const stale = removeHermesShim([priorPersistedPath]);
185
+ if (stale.notes.length > 0) {
186
+ notes.push(`Cleaned prior-install shim: ${stale.notes.join("; ")}`);
187
+ }
188
+ priorCleanedAt = stale.removedPaths.includes(priorPersistedPath) ? priorPersistedPath : null;
189
+ } catch {
190
+ notes.push(
191
+ `Note: could not clean the prior-install Hermes plugin shim at ${priorPersistedPath} \u2014 remove it manually if present.`
192
+ );
193
+ }
194
+ }
195
+ return { notes, persistPath: target, materializedAt: target, createdNew, priorCleanedAt };
196
+ }
197
+ function sameShimTarget(leftPath, rightPath) {
198
+ return resolveShimTarget(leftPath) === resolveShimTarget(rightPath);
199
+ }
200
+ function resolveShimTarget(candidate) {
201
+ try {
202
+ return fs.realpathSync.native(candidate);
203
+ } catch {
204
+ return path.resolve(candidate);
205
+ }
206
+ }
207
+ function removeHermesShim(candidatePaths) {
208
+ const notes = [];
209
+ const removedPaths = [];
210
+ for (const shimPath of new Set(candidatePaths)) {
211
+ if (!isPlausibleHermesShimPath(shimPath)) {
212
+ continue;
213
+ }
214
+ try {
215
+ assertShimComponentsNotSymlinked(shimPath);
216
+ } catch {
217
+ notes.push(`Hermes plugin shim left untouched (symlinked path component): ${shimPath}`);
218
+ continue;
219
+ }
220
+ if (!fs.existsSync(shimPath)) {
221
+ continue;
222
+ }
223
+ let content;
224
+ try {
225
+ content = fs.readFileSync(shimPath, "utf8");
226
+ } catch {
227
+ notes.push(`Hermes plugin shim left untouched (unreadable): ${shimPath}`);
228
+ continue;
229
+ }
230
+ if (!content.includes(HERMES_SHIM_MARKER)) {
231
+ notes.push(`Hermes plugin shim left untouched (not Remnic-generated): ${shimPath}`);
232
+ continue;
233
+ }
234
+ try {
235
+ fs.unlinkSync(shimPath);
236
+ } catch (unlinkErr) {
237
+ notes.push(
238
+ `Hermes plugin shim could not be removed (${unlinkErr instanceof Error ? unlinkErr.message : String(unlinkErr)}): ${shimPath} \u2014 remove it manually or re-run after fixing permissions.`
239
+ );
240
+ continue;
241
+ }
242
+ removedPaths.push(shimPath);
243
+ try {
244
+ fs.rmdirSync(path.dirname(shimPath));
245
+ } catch {
246
+ }
247
+ notes.push(`Removed Hermes plugin shim: ${shimPath}`);
248
+ }
249
+ return { removedPaths, notes };
250
+ }
251
+ function isPlausibleHermesShimPath(candidate) {
252
+ if (typeof candidate !== "string" || candidate.length === 0 || !path.isAbsolute(candidate)) {
253
+ return false;
254
+ }
255
+ const dir = path.dirname(candidate);
256
+ return path.basename(candidate) === "__init__.py" && path.basename(dir) === "remnic" && path.basename(path.dirname(dir)) === "plugins";
257
+ }
258
+ function isPlausibleHermesConfigPath(candidate) {
259
+ if (typeof candidate !== "string" || candidate.length === 0 || !path.isAbsolute(candidate)) {
260
+ return false;
261
+ }
262
+ if (path.basename(candidate) !== "config.yaml") {
263
+ return false;
264
+ }
265
+ const dir = path.dirname(candidate);
266
+ const dirName = path.basename(dir).toLowerCase();
267
+ if (dirName === ".hermes" || dirName === "hermes") {
268
+ return true;
269
+ }
270
+ if (path.basename(path.dirname(dir)) === "profiles") {
271
+ return true;
272
+ }
273
+ if (["plugins", "profiles"].some((sibling) => {
274
+ try {
275
+ return fs.statSync(path.join(dir, sibling)).isDirectory();
276
+ } catch {
277
+ return false;
278
+ }
279
+ })) {
280
+ return true;
281
+ }
282
+ try {
283
+ const stat = fs.statSync(candidate);
284
+ if (!stat.isFile() || stat.size > 1024 * 1024) {
285
+ return false;
286
+ }
287
+ return /^remnic:/m.test(fs.readFileSync(candidate, "utf8"));
288
+ } catch {
289
+ return false;
290
+ }
291
+ }
292
+ function survivingMarkerShims(candidates, excludeTarget) {
293
+ const survivors = [];
294
+ for (const candidate of new Set(candidates)) {
295
+ if (!isPlausibleHermesShimPath(candidate)) {
296
+ continue;
297
+ }
298
+ if (excludeTarget !== null && sameShimTarget(candidate, excludeTarget)) {
299
+ continue;
300
+ }
301
+ if (survivors.some((kept) => sameShimTarget(kept, candidate))) {
302
+ continue;
303
+ }
304
+ let carriesMarker = false;
305
+ try {
306
+ carriesMarker = fs.readFileSync(candidate, "utf8").includes(HERMES_SHIM_MARKER);
307
+ } catch {
308
+ carriesMarker = false;
309
+ }
310
+ if (carriesMarker) {
311
+ survivors.push(candidate);
312
+ }
313
+ }
314
+ return survivors;
315
+ }
316
+ function assertConfigComponentsNotSymlinked(cfgPath) {
317
+ const dir = path.dirname(cfgPath);
318
+ const components = [cfgPath, dir];
319
+ const grandparent = path.dirname(dir);
320
+ if (path.basename(grandparent) === "profiles") {
321
+ components.push(grandparent);
322
+ }
323
+ for (const component of components) {
324
+ let isLink = false;
325
+ try {
326
+ isLink = fs.lstatSync(component).isSymbolicLink();
327
+ } catch {
328
+ continue;
329
+ }
330
+ if (isLink) {
331
+ throw new Error(`refusing to operate through a symbolic link: ${component}`);
332
+ }
333
+ }
334
+ }
335
+
336
+ // src/connectors/codex-marketplace.ts
337
+ import fs2 from "fs";
338
+ import path2 from "path";
44
339
  var MARKETPLACE_SCHEMA_VERSION = 1;
45
340
  var MARKETPLACE_MANIFEST_FILENAME = "marketplace.json";
46
341
  var VALID_INSTALL_TYPES = /* @__PURE__ */ new Set(["github", "git", "local", "url"]);
@@ -142,12 +437,12 @@ async function writeMarketplaceManifest(outputDir, manifest) {
142
437
  `Refusing to write invalid manifest: ${validation.errors.join("; ")}`
143
438
  );
144
439
  }
145
- fs.mkdirSync(outputDir, { recursive: true });
146
- const destPath = path.join(outputDir, MARKETPLACE_MANIFEST_FILENAME);
440
+ fs2.mkdirSync(outputDir, { recursive: true });
441
+ const destPath = path2.join(outputDir, MARKETPLACE_MANIFEST_FILENAME);
147
442
  const tmpPath = `${destPath}.tmp.${process.pid}`;
148
443
  const content = JSON.stringify(manifest, null, 2) + "\n";
149
- fs.writeFileSync(tmpPath, content);
150
- fs.renameSync(tmpPath, destPath);
444
+ fs2.writeFileSync(tmpPath, content);
445
+ fs2.renameSync(tmpPath, destPath);
151
446
  }
152
447
  async function installFromMarketplace(source, sourceType, config, logger) {
153
448
  const _log = logger ?? {
@@ -207,12 +502,12 @@ async function resolveManifest(source, sourceType, logger) {
207
502
  }
208
503
  }
209
504
  function resolveLocal(dirPath, logger) {
210
- const manifestPath = path.join(dirPath, MARKETPLACE_MANIFEST_FILENAME);
211
- if (!fs.existsSync(manifestPath)) {
505
+ const manifestPath = path2.join(dirPath, MARKETPLACE_MANIFEST_FILENAME);
506
+ if (!fs2.existsSync(manifestPath)) {
212
507
  throw new Error(`marketplace.json not found at ${manifestPath}`);
213
508
  }
214
509
  logger.debug?.(`reading local marketplace manifest: ${manifestPath}`);
215
- const raw = fs.readFileSync(manifestPath, "utf-8");
510
+ const raw = fs2.readFileSync(manifestPath, "utf-8");
216
511
  let parsed;
217
512
  try {
218
513
  parsed = JSON.parse(raw);
@@ -264,15 +559,15 @@ async function resolveGit(gitUrl, logger) {
264
559
  }
265
560
  function readPackageVersion() {
266
561
  const candidates = [
267
- path.resolve(import.meta.dirname ?? ".", "../../../.."),
268
- path.resolve(import.meta.dirname ?? ".", "../../../../.."),
269
- path.resolve(import.meta.dirname ?? ".", "..")
562
+ path2.resolve(import.meta.dirname ?? ".", "../../../.."),
563
+ path2.resolve(import.meta.dirname ?? ".", "../../../../.."),
564
+ path2.resolve(import.meta.dirname ?? ".", "..")
270
565
  ];
271
566
  for (const candidate of candidates) {
272
- const pkgPath = path.join(candidate, "package.json");
567
+ const pkgPath = path2.join(candidate, "package.json");
273
568
  try {
274
- if (!fs.existsSync(pkgPath)) continue;
275
- const raw = fs.readFileSync(pkgPath, "utf-8");
569
+ if (!fs2.existsSync(pkgPath)) continue;
570
+ const raw = fs2.readFileSync(pkgPath, "utf-8");
276
571
  const parsed = JSON.parse(raw);
277
572
  if (typeof parsed === "object" && parsed !== null && typeof parsed.version === "string") {
278
573
  return parsed.version;
@@ -642,7 +937,7 @@ function isConnectorManifest(value) {
642
937
  }
643
938
  function loadRegistry() {
644
939
  const regPath = getRegistryPath();
645
- if (!fs2.existsSync(regPath)) {
940
+ if (!fs3.existsSync(regPath)) {
646
941
  const registry = {
647
942
  connectors: BUILTIN_CONNECTORS,
648
943
  registryPath: regPath
@@ -651,7 +946,7 @@ function loadRegistry() {
651
946
  return registry;
652
947
  }
653
948
  try {
654
- const raw = fs2.readFileSync(regPath, "utf8");
949
+ const raw = fs3.readFileSync(regPath, "utf8");
655
950
  const parsed = JSON.parse(raw);
656
951
  if (typeof parsed !== "object" || parsed === null || !Array.isArray(parsed.connectors)) {
657
952
  throw new Error("invalid registry schema");
@@ -674,19 +969,19 @@ function loadRegistry() {
674
969
  }
675
970
  function saveRegistry(registry) {
676
971
  const regPath = registry.registryPath;
677
- fs2.mkdirSync(path2.dirname(regPath), { recursive: true });
678
- fs2.writeFileSync(regPath, JSON.stringify({ connectors: registry.connectors }, null, 2));
972
+ fs3.mkdirSync(path3.dirname(regPath), { recursive: true });
973
+ fs3.writeFileSync(regPath, JSON.stringify({ connectors: registry.connectors }, null, 2));
679
974
  }
680
975
  function listConnectors() {
681
976
  const registry = loadRegistry();
682
977
  const connectorsDir = getConnectorsDir();
683
978
  const installedIds = /* @__PURE__ */ new Set();
684
- if (fs2.existsSync(connectorsDir)) {
685
- for (const entry of fs2.readdirSync(connectorsDir)) {
979
+ if (fs3.existsSync(connectorsDir)) {
980
+ for (const entry of fs3.readdirSync(connectorsDir)) {
686
981
  if (entry.endsWith(".json")) {
687
982
  try {
688
983
  const config = JSON.parse(
689
- fs2.readFileSync(path2.join(connectorsDir, entry), "utf8")
984
+ fs3.readFileSync(path3.join(connectorsDir, entry), "utf8")
690
985
  );
691
986
  if (isValidConnectorId(config.connectorId)) {
692
987
  installedIds.add(config.connectorId);
@@ -702,9 +997,9 @@ function listConnectors() {
702
997
  }));
703
998
  const installed = [];
704
999
  for (const id of installedIds) {
705
- const configPath = path2.join(connectorsDir, `${id}.json`);
1000
+ const configPath = path3.join(connectorsDir, `${id}.json`);
706
1001
  try {
707
- const raw = JSON.parse(fs2.readFileSync(configPath, "utf8"));
1002
+ const raw = JSON.parse(fs3.readFileSync(configPath, "utf8"));
708
1003
  const { token: _redacted, ...config } = raw;
709
1004
  installed.push({
710
1005
  connectorId: id,
@@ -728,9 +1023,9 @@ function getConnectorToken(connectorId) {
728
1023
  }
729
1024
  }
730
1025
  function readSavedConnectorConfig(configPath) {
731
- if (!fs2.existsSync(configPath)) return {};
1026
+ if (!fs3.existsSync(configPath)) return {};
732
1027
  try {
733
- const parsed = JSON.parse(fs2.readFileSync(configPath, "utf8"));
1028
+ const parsed = JSON.parse(fs3.readFileSync(configPath, "utf8"));
734
1029
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {};
735
1030
  const {
736
1031
  connectorId: _connectorId,
@@ -781,15 +1076,134 @@ function installConnector(options) {
781
1076
  (c) => c.connectorId === options.connectorId
782
1077
  );
783
1078
  if (existing && !options.force) {
1079
+ let backfillNote = "";
1080
+ if (options.connectorId === "hermes") {
1081
+ try {
1082
+ const hermesJsonPath = path3.join(getConnectorsDir(), "hermes.json");
1083
+ let parsed = null;
1084
+ try {
1085
+ const parsedRaw = JSON.parse(fs3.readFileSync(hermesJsonPath, "utf8"));
1086
+ if (parsedRaw !== null && typeof parsedRaw === "object" && !Array.isArray(parsedRaw)) {
1087
+ parsed = parsedRaw;
1088
+ }
1089
+ } catch {
1090
+ }
1091
+ const priorRaw = parsed?.pluginShimPath;
1092
+ const priorPersisted = typeof priorRaw === "string" && priorRaw.length > 0 ? priorRaw : null;
1093
+ const profileRaw = parsed?.profile;
1094
+ let currentConfigPath = null;
1095
+ let activeConfigHasBlock = false;
1096
+ let configResolutionError = null;
1097
+ try {
1098
+ currentConfigPath = hermesConfigPath(
1099
+ typeof profileRaw === "string" && profileRaw.length > 0 ? profileRaw : "default"
1100
+ );
1101
+ activeConfigHasBlock = fs3.existsSync(currentConfigPath) && /^remnic:/m.test(fs3.readFileSync(currentConfigPath, "utf8"));
1102
+ } catch (resolveErr) {
1103
+ configResolutionError = resolveErr instanceof Error ? resolveErr.message : String(resolveErr);
1104
+ activeConfigHasBlock = false;
1105
+ }
1106
+ if (!activeConfigHasBlock) {
1107
+ backfillNote = configResolutionError ? ` Note: could not resolve the active Hermes home (${configResolutionError}) \u2014 skipped the shim backfill. Fix the HERMES_HOME/profile configuration and re-run.` : " Note: the active Hermes home's config.yaml has no remnic: block \u2014 skipped the shim backfill so Hermes cannot discover an unconfigured provider. If Hermes was installed under a different HERMES_HOME, re-run with --force to rewrite the config (and token) and migrate the shim under the current home.";
1108
+ return {
1109
+ connectorId: options.connectorId,
1110
+ status: "already_installed",
1111
+ message: `Already installed. Use --force to reinstall.${backfillNote}`
1112
+ };
1113
+ }
1114
+ const outcome = reconcileHermesShim(priorPersisted);
1115
+ if (outcome.notes.length > 0) {
1116
+ backfillNote = ` ${outcome.notes.join(" ")}`;
1117
+ }
1118
+ let configProvenanceAdded = false;
1119
+ if (parsed !== null && currentConfigPath !== null && (typeof parsed.hermesConfigPath !== "string" || parsed.hermesConfigPath.length === 0)) {
1120
+ parsed.hermesConfigPath = currentConfigPath;
1121
+ configProvenanceAdded = true;
1122
+ }
1123
+ let shimPriorsChanged = false;
1124
+ const cleanedInheritedBackfillShims = [];
1125
+ if (parsed !== null) {
1126
+ const shimPriorCandidates = [];
1127
+ if (priorPersisted !== null) {
1128
+ shimPriorCandidates.push(priorPersisted);
1129
+ }
1130
+ if (Array.isArray(parsed.priorPluginShimPaths)) {
1131
+ for (const entry of parsed.priorPluginShimPaths) {
1132
+ if (typeof entry === "string" && entry.length > 0) {
1133
+ shimPriorCandidates.push(entry);
1134
+ }
1135
+ }
1136
+ }
1137
+ if (outcome.materializedAt !== null && shimPriorCandidates.length > 0) {
1138
+ const cleanupTargets = shimPriorCandidates.filter(
1139
+ (candidate) => outcome.persistPath === null || !sameShimTarget(candidate, outcome.persistPath)
1140
+ );
1141
+ if (cleanupTargets.length > 0) {
1142
+ try {
1143
+ const cleaned = removeHermesShim(cleanupTargets);
1144
+ cleanedInheritedBackfillShims.push(...cleaned.removedPaths);
1145
+ } catch {
1146
+ }
1147
+ }
1148
+ }
1149
+ const survivingShims = survivingMarkerShims(shimPriorCandidates, outcome.persistPath);
1150
+ const persistedShimPriors = Array.isArray(parsed.priorPluginShimPaths) ? parsed.priorPluginShimPaths : null;
1151
+ shimPriorsChanged = persistedShimPriors === null ? survivingShims.length > 0 : persistedShimPriors.length !== survivingShims.length || survivingShims.some((entry, index) => persistedShimPriors[index] !== entry);
1152
+ if (survivingShims.length > 0) {
1153
+ parsed.priorPluginShimPaths = survivingShims;
1154
+ } else {
1155
+ delete parsed.priorPluginShimPaths;
1156
+ }
1157
+ }
1158
+ const shimPathChanged = parsed !== null && outcome.persistPath !== null && parsed.pluginShimPath !== outcome.persistPath;
1159
+ if (parsed !== null && (shimPathChanged || configProvenanceAdded || shimPriorsChanged)) {
1160
+ if (outcome.persistPath !== null) {
1161
+ parsed.pluginShimPath = outcome.persistPath;
1162
+ }
1163
+ try {
1164
+ writeSecretFileSync(hermesJsonPath, JSON.stringify(parsed, null, 2));
1165
+ } catch (persistErr) {
1166
+ const rollbackFailures = [];
1167
+ if (outcome.materializedAt !== null && outcome.createdNew && (priorPersisted === null || !sameShimTarget(outcome.materializedAt, priorPersisted))) {
1168
+ try {
1169
+ removeHermesShim([outcome.materializedAt]);
1170
+ } catch {
1171
+ rollbackFailures.push(`remove ${outcome.materializedAt} manually`);
1172
+ }
1173
+ }
1174
+ if (outcome.priorCleanedAt !== null) {
1175
+ try {
1176
+ materializeHermesShim(outcome.priorCleanedAt);
1177
+ } catch {
1178
+ rollbackFailures.push(`restore the shim at ${outcome.priorCleanedAt} manually`);
1179
+ }
1180
+ }
1181
+ for (const cleanedPath of cleanedInheritedBackfillShims) {
1182
+ if (outcome.priorCleanedAt !== null && sameShimTarget(cleanedPath, outcome.priorCleanedAt)) {
1183
+ continue;
1184
+ }
1185
+ try {
1186
+ materializeHermesShim(cleanedPath);
1187
+ } catch {
1188
+ rollbackFailures.push(`restore the shim at ${cleanedPath} manually`);
1189
+ }
1190
+ }
1191
+ const persistMsg = persistErr instanceof Error ? persistErr.message : String(persistErr);
1192
+ backfillNote = rollbackFailures.length === 0 ? ` Note: could not persist the shim path (${persistMsg}); shim changes were rolled back.` : ` Note: could not persist the shim path (${persistMsg}) and rollback was incomplete \u2014 ${rollbackFailures.join("; ")}.`;
1193
+ }
1194
+ }
1195
+ } catch {
1196
+ }
1197
+ }
784
1198
  return {
785
1199
  connectorId: options.connectorId,
786
1200
  status: "already_installed",
787
- message: "Already installed. Use --force to reinstall."
1201
+ message: `Already installed. Use --force to reinstall.${backfillNote}`
788
1202
  };
789
1203
  }
790
1204
  const configDir = getConnectorsDir();
791
- fs2.mkdirSync(configDir, { recursive: true });
792
- const configPath = path2.join(configDir, `${options.connectorId}.json`);
1205
+ fs3.mkdirSync(configDir, { recursive: true });
1206
+ const configPath = path3.join(configDir, `${options.connectorId}.json`);
793
1207
  const savedConnectorConfig = existing ? readSavedConnectorConfig(configPath) : {};
794
1208
  let hermesSavedProfile;
795
1209
  let hermesSavedHost;
@@ -798,9 +1212,9 @@ function installConnector(options) {
798
1212
  let hermesResolvedHost;
799
1213
  let hermesResolvedPort;
800
1214
  if (options.connectorId === "hermes") {
801
- if (fs2.existsSync(configPath)) {
1215
+ if (fs3.existsSync(configPath)) {
802
1216
  try {
803
- const prev = JSON.parse(fs2.readFileSync(configPath, "utf8"));
1217
+ const prev = JSON.parse(fs3.readFileSync(configPath, "utf8"));
804
1218
  if (prev?.profile != null) {
805
1219
  try {
806
1220
  hermesSavedProfile = sanitizeHermesProfile(String(prev.profile));
@@ -927,6 +1341,15 @@ function installConnector(options) {
927
1341
  message: "Hermes install aborted: token store unavailable. Run `remnic token generate hermes` then reinstall to complete setup."
928
1342
  };
929
1343
  }
1344
+ {
1345
+ const envHermesHome = readEnvVar("HERMES_HOME");
1346
+ if (typeof envHermesHome === "string" && envHermesHome.trim().length > 0) {
1347
+ try {
1348
+ fs3.mkdirSync(path3.dirname(hermesConfigPath(hermesProfile)), { recursive: true });
1349
+ } catch {
1350
+ }
1351
+ }
1352
+ }
930
1353
  let yamlResult;
931
1354
  try {
932
1355
  yamlResult = upsertHermesConfig({
@@ -967,7 +1390,7 @@ function installConnector(options) {
967
1390
  let yamlRollbackErrMsg = "";
968
1391
  try {
969
1392
  if (yamlResult.priorContent === null) {
970
- fs2.unlinkSync(yamlResult.configPath);
1393
+ fs3.unlinkSync(yamlResult.configPath);
971
1394
  } else if (typeof yamlResult.priorContent === "string") {
972
1395
  writeSecretFileSync(yamlResult.configPath, yamlResult.priorContent);
973
1396
  }
@@ -992,6 +1415,140 @@ function installConnector(options) {
992
1415
  message
993
1416
  };
994
1417
  }
1418
+ const priorShimPathRaw = savedConnectorConfig.pluginShimPath;
1419
+ const shimOutcome = reconcileHermesShim(
1420
+ typeof priorShimPathRaw === "string" && priorShimPathRaw.length > 0 ? priorShimPathRaw : null
1421
+ );
1422
+ if (shimOutcome.persistPath !== null) {
1423
+ resolvedConfig.pluginShimPath = shimOutcome.persistPath;
1424
+ }
1425
+ const cleanedInheritedShims = [];
1426
+ {
1427
+ const shimPriorCandidates = [];
1428
+ if (typeof priorShimPathRaw === "string" && priorShimPathRaw.length > 0) {
1429
+ shimPriorCandidates.push(priorShimPathRaw);
1430
+ }
1431
+ const inheritedShimPriorsRaw = savedConnectorConfig.priorPluginShimPaths;
1432
+ if (Array.isArray(inheritedShimPriorsRaw)) {
1433
+ for (const entry of inheritedShimPriorsRaw) {
1434
+ if (typeof entry === "string" && entry.length > 0) {
1435
+ shimPriorCandidates.push(entry);
1436
+ }
1437
+ }
1438
+ }
1439
+ if (shimOutcome.materializedAt !== null && shimPriorCandidates.length > 0) {
1440
+ const cleanupTargets = shimPriorCandidates.filter(
1441
+ (candidate) => shimOutcome.persistPath === null || !sameShimTarget(candidate, shimOutcome.persistPath)
1442
+ );
1443
+ if (cleanupTargets.length > 0) {
1444
+ try {
1445
+ const cleaned = removeHermesShim(cleanupTargets);
1446
+ cleanedInheritedShims.push(...cleaned.removedPaths);
1447
+ } catch {
1448
+ }
1449
+ }
1450
+ }
1451
+ const survivingShims = survivingMarkerShims(shimPriorCandidates, shimOutcome.persistPath);
1452
+ if (survivingShims.length > 0) {
1453
+ resolvedConfig.priorPluginShimPaths = survivingShims;
1454
+ } else {
1455
+ delete resolvedConfig.priorPluginShimPaths;
1456
+ }
1457
+ }
1458
+ resolvedConfig.hermesConfigPath = yamlResult.configPath;
1459
+ const hermesPriorNotes = [];
1460
+ const priorConfigPathRaw = savedConnectorConfig.hermesConfigPath;
1461
+ const priorConfigPath = typeof priorConfigPathRaw === "string" && priorConfigPathRaw.length > 0 ? priorConfigPathRaw : null;
1462
+ const priorConfigDiffers = priorConfigPath !== null && !sameHermesConfigTarget(priorConfigPath, yamlResult.configPath);
1463
+ const hermesPriorConfigMutations = [];
1464
+ if (priorConfigDiffers && priorConfigPath !== null && shimOutcome.materializedAt === null) {
1465
+ let fallbackDetail = "";
1466
+ if (isPlausibleHermesConfigPath(priorConfigPath)) {
1467
+ try {
1468
+ const beforeRefresh = fs3.readFileSync(priorConfigPath, "utf8");
1469
+ const refresh = upsertHermesConfigAt(priorConfigPath, {
1470
+ host: hermesHost,
1471
+ port: hermesPort,
1472
+ token: tokenEntry.token
1473
+ });
1474
+ if (refresh.updated) {
1475
+ hermesPriorConfigMutations.push({ mutatedPath: priorConfigPath, priorContent: beforeRefresh });
1476
+ }
1477
+ fallbackDetail = refresh.updated ? " Its remnic: block was refreshed with the newly-issued token so it keeps working." : ` Note: its token could not be refreshed (${refresh.reason ?? "config not writable"}) \u2014 re-run install with HERMES_HOME pointing there to restore daemon auth.`;
1478
+ } catch (refreshErr) {
1479
+ fallbackDetail = ` Note: its token could not be refreshed (${refreshErr instanceof Error ? refreshErr.message : String(refreshErr)}) \u2014 re-run install with HERMES_HOME pointing there to restore daemon auth.`;
1480
+ }
1481
+ } else {
1482
+ fallbackDetail = " Note: its token was rotated by this install \u2014 re-run install with HERMES_HOME pointing there to restore daemon auth.";
1483
+ }
1484
+ hermesPriorNotes.push(
1485
+ `Note: left the prior-install Hermes config in place at ${priorConfigPath} \u2014 no Remnic shim could be written under the current Hermes home, so the prior installation remains the working fallback.${fallbackDetail} Resolve the shim collision/failure above and re-run with --force to complete the migration.`
1486
+ );
1487
+ }
1488
+ {
1489
+ const candidates = [];
1490
+ if (priorConfigDiffers && priorConfigPath !== null) {
1491
+ candidates.push(priorConfigPath);
1492
+ }
1493
+ const inheritedArrayRaw = savedConnectorConfig.priorHermesConfigPaths;
1494
+ if (Array.isArray(inheritedArrayRaw)) {
1495
+ for (const entry of inheritedArrayRaw) {
1496
+ if (typeof entry === "string" && entry.length > 0) {
1497
+ candidates.push(entry);
1498
+ }
1499
+ }
1500
+ }
1501
+ const inheritedLegacyRaw = savedConnectorConfig.priorHermesConfigPath;
1502
+ if (typeof inheritedLegacyRaw === "string" && inheritedLegacyRaw.length > 0) {
1503
+ candidates.push(inheritedLegacyRaw);
1504
+ }
1505
+ if (shimOutcome.materializedAt !== null) {
1506
+ for (const candidate of candidates) {
1507
+ if (sameHermesConfigTarget(candidate, yamlResult.configPath)) {
1508
+ continue;
1509
+ }
1510
+ if (!isPlausibleHermesConfigPath(candidate)) {
1511
+ continue;
1512
+ }
1513
+ try {
1514
+ const beforeCleanup = fs3.readFileSync(candidate, "utf8");
1515
+ const cleanup = removeHermesConfigFile(candidate);
1516
+ if (cleanup.updated) {
1517
+ hermesPriorConfigMutations.push({ mutatedPath: candidate, priorContent: beforeCleanup });
1518
+ hermesPriorNotes.push(`Cleaned remnic: block from prior-install Hermes config: ${candidate}`);
1519
+ }
1520
+ } catch {
1521
+ hermesPriorNotes.push(
1522
+ `Note: could not clean the remnic: block from the prior-install Hermes config at ${candidate} \u2014 remove it manually.`
1523
+ );
1524
+ }
1525
+ }
1526
+ }
1527
+ const survivingPriors = [];
1528
+ for (const candidate of candidates) {
1529
+ if (sameHermesConfigTarget(candidate, yamlResult.configPath)) {
1530
+ continue;
1531
+ }
1532
+ if (survivingPriors.some((kept) => sameHermesConfigTarget(kept, candidate))) {
1533
+ continue;
1534
+ }
1535
+ let blockSurvives = false;
1536
+ try {
1537
+ blockSurvives = /^remnic:/m.test(fs3.readFileSync(candidate, "utf8"));
1538
+ } catch {
1539
+ blockSurvives = false;
1540
+ }
1541
+ if (blockSurvives) {
1542
+ survivingPriors.push(candidate);
1543
+ }
1544
+ }
1545
+ if (survivingPriors.length > 0) {
1546
+ resolvedConfig.priorHermesConfigPaths = survivingPriors;
1547
+ } else {
1548
+ delete resolvedConfig.priorHermesConfigPaths;
1549
+ }
1550
+ delete resolvedConfig.priorHermesConfigPath;
1551
+ }
995
1552
  try {
996
1553
  writeSecretFileSync(configPath, JSON.stringify(resolvedConfig, null, 2));
997
1554
  } catch (writeErr) {
@@ -1009,7 +1566,7 @@ function installConnector(options) {
1009
1566
  let unlinkSucceeded = false;
1010
1567
  let unlinkErr;
1011
1568
  try {
1012
- fs2.unlinkSync(yamlResult.configPath);
1569
+ fs3.unlinkSync(yamlResult.configPath);
1013
1570
  unlinkSucceeded = true;
1014
1571
  } catch (err) {
1015
1572
  unlinkErr = err;
@@ -1027,15 +1584,70 @@ function installConnector(options) {
1027
1584
  } catch (yamlRollbackErr) {
1028
1585
  yamlRollbackMsg = `config.yaml rollback failed: ${yamlRollbackErr instanceof Error ? yamlRollbackErr.message : String(yamlRollbackErr)}`;
1029
1586
  }
1587
+ let shimRollbackMsg = "no shim changes to roll back";
1588
+ const shimRollbackFailures = [];
1589
+ const priorPersisted = typeof priorShimPathRaw === "string" && priorShimPathRaw.length > 0 ? priorShimPathRaw : null;
1590
+ if (shimOutcome.materializedAt !== null && shimOutcome.createdNew && (priorPersisted === null || !sameShimTarget(shimOutcome.materializedAt, priorPersisted))) {
1591
+ try {
1592
+ removeHermesShim([shimOutcome.materializedAt]);
1593
+ } catch (err) {
1594
+ shimRollbackFailures.push(
1595
+ `could not remove the newly-written shim at ${shimOutcome.materializedAt} (${err instanceof Error ? err.message : String(err)})`
1596
+ );
1597
+ }
1598
+ }
1599
+ if (shimOutcome.priorCleanedAt !== null) {
1600
+ try {
1601
+ materializeHermesShim(shimOutcome.priorCleanedAt);
1602
+ } catch (err) {
1603
+ shimRollbackFailures.push(
1604
+ `could not restore the prior shim at ${shimOutcome.priorCleanedAt} (${err instanceof Error ? err.message : String(err)})`
1605
+ );
1606
+ }
1607
+ }
1608
+ for (const cleanedPath of cleanedInheritedShims) {
1609
+ if (shimOutcome.priorCleanedAt !== null && sameShimTarget(cleanedPath, shimOutcome.priorCleanedAt)) {
1610
+ continue;
1611
+ }
1612
+ try {
1613
+ materializeHermesShim(cleanedPath);
1614
+ } catch (err) {
1615
+ shimRollbackFailures.push(
1616
+ `could not restore the prior shim at ${cleanedPath} (${err instanceof Error ? err.message : String(err)})`
1617
+ );
1618
+ }
1619
+ }
1620
+ if (shimOutcome.materializedAt !== null || shimOutcome.priorCleanedAt !== null || cleanedInheritedShims.length > 0) {
1621
+ shimRollbackMsg = shimRollbackFailures.length === 0 ? "plugin shim changes rolled back" : `plugin shim rollback incomplete: ${shimRollbackFailures.join("; ")}`;
1622
+ }
1623
+ let priorConfigRollbackMsg = "no prior-config changes to roll back";
1624
+ if (hermesPriorConfigMutations.length > 0) {
1625
+ const priorConfigRollbackFailures = [];
1626
+ for (const mutation of [...hermesPriorConfigMutations].reverse()) {
1627
+ try {
1628
+ writeSecretFileSync(mutation.mutatedPath, mutation.priorContent);
1629
+ } catch (restoreErr) {
1630
+ priorConfigRollbackFailures.push(
1631
+ `could not restore ${mutation.mutatedPath} (${restoreErr instanceof Error ? restoreErr.message : String(restoreErr)})`
1632
+ );
1633
+ }
1634
+ }
1635
+ priorConfigRollbackMsg = priorConfigRollbackFailures.length === 0 ? "prior-install config(s) restored" : `prior-install config rollback incomplete: ${priorConfigRollbackFailures.join("; ")}`;
1636
+ }
1030
1637
  const urgentSuffix = tokenRollbackFailed ? ` tokens.json may be in an inconsistent state \u2014 manually restore hermes token with 'remnic token generate hermes'.` : "";
1031
1638
  return {
1032
1639
  connectorId: options.connectorId,
1033
1640
  status: "error",
1034
- message: `Hermes install aborted: connector config write failed \u2014 connector directory may not be writable. Rollback: ${tokenRollbackMsg}; ${yamlRollbackMsg}.${urgentSuffix} Resolve the permission issue, then reinstall.`
1641
+ message: `Hermes install aborted: connector config write failed \u2014 connector directory may not be writable. Rollback: ${tokenRollbackMsg}; ${yamlRollbackMsg}; ${shimRollbackMsg}; ${priorConfigRollbackMsg}.${urgentSuffix} Resolve the permission issue, then reinstall.`
1035
1642
  };
1036
1643
  }
1037
1644
  const notes = [];
1038
1645
  notes.push(`Updated Hermes config: ${yamlResult.configPath}`);
1646
+ notes.push(...shimOutcome.notes);
1647
+ notes.push(...hermesPriorNotes);
1648
+ notes.push(
1649
+ "Next step: set `memory.provider: remnic` (and `memory_enabled: true`) in your Hermes config.yaml to activate the provider."
1650
+ );
1039
1651
  if (hermesProfile === "default") {
1040
1652
  const legacyDefaultConfigPath = hermesDefaultProfileConfigPath();
1041
1653
  if (!sameHermesConfigTarget(yamlResult.configPath, legacyDefaultConfigPath)) {
@@ -1137,9 +1749,9 @@ function installConnector(options) {
1137
1749
  if (options.connectorId === "weclone") {
1138
1750
  try {
1139
1751
  let proxyConfigPath = null;
1140
- if (existing && fs2.existsSync(configPath)) {
1752
+ if (existing && fs3.existsSync(configPath)) {
1141
1753
  try {
1142
- const savedRegistryConfig = JSON.parse(fs2.readFileSync(configPath, "utf8"));
1754
+ const savedRegistryConfig = JSON.parse(fs3.readFileSync(configPath, "utf8"));
1143
1755
  if (typeof savedRegistryConfig.proxyConfigPath === "string" && savedRegistryConfig.proxyConfigPath.length > 0) {
1144
1756
  proxyConfigPath = savedRegistryConfig.proxyConfigPath;
1145
1757
  }
@@ -1155,12 +1767,12 @@ function installConnector(options) {
1155
1767
  priorConfig: prior ? safeParseJson(prior) : null,
1156
1768
  authToken: tokenEntry?.token
1157
1769
  });
1158
- fs2.mkdirSync(path2.dirname(proxyConfigPath), { recursive: true });
1770
+ fs3.mkdirSync(path3.dirname(proxyConfigPath), { recursive: true });
1159
1771
  weCloneProxyHandleRollback = () => {
1160
1772
  try {
1161
1773
  if (prior === null) {
1162
- if (fs2.existsSync(proxyConfigPath)) {
1163
- fs2.unlinkSync(proxyConfigPath);
1774
+ if (fs3.existsSync(proxyConfigPath)) {
1775
+ fs3.unlinkSync(proxyConfigPath);
1164
1776
  }
1165
1777
  } else {
1166
1778
  writeSecretFileSync(proxyConfigPath, prior);
@@ -1270,13 +1882,13 @@ function removeConnector(connectorId) {
1270
1882
  message: `Removal aborted: invalid connector ID ${JSON.stringify(connectorId)}. Connector IDs must match [A-Za-z0-9][A-Za-z0-9._-]*.`
1271
1883
  };
1272
1884
  }
1273
- const configPath = path2.join(configDir, `${connectorId}.json`);
1885
+ const configPath = path3.join(configDir, `${connectorId}.json`);
1274
1886
  let codexHomeOverride = null;
1275
1887
  let savedInstallExtension = void 0;
1276
1888
  let configParsed = false;
1277
- if (connectorId === "codex-cli" && fs2.existsSync(configPath)) {
1889
+ if (connectorId === "codex-cli" && fs3.existsSync(configPath)) {
1278
1890
  try {
1279
- const parsed = JSON.parse(fs2.readFileSync(configPath, "utf8"));
1891
+ const parsed = JSON.parse(fs3.readFileSync(configPath, "utf8"));
1280
1892
  configParsed = true;
1281
1893
  if (typeof parsed.codexHome === "string" && parsed.codexHome.length > 0) {
1282
1894
  codexHomeOverride = parsed.codexHome;
@@ -1291,7 +1903,50 @@ function removeConnector(connectorId) {
1291
1903
  );
1292
1904
  }
1293
1905
  }
1294
- if (!fs2.existsSync(configPath)) {
1906
+ let savedHermesShimPath = null;
1907
+ let savedHermesConfigPath = null;
1908
+ const savedPriorHermesConfigPaths = [];
1909
+ const savedPriorHermesShimPaths = [];
1910
+ if (connectorId === "hermes" && fs3.existsSync(configPath)) {
1911
+ try {
1912
+ const parsedRaw = JSON.parse(fs3.readFileSync(configPath, "utf8"));
1913
+ if (parsedRaw === null || typeof parsedRaw !== "object" || Array.isArray(parsedRaw)) {
1914
+ throw new Error("hermes.json is not a JSON object");
1915
+ }
1916
+ const parsed = parsedRaw;
1917
+ if (typeof parsed.pluginShimPath === "string" && parsed.pluginShimPath.length > 0) {
1918
+ savedHermesShimPath = parsed.pluginShimPath;
1919
+ }
1920
+ if (Array.isArray(parsed.priorPluginShimPaths)) {
1921
+ for (const entry of parsed.priorPluginShimPaths) {
1922
+ if (typeof entry === "string" && entry.length > 0) {
1923
+ savedPriorHermesShimPaths.push(entry);
1924
+ }
1925
+ }
1926
+ }
1927
+ if (typeof parsed.hermesConfigPath === "string" && parsed.hermesConfigPath.length > 0) {
1928
+ savedHermesConfigPath = parsed.hermesConfigPath;
1929
+ }
1930
+ if (Array.isArray(parsed.priorHermesConfigPaths)) {
1931
+ for (const entry of parsed.priorHermesConfigPaths) {
1932
+ if (typeof entry === "string" && entry.length > 0) {
1933
+ savedPriorHermesConfigPaths.push(entry);
1934
+ }
1935
+ }
1936
+ }
1937
+ if (typeof parsed.priorHermesConfigPath === "string" && parsed.priorHermesConfigPath.length > 0) {
1938
+ savedPriorHermesConfigPaths.push(parsed.priorHermesConfigPath);
1939
+ }
1940
+ } catch {
1941
+ return {
1942
+ connectorId,
1943
+ configPath,
1944
+ status: "error",
1945
+ message: `Removal aborted: ${configPath} is malformed and cannot be parsed. It records where the Hermes plugin shim was installed; removing the connector without it could orphan the shim. Fix or delete the file (and remove any plugins/remnic/__init__.py under your Hermes home manually), then re-run removal.`
1946
+ };
1947
+ }
1948
+ }
1949
+ if (!fs3.existsSync(configPath)) {
1295
1950
  let staleTokenRevoked = false;
1296
1951
  try {
1297
1952
  staleTokenRevoked = revokeToken(connectorId);
@@ -1308,7 +1963,7 @@ function removeConnector(connectorId) {
1308
1963
  let storedProfile = "default";
1309
1964
  if (connectorId === "hermes") {
1310
1965
  try {
1311
- const stored = JSON.parse(fs2.readFileSync(configPath, "utf8"));
1966
+ const stored = JSON.parse(fs3.readFileSync(configPath, "utf8"));
1312
1967
  if (typeof stored?.profile === "string") storedProfile = stored.profile;
1313
1968
  } catch {
1314
1969
  }
@@ -1317,7 +1972,7 @@ function removeConnector(connectorId) {
1317
1972
  let weCloneRegistryParseFailed = false;
1318
1973
  if (connectorId === "weclone") {
1319
1974
  try {
1320
- const stored = JSON.parse(fs2.readFileSync(configPath, "utf8"));
1975
+ const stored = JSON.parse(fs3.readFileSync(configPath, "utf8"));
1321
1976
  if (typeof stored.proxyConfigPath === "string" && stored.proxyConfigPath.length > 0) {
1322
1977
  weCloneProxyConfigPath = stored.proxyConfigPath;
1323
1978
  }
@@ -1343,7 +1998,7 @@ function removeConnector(connectorId) {
1343
1998
  reason: "config-parse-failed"
1344
1999
  };
1345
2000
  }
1346
- if (connectorId === "codex-cli" && fs2.existsSync(configPath) && !configParsed) {
2001
+ if (connectorId === "codex-cli" && fs3.existsSync(configPath) && !configParsed) {
1347
2002
  console.warn(
1348
2003
  "[remnic/connectors] removeConnector: codex-cli.json is malformed \u2014 aborting removal to preserve provenance. Fix or delete " + configPath + " manually and retry."
1349
2004
  );
@@ -1366,8 +2021,57 @@ function removeConnector(connectorId) {
1366
2021
  extensionMessage = extResult.removed ? ` (memory extension removed: ${extResult.remnicExtensionDir})` : " (no memory extension present)";
1367
2022
  }
1368
2023
  }
2024
+ if (connectorId === "hermes") {
2025
+ const preflightCandidates = [];
2026
+ try {
2027
+ preflightCandidates.push(...hermesConfigCleanupPaths(storedProfile));
2028
+ } catch {
2029
+ }
2030
+ if (savedHermesConfigPath !== null && isPlausibleHermesConfigPath(savedHermesConfigPath)) {
2031
+ preflightCandidates.push(savedHermesConfigPath);
2032
+ }
2033
+ preflightCandidates.push(...savedPriorHermesConfigPaths.filter(isPlausibleHermesConfigPath));
2034
+ const uniqueCandidates = [];
2035
+ for (const candidate of preflightCandidates) {
2036
+ if (!uniqueCandidates.some((kept) => sameHermesConfigTarget(kept, candidate))) {
2037
+ uniqueCandidates.push(candidate);
2038
+ }
2039
+ }
2040
+ const blocked = [];
2041
+ for (const candidate of uniqueCandidates) {
2042
+ let needsCleanup = false;
2043
+ try {
2044
+ needsCleanup = /^remnic:/m.test(fs3.readFileSync(candidate, "utf8"));
2045
+ } catch {
2046
+ needsCleanup = false;
2047
+ }
2048
+ if (!needsCleanup) {
2049
+ continue;
2050
+ }
2051
+ try {
2052
+ assertConfigComponentsNotSymlinked(candidate);
2053
+ } catch {
2054
+ blocked.push(`${candidate} (symlinked path component)`);
2055
+ continue;
2056
+ }
2057
+ try {
2058
+ fs3.accessSync(candidate, fs3.constants.W_OK);
2059
+ fs3.accessSync(path3.dirname(candidate), fs3.constants.W_OK);
2060
+ } catch {
2061
+ blocked.push(`${candidate} (not writable)`);
2062
+ }
2063
+ }
2064
+ if (blocked.length > 0) {
2065
+ return {
2066
+ connectorId,
2067
+ configPath,
2068
+ status: "error",
2069
+ message: `Hermes remove aborted: the following config.yaml path(s) hold a remnic: block but cannot be cleaned: ${blocked.join(", ")}. The connector registry entry, provenance, and token were left untouched \u2014 fix the permissions or resolve the symlink and re-run removal.`
2070
+ };
2071
+ }
2072
+ }
1369
2073
  try {
1370
- fs2.unlinkSync(configPath);
2074
+ fs3.unlinkSync(configPath);
1371
2075
  } catch (unlinkErr) {
1372
2076
  const sanitizedErr = unlinkErr instanceof Error ? unlinkErr.message : String(unlinkErr);
1373
2077
  return {
@@ -1393,14 +2097,14 @@ function removeConnector(connectorId) {
1393
2097
  "WeClone proxy config cleanup skipped: no persisted path found in saved config (likely a legacy install predating proxyConfigPath provenance)."
1394
2098
  );
1395
2099
  } else {
1396
- const expectedSuffix = path2.join("connectors", "weclone.json");
1397
- const isSafePath = path2.isAbsolute(weCloneProxyConfigPath) && weCloneProxyConfigPath.endsWith(expectedSuffix);
2100
+ const expectedSuffix = path3.join("connectors", "weclone.json");
2101
+ const isSafePath = path3.isAbsolute(weCloneProxyConfigPath) && weCloneProxyConfigPath.endsWith(expectedSuffix);
1398
2102
  if (!isSafePath) {
1399
2103
  weCloneProxyDeleteFailed = `Proxy config path ${JSON.stringify(weCloneProxyConfigPath)} failed safety validation (must be absolute and end with "${expectedSuffix}"). Refusing to delete \u2014 remove the file manually if it exists.`;
1400
2104
  } else {
1401
2105
  try {
1402
- if (fs2.existsSync(weCloneProxyConfigPath)) {
1403
- fs2.unlinkSync(weCloneProxyConfigPath);
2106
+ if (fs3.existsSync(weCloneProxyConfigPath)) {
2107
+ fs3.unlinkSync(weCloneProxyConfigPath);
1404
2108
  notes.push(`Removed WeClone proxy config: ${weCloneProxyConfigPath}`);
1405
2109
  }
1406
2110
  } catch (err) {
@@ -1420,16 +2124,42 @@ function removeConnector(connectorId) {
1420
2124
  }
1421
2125
  if (connectorId === "hermes") {
1422
2126
  try {
1423
- const yamlResult = removeHermesConfig({ profile: storedProfile });
2127
+ const shimCandidates = [];
2128
+ if (savedHermesShimPath !== null) {
2129
+ shimCandidates.push(savedHermesShimPath);
2130
+ }
2131
+ shimCandidates.push(...savedPriorHermesShimPaths);
2132
+ try {
2133
+ shimCandidates.push(hermesShimPath());
2134
+ } catch {
2135
+ }
2136
+ const shimResult = removeHermesShim(shimCandidates);
2137
+ if (shimResult.notes.length > 0) {
2138
+ notes.push(shimResult.notes.join("; "));
2139
+ }
2140
+ } catch (shimErr) {
2141
+ notes.push(
2142
+ `Hermes plugin shim cleanup skipped: ${shimErr instanceof Error ? shimErr.message : String(shimErr)}`
2143
+ );
2144
+ }
2145
+ try {
2146
+ const yamlResult = removeHermesConfig({
2147
+ profile: storedProfile,
2148
+ extraConfigPaths: [
2149
+ ...savedHermesConfigPath !== null ? [savedHermesConfigPath] : [],
2150
+ ...savedPriorHermesConfigPaths
2151
+ ]
2152
+ });
1424
2153
  if (yamlResult.updated) {
1425
2154
  notes.push(`Removed remnic: block from Hermes config: ${yamlResult.configPath}`);
1426
2155
  } else if (yamlResult.reason?.startsWith("Hermes config cleanup partially failed:")) {
1427
2156
  const tokenStatus = tokenRevoked ? "the connector registry config was deleted and the token was revoked" : "the connector registry config was deleted but TOKEN REVOCATION ALSO FAILED \u2014 inspect ~/.remnic/tokens.json and revoke manually";
2157
+ const shimSuffix = notes.length > 0 ? ` Completed cleanup: ${notes.join("; ")}.` : "";
1428
2158
  return {
1429
2159
  connectorId,
1430
2160
  configPath,
1431
2161
  status: "error",
1432
- message: `Hermes remove partially succeeded: ${tokenStatus}, but ${yamlResult.reason}. Updated paths: ${yamlResult.configPath}. Manually remove any stale remnic: block and token material from the failed Hermes config path.`
2162
+ message: `Hermes remove partially succeeded: ${tokenStatus}, but ${yamlResult.reason}. Updated paths: ${yamlResult.configPath}. Manually remove any stale remnic: block and token material from the failed Hermes config path.` + shimSuffix
1433
2163
  };
1434
2164
  } else if (yamlResult.skipped) {
1435
2165
  notes.push(`Hermes config cleanup skipped: ${yamlResult.reason}`);
@@ -1465,18 +2195,18 @@ function sanitizeHermesProfile(profile) {
1465
2195
  }
1466
2196
  function hermesConfigPath(profile) {
1467
2197
  const safeProfile = sanitizeHermesProfile(profile);
1468
- const hermesRoot = path2.resolve(resolveHomeDir(), ".hermes");
1469
- const rootConfigPath = path2.join(hermesRoot, "config.yaml");
1470
- const profilesRoot = path2.join(hermesRoot, "profiles");
2198
+ const hermesRoot = resolveHermesRoot();
2199
+ const rootConfigPath = path3.join(hermesRoot, "config.yaml");
2200
+ const profilesRoot = path3.join(hermesRoot, "profiles");
1471
2201
  if (safeProfile === "default") {
1472
- const defaultProfileDir = path2.join(profilesRoot, safeProfile);
1473
- if (isFile(rootConfigPath) || !fs2.existsSync(rootConfigPath) && !isDirectory(defaultProfileDir)) {
2202
+ const defaultProfileDir = path3.join(profilesRoot, safeProfile);
2203
+ if (isFile(rootConfigPath) || !fs3.existsSync(rootConfigPath) && !isDirectory(defaultProfileDir)) {
1474
2204
  return rootConfigPath;
1475
2205
  }
1476
2206
  }
1477
- const cfgPath = path2.resolve(profilesRoot, safeProfile, "config.yaml");
1478
- const rel = path2.relative(profilesRoot, cfgPath);
1479
- if (rel.startsWith("..") || path2.isAbsolute(rel)) {
2207
+ const cfgPath = path3.resolve(profilesRoot, safeProfile, "config.yaml");
2208
+ const rel = path3.relative(profilesRoot, cfgPath);
2209
+ if (rel.startsWith("..") || path3.isAbsolute(rel)) {
1480
2210
  throw new Error(
1481
2211
  `Invalid Hermes profile path: resolved outside ${profilesRoot}`
1482
2212
  );
@@ -1485,31 +2215,30 @@ function hermesConfigPath(profile) {
1485
2215
  }
1486
2216
  function isDirectory(filePath) {
1487
2217
  try {
1488
- return fs2.statSync(filePath).isDirectory();
2218
+ return fs3.statSync(filePath).isDirectory();
1489
2219
  } catch {
1490
2220
  return false;
1491
2221
  }
1492
2222
  }
1493
2223
  function isFile(filePath) {
1494
2224
  try {
1495
- return fs2.statSync(filePath).isFile();
2225
+ return fs3.statSync(filePath).isFile();
1496
2226
  } catch {
1497
2227
  return false;
1498
2228
  }
1499
2229
  }
1500
2230
  function hermesConfigTarget(filePath) {
1501
2231
  try {
1502
- return fs2.realpathSync.native(filePath);
2232
+ return fs3.realpathSync.native(filePath);
1503
2233
  } catch {
1504
- return path2.resolve(filePath);
2234
+ return path3.resolve(filePath);
1505
2235
  }
1506
2236
  }
1507
2237
  function sameHermesConfigTarget(leftPath, rightPath) {
1508
2238
  return hermesConfigTarget(leftPath) === hermesConfigTarget(rightPath);
1509
2239
  }
1510
2240
  function hermesDefaultProfileConfigPath() {
1511
- const hermesRoot = path2.resolve(resolveHomeDir(), ".hermes");
1512
- return path2.join(hermesRoot, "profiles", "default", "config.yaml");
2241
+ return path3.join(resolveHermesRoot(), "profiles", "default", "config.yaml");
1513
2242
  }
1514
2243
  function hermesConfigCleanupPaths(profile) {
1515
2244
  const cfgPath = hermesConfigPath(profile);
@@ -1565,29 +2294,29 @@ function sanitizeHermesPort(port) {
1565
2294
  return numeric;
1566
2295
  }
1567
2296
  function writeSecretFileSync(filePath, data) {
1568
- const dir = path2.dirname(filePath);
1569
- const base = path2.basename(filePath);
1570
- const tmpPath = path2.join(
2297
+ const dir = path3.dirname(filePath);
2298
+ const base = path3.basename(filePath);
2299
+ const tmpPath = path3.join(
1571
2300
  dir,
1572
2301
  `.${base}.${process.pid}.${Date.now()}.${Math.random().toString(16).slice(2)}.tmp`
1573
2302
  );
1574
2303
  let wroteTemp = false;
1575
2304
  try {
1576
- fs2.writeFileSync(tmpPath, data, { mode: 384, flag: "wx" });
2305
+ fs3.writeFileSync(tmpPath, data, { mode: 384, flag: "wx" });
1577
2306
  wroteTemp = true;
1578
2307
  try {
1579
- fs2.chmodSync(tmpPath, 384);
2308
+ fs3.chmodSync(tmpPath, 384);
1580
2309
  } catch {
1581
2310
  }
1582
- fs2.renameSync(tmpPath, filePath);
2311
+ fs3.renameSync(tmpPath, filePath);
1583
2312
  try {
1584
- fs2.chmodSync(filePath, 384);
2313
+ fs3.chmodSync(filePath, 384);
1585
2314
  } catch {
1586
2315
  }
1587
2316
  } catch (err) {
1588
2317
  if (wroteTemp) {
1589
2318
  try {
1590
- fs2.unlinkSync(tmpPath);
2319
+ fs3.unlinkSync(tmpPath);
1591
2320
  } catch {
1592
2321
  }
1593
2322
  }
@@ -1595,8 +2324,11 @@ function writeSecretFileSync(filePath, data) {
1595
2324
  }
1596
2325
  }
1597
2326
  function upsertHermesConfig(opts) {
1598
- const cfgPath = hermesConfigPath(opts.profile);
1599
- const profileDir = path2.dirname(cfgPath);
2327
+ return upsertHermesConfigAt(hermesConfigPath(opts.profile), opts);
2328
+ }
2329
+ function upsertHermesConfigAt(cfgPath, opts) {
2330
+ assertConfigComponentsNotSymlinked(cfgPath);
2331
+ const profileDir = path3.dirname(cfgPath);
1600
2332
  const safeHost = sanitizeHermesHost(opts.host);
1601
2333
  const safePort = sanitizeHermesPort(opts.port);
1602
2334
  if (!/^[A-Za-z0-9_]+$/.test(opts.token)) {
@@ -1616,11 +2348,11 @@ function upsertHermesConfig(opts) {
1616
2348
  ` port: ${safePort}`,
1617
2349
  ` token: "${opts.token}"`
1618
2350
  ].join("\n");
1619
- if (!fs2.existsSync(cfgPath)) {
2351
+ if (!fs3.existsSync(cfgPath)) {
1620
2352
  writeSecretFileSync(cfgPath, block + "\n");
1621
2353
  return { updated: true, skipped: false, configPath: cfgPath, priorContent: null };
1622
2354
  }
1623
- const raw = fs2.readFileSync(cfgPath, "utf8");
2355
+ const raw = fs3.readFileSync(cfgPath, "utf8");
1624
2356
  const hasRemnicBlock = /^remnic:/m.test(raw);
1625
2357
  if (!hasRemnicBlock) {
1626
2358
  const separator = raw.endsWith("\n") ? "\n" : "\n\n";
@@ -1678,7 +2410,33 @@ function upsertHermesConfig(opts) {
1678
2410
  return { updated: true, skipped: false, configPath: cfgPath, priorContent: raw };
1679
2411
  }
1680
2412
  function removeHermesConfig(opts) {
1681
- const cfgPaths = hermesConfigCleanupPaths(opts.profile);
2413
+ let envCfgPaths;
2414
+ let envResolutionError = null;
2415
+ try {
2416
+ envCfgPaths = [...hermesConfigCleanupPaths(opts.profile)];
2417
+ } catch (err) {
2418
+ envCfgPaths = [];
2419
+ envResolutionError = err instanceof Error ? err.message : String(err);
2420
+ }
2421
+ const cfgPaths = [];
2422
+ for (const candidate of envCfgPaths) {
2423
+ if (!cfgPaths.some((existing) => sameHermesConfigTarget(existing, candidate))) {
2424
+ cfgPaths.push(candidate);
2425
+ }
2426
+ }
2427
+ for (const extra of opts.extraConfigPaths ?? []) {
2428
+ if (isPlausibleHermesConfigPath(extra) && !cfgPaths.some((existing) => sameHermesConfigTarget(existing, extra))) {
2429
+ cfgPaths.push(extra);
2430
+ }
2431
+ }
2432
+ if (cfgPaths.length === 0) {
2433
+ return {
2434
+ updated: false,
2435
+ skipped: true,
2436
+ reason: envResolutionError ? `Hermes config cleanup failed: ${envResolutionError}` : "Hermes config.yaml not found",
2437
+ configPath: "<unresolved>"
2438
+ };
2439
+ }
1682
2440
  const results = cfgPaths.map((cfgPath) => {
1683
2441
  try {
1684
2442
  return removeHermesConfigFile(cfgPath);
@@ -1715,15 +2473,29 @@ function removeHermesConfig(opts) {
1715
2473
  return cleanupFailure;
1716
2474
  }
1717
2475
  const existingWithoutBlock = results.find((result) => result.reason !== "Hermes config.yaml not found");
1718
- return existingWithoutBlock ?? results[0] ?? {
2476
+ const fallbackResult = existingWithoutBlock ?? results[0];
2477
+ if (fallbackResult) {
2478
+ return fallbackResult;
2479
+ }
2480
+ return {
1719
2481
  updated: false,
1720
2482
  skipped: true,
1721
2483
  reason: "Hermes config.yaml not found",
1722
- configPath: hermesConfigPath(opts.profile)
2484
+ configPath: cfgPaths[0] ?? "<unresolved>"
1723
2485
  };
1724
2486
  }
1725
2487
  function removeHermesConfigFile(cfgPath) {
1726
- if (!fs2.existsSync(cfgPath)) {
2488
+ try {
2489
+ assertConfigComponentsNotSymlinked(cfgPath);
2490
+ } catch (err) {
2491
+ return {
2492
+ updated: false,
2493
+ skipped: true,
2494
+ reason: `Hermes config cleanup skipped: ${err instanceof Error ? err.message : String(err)}`,
2495
+ configPath: cfgPath
2496
+ };
2497
+ }
2498
+ if (!fs3.existsSync(cfgPath)) {
1727
2499
  return {
1728
2500
  updated: false,
1729
2501
  skipped: true,
@@ -1731,7 +2503,7 @@ function removeHermesConfigFile(cfgPath) {
1731
2503
  configPath: cfgPath
1732
2504
  };
1733
2505
  }
1734
- const raw = fs2.readFileSync(cfgPath, "utf8");
2506
+ const raw = fs3.readFileSync(cfgPath, "utf8");
1735
2507
  if (!/^remnic:/m.test(raw)) {
1736
2508
  return {
1737
2509
  updated: false,
@@ -1828,15 +2600,15 @@ async function doctorConnector(connectorId) {
1828
2600
  healthy: false
1829
2601
  };
1830
2602
  }
1831
- const configPath = path2.join(getConnectorsDir(), `${connectorId}.json`);
2603
+ const configPath = path3.join(getConnectorsDir(), `${connectorId}.json`);
1832
2604
  const checks = [];
1833
2605
  checks.push({
1834
2606
  name: "Config file",
1835
- ok: fs2.existsSync(configPath),
2607
+ ok: fs3.existsSync(configPath),
1836
2608
  detail: configPath
1837
2609
  });
1838
2610
  try {
1839
- const raw = fs2.readFileSync(configPath, "utf8");
2611
+ const raw = fs3.readFileSync(configPath, "utf8");
1840
2612
  JSON.parse(raw);
1841
2613
  checks.push({ name: "Config valid", ok: true, detail: "OK" });
1842
2614
  } catch (e) {
@@ -1860,7 +2632,7 @@ async function doctorConnector(connectorId) {
1860
2632
  }
1861
2633
  const memoryDir = instance.config.memoryDir;
1862
2634
  if (memoryDir) {
1863
- if (fs2.existsSync(memoryDir)) {
2635
+ if (fs3.existsSync(memoryDir)) {
1864
2636
  checks.push({ name: "Memory directory", ok: true, detail: memoryDir });
1865
2637
  } else {
1866
2638
  checks.push({ name: "Memory directory", ok: false, detail: `Not found: ${memoryDir}` });
@@ -1874,32 +2646,32 @@ var CODEX_EXTENSIONS_SUBDIR = "memories_extensions";
1874
2646
  var REMNIC_EXTENSION_DIR_NAME = "remnic";
1875
2647
  function resolveCodexHome(override) {
1876
2648
  if (override && typeof override === "string" && override.trim().length > 0) {
1877
- return path2.resolve(override.trim());
2649
+ return path3.resolve(override.trim());
1878
2650
  }
1879
2651
  const envHome = readEnvVar("CODEX_HOME");
1880
2652
  if (envHome && envHome.trim().length > 0) {
1881
- return path2.resolve(envHome.trim());
2653
+ return path3.resolve(envHome.trim());
1882
2654
  }
1883
2655
  const home = readEnvVar("HOME") || readEnvVar("USERPROFILE") || resolveHomeDir();
1884
- return path2.resolve(home, ".codex");
2656
+ return path3.resolve(home, ".codex");
1885
2657
  }
1886
2658
  function resolveCodexMemoryExtensionPaths(codexHomeOverride) {
1887
2659
  const codexHome = resolveCodexHome(codexHomeOverride);
1888
- const memoriesDir = path2.join(codexHome, CODEX_MEMORIES_SUBDIR);
1889
- const extensionsRoot = path2.join(path2.dirname(memoriesDir), CODEX_EXTENSIONS_SUBDIR);
1890
- const remnicExtensionDir = path2.join(extensionsRoot, REMNIC_EXTENSION_DIR_NAME);
2660
+ const memoriesDir = path3.join(codexHome, CODEX_MEMORIES_SUBDIR);
2661
+ const extensionsRoot = path3.join(path3.dirname(memoriesDir), CODEX_EXTENSIONS_SUBDIR);
2662
+ const remnicExtensionDir = path3.join(extensionsRoot, REMNIC_EXTENSION_DIR_NAME);
1891
2663
  return { codexHome, memoriesDir, extensionsRoot, remnicExtensionDir };
1892
2664
  }
1893
2665
  function locatePluginCodexExtensionSource(override) {
1894
2666
  if (override && typeof override === "string" && override.trim().length > 0) {
1895
- const resolved = path2.resolve(override.trim());
1896
- if (fs2.existsSync(resolved) && fs2.statSync(resolved).isDirectory()) {
2667
+ const resolved = path3.resolve(override.trim());
2668
+ if (fs3.existsSync(resolved) && fs3.statSync(resolved).isDirectory()) {
1897
2669
  return resolved;
1898
2670
  }
1899
2671
  throw new Error(`Codex extension source directory not found: ${resolved}`);
1900
2672
  }
1901
- const EXTENSION_SUBPATH = path2.join("memories_extensions", "remnic");
1902
- const WORKSPACE_RELATIVE_PATH = path2.join(
2673
+ const EXTENSION_SUBPATH = path3.join("memories_extensions", "remnic");
2674
+ const WORKSPACE_RELATIVE_PATH = path3.join(
1903
2675
  "packages",
1904
2676
  "plugin-codex",
1905
2677
  "memories_extensions",
@@ -1907,15 +2679,15 @@ function locatePluginCodexExtensionSource(override) {
1907
2679
  );
1908
2680
  const searched = [];
1909
2681
  try {
1910
- const moduleDir = path2.dirname(fileURLToPath(import.meta.url));
1911
- const bundledCandidate = path2.join(moduleDir, "codex");
2682
+ const moduleDir = path3.dirname(fileURLToPath(import.meta.url));
2683
+ const bundledCandidate = path3.join(moduleDir, "codex");
1912
2684
  searched.push(bundledCandidate);
1913
- if (fs2.existsSync(bundledCandidate) && fs2.statSync(bundledCandidate).isDirectory()) {
2685
+ if (fs3.existsSync(bundledCandidate) && fs3.statSync(bundledCandidate).isDirectory()) {
1914
2686
  return bundledCandidate;
1915
2687
  }
1916
- const distConnectorsCandidate = path2.join(moduleDir, "connectors", "codex");
2688
+ const distConnectorsCandidate = path3.join(moduleDir, "connectors", "codex");
1917
2689
  searched.push(distConnectorsCandidate);
1918
- if (fs2.existsSync(distConnectorsCandidate) && fs2.statSync(distConnectorsCandidate).isDirectory()) {
2690
+ if (fs3.existsSync(distConnectorsCandidate) && fs3.statSync(distConnectorsCandidate).isDirectory()) {
1919
2691
  return distConnectorsCandidate;
1920
2692
  }
1921
2693
  } catch {
@@ -1923,19 +2695,19 @@ function locatePluginCodexExtensionSource(override) {
1923
2695
  try {
1924
2696
  const requireFromHere = createRequire(import.meta.url);
1925
2697
  const pluginPkgJsonPath = requireFromHere.resolve("@remnic/plugin-codex/package.json");
1926
- const pluginPkgRoot = path2.dirname(pluginPkgJsonPath);
1927
- const candidate = path2.join(pluginPkgRoot, EXTENSION_SUBPATH);
2698
+ const pluginPkgRoot = path3.dirname(pluginPkgJsonPath);
2699
+ const candidate = path3.join(pluginPkgRoot, EXTENSION_SUBPATH);
1928
2700
  searched.push(candidate);
1929
- if (fs2.existsSync(candidate) && fs2.statSync(candidate).isDirectory()) {
2701
+ if (fs3.existsSync(candidate) && fs3.statSync(candidate).isDirectory()) {
1930
2702
  return candidate;
1931
2703
  }
1932
2704
  } catch {
1933
2705
  }
1934
2706
  try {
1935
- const moduleDir = path2.dirname(fileURLToPath(import.meta.url));
2707
+ const moduleDir = path3.dirname(fileURLToPath(import.meta.url));
1936
2708
  let dir = moduleDir;
1937
2709
  for (let depth = 0; depth < 8; depth += 1) {
1938
- const candidate = path2.join(
2710
+ const candidate = path3.join(
1939
2711
  dir,
1940
2712
  "node_modules",
1941
2713
  "@remnic",
@@ -1943,10 +2715,10 @@ function locatePluginCodexExtensionSource(override) {
1943
2715
  EXTENSION_SUBPATH
1944
2716
  );
1945
2717
  searched.push(candidate);
1946
- if (fs2.existsSync(candidate) && fs2.statSync(candidate).isDirectory()) {
2718
+ if (fs3.existsSync(candidate) && fs3.statSync(candidate).isDirectory()) {
1947
2719
  return candidate;
1948
2720
  }
1949
- const parent = path2.dirname(dir);
2721
+ const parent = path3.dirname(dir);
1950
2722
  if (parent === dir) break;
1951
2723
  dir = parent;
1952
2724
  }
@@ -1954,19 +2726,19 @@ function locatePluginCodexExtensionSource(override) {
1954
2726
  }
1955
2727
  const anchors = [];
1956
2728
  try {
1957
- anchors.push(path2.dirname(fileURLToPath(import.meta.url)));
2729
+ anchors.push(path3.dirname(fileURLToPath(import.meta.url)));
1958
2730
  } catch {
1959
2731
  }
1960
2732
  anchors.push(process.cwd());
1961
2733
  for (const anchor of anchors) {
1962
2734
  let dir = anchor;
1963
2735
  for (let depth = 0; depth < 12; depth += 1) {
1964
- const candidate = path2.join(dir, WORKSPACE_RELATIVE_PATH);
2736
+ const candidate = path3.join(dir, WORKSPACE_RELATIVE_PATH);
1965
2737
  searched.push(candidate);
1966
- if (fs2.existsSync(candidate) && fs2.statSync(candidate).isDirectory()) {
2738
+ if (fs3.existsSync(candidate) && fs3.statSync(candidate).isDirectory()) {
1967
2739
  return candidate;
1968
2740
  }
1969
- const parent = path2.dirname(dir);
2741
+ const parent = path3.dirname(dir);
1970
2742
  if (parent === dir) break;
1971
2743
  dir = parent;
1972
2744
  }
@@ -1977,15 +2749,15 @@ function locatePluginCodexExtensionSource(override) {
1977
2749
  }
1978
2750
  function copyDirRecursiveSync(src, dest) {
1979
2751
  let count = 0;
1980
- fs2.mkdirSync(dest, { recursive: true });
1981
- const entries = fs2.readdirSync(src, { withFileTypes: true });
2752
+ fs3.mkdirSync(dest, { recursive: true });
2753
+ const entries = fs3.readdirSync(src, { withFileTypes: true });
1982
2754
  for (const entry of entries) {
1983
- const from = path2.join(src, entry.name);
1984
- const to = path2.join(dest, entry.name);
2755
+ const from = path3.join(src, entry.name);
2756
+ const to = path3.join(dest, entry.name);
1985
2757
  if (entry.isDirectory()) {
1986
2758
  count += copyDirRecursiveSync(from, to);
1987
2759
  } else if (entry.isFile()) {
1988
- fs2.copyFileSync(from, to);
2760
+ fs3.copyFileSync(from, to);
1989
2761
  count += 1;
1990
2762
  }
1991
2763
  }
@@ -1994,29 +2766,29 @@ function copyDirRecursiveSync(src, dest) {
1994
2766
  function installCodexMemoryExtension(options = {}) {
1995
2767
  const paths = resolveCodexMemoryExtensionPaths(options.codexHome ?? null);
1996
2768
  const sourceDir = locatePluginCodexExtensionSource(options.sourceDir ?? null);
1997
- fs2.mkdirSync(paths.extensionsRoot, { recursive: true });
2769
+ fs3.mkdirSync(paths.extensionsRoot, { recursive: true });
1998
2770
  const tmpPrefix = `.${REMNIC_EXTENSION_DIR_NAME}.tmp-`;
1999
2771
  const STALE_TMP_THRESHOLD_MS = 10 * 60 * 1e3;
2000
2772
  const now = Date.now();
2001
2773
  try {
2002
- const existingEntries = fs2.readdirSync(paths.extensionsRoot);
2774
+ const existingEntries = fs3.readdirSync(paths.extensionsRoot);
2003
2775
  for (const entry of existingEntries) {
2004
2776
  if (!entry.startsWith(tmpPrefix)) continue;
2005
- const stalePath = path2.join(paths.extensionsRoot, entry);
2777
+ const stalePath = path3.join(paths.extensionsRoot, entry);
2006
2778
  try {
2007
- const stat = fs2.statSync(stalePath);
2779
+ const stat = fs3.statSync(stalePath);
2008
2780
  const ageMs = now - stat.mtimeMs;
2009
2781
  if (ageMs < STALE_TMP_THRESHOLD_MS) {
2010
2782
  continue;
2011
2783
  }
2012
- fs2.rmSync(stalePath, { recursive: true, force: true });
2784
+ fs3.rmSync(stalePath, { recursive: true, force: true });
2013
2785
  } catch {
2014
2786
  }
2015
2787
  }
2016
2788
  } catch {
2017
2789
  }
2018
2790
  const tmpName = `${tmpPrefix}${process.pid}-${Date.now()}`;
2019
- const tmpDir = path2.join(paths.extensionsRoot, tmpName);
2791
+ const tmpDir = path3.join(paths.extensionsRoot, tmpName);
2020
2792
  let filesCopied = 0;
2021
2793
  let commitFn = () => {
2022
2794
  };
@@ -2025,16 +2797,16 @@ function installCodexMemoryExtension(options = {}) {
2025
2797
  try {
2026
2798
  filesCopied = copyDirRecursiveSync(sourceDir, tmpDir);
2027
2799
  const backupDir = `${paths.remnicExtensionDir}.bak-${Date.now()}`;
2028
- const hadExisting = fs2.existsSync(paths.remnicExtensionDir);
2800
+ const hadExisting = fs3.existsSync(paths.remnicExtensionDir);
2029
2801
  if (hadExisting) {
2030
- fs2.renameSync(paths.remnicExtensionDir, backupDir);
2802
+ fs3.renameSync(paths.remnicExtensionDir, backupDir);
2031
2803
  }
2032
2804
  try {
2033
- fs2.renameSync(tmpDir, paths.remnicExtensionDir);
2805
+ fs3.renameSync(tmpDir, paths.remnicExtensionDir);
2034
2806
  } catch (renameErr) {
2035
2807
  if (hadExisting) {
2036
2808
  try {
2037
- fs2.renameSync(backupDir, paths.remnicExtensionDir);
2809
+ fs3.renameSync(backupDir, paths.remnicExtensionDir);
2038
2810
  } catch {
2039
2811
  }
2040
2812
  }
@@ -2043,7 +2815,7 @@ function installCodexMemoryExtension(options = {}) {
2043
2815
  commitFn = () => {
2044
2816
  if (hadExisting) {
2045
2817
  try {
2046
- fs2.rmSync(backupDir, { recursive: true, force: true });
2818
+ fs3.rmSync(backupDir, { recursive: true, force: true });
2047
2819
  } catch {
2048
2820
  }
2049
2821
  }
@@ -2051,31 +2823,31 @@ function installCodexMemoryExtension(options = {}) {
2051
2823
  rollbackFn = () => {
2052
2824
  if (hadExisting) {
2053
2825
  try {
2054
- if (fs2.existsSync(paths.remnicExtensionDir)) {
2055
- fs2.rmSync(paths.remnicExtensionDir, { recursive: true, force: true });
2826
+ if (fs3.existsSync(paths.remnicExtensionDir)) {
2827
+ fs3.rmSync(paths.remnicExtensionDir, { recursive: true, force: true });
2056
2828
  }
2057
- fs2.renameSync(backupDir, paths.remnicExtensionDir);
2829
+ fs3.renameSync(backupDir, paths.remnicExtensionDir);
2058
2830
  } catch {
2059
2831
  }
2060
2832
  } else {
2061
2833
  try {
2062
- if (fs2.existsSync(paths.remnicExtensionDir)) {
2063
- fs2.rmSync(paths.remnicExtensionDir, { recursive: true, force: true });
2834
+ if (fs3.existsSync(paths.remnicExtensionDir)) {
2835
+ fs3.rmSync(paths.remnicExtensionDir, { recursive: true, force: true });
2064
2836
  }
2065
2837
  } catch {
2066
2838
  }
2067
2839
  }
2068
2840
  };
2069
2841
  } catch (err) {
2070
- if (fs2.existsSync(tmpDir)) {
2842
+ if (fs3.existsSync(tmpDir)) {
2071
2843
  try {
2072
- fs2.rmSync(tmpDir, { recursive: true, force: true });
2844
+ fs3.rmSync(tmpDir, { recursive: true, force: true });
2073
2845
  } catch {
2074
2846
  }
2075
2847
  }
2076
2848
  throw err;
2077
2849
  }
2078
- const instructionsPath = path2.join(paths.remnicExtensionDir, "instructions.md");
2850
+ const instructionsPath = path3.join(paths.remnicExtensionDir, "instructions.md");
2079
2851
  return {
2080
2852
  ...paths,
2081
2853
  instructionsPath,
@@ -2087,8 +2859,8 @@ function installCodexMemoryExtension(options = {}) {
2087
2859
  function removeCodexMemoryExtension(options = {}) {
2088
2860
  const paths = resolveCodexMemoryExtensionPaths(options.codexHome ?? null);
2089
2861
  let removed = false;
2090
- if (fs2.existsSync(paths.remnicExtensionDir)) {
2091
- fs2.rmSync(paths.remnicExtensionDir, { recursive: true, force: true });
2862
+ if (fs3.existsSync(paths.remnicExtensionDir)) {
2863
+ fs3.rmSync(paths.remnicExtensionDir, { recursive: true, force: true });
2092
2864
  removed = true;
2093
2865
  }
2094
2866
  return { ...paths, removed };
@@ -2099,11 +2871,11 @@ function resolveWeCloneProxyConfigPath() {
2099
2871
  const remnicHome = readEnvVar("REMNIC_HOME");
2100
2872
  const override = remnicHome && remnicHome.length > 0 ? remnicHome : readEnvVar("ENGRAM_HOME");
2101
2873
  if (override && override.length > 0) {
2102
- return path2.resolve(expandTildePath(override), "connectors", WECLONE_PROXY_CONFIG_FILENAME);
2874
+ return path3.resolve(expandTildePath(override), "connectors", WECLONE_PROXY_CONFIG_FILENAME);
2103
2875
  }
2104
2876
  const envHome = readEnvVar("HOME");
2105
2877
  const home = envHome && envHome.length > 0 ? envHome : os.homedir();
2106
- return path2.resolve(
2878
+ return path3.resolve(
2107
2879
  home,
2108
2880
  WECLONE_PROXY_CONFIG_DIRNAME,
2109
2881
  "connectors",
@@ -2112,8 +2884,8 @@ function resolveWeCloneProxyConfigPath() {
2112
2884
  }
2113
2885
  function readWeCloneProxyConfigIfExists(configPath) {
2114
2886
  try {
2115
- if (!fs2.existsSync(configPath)) return null;
2116
- return fs2.readFileSync(configPath, "utf8");
2887
+ if (!fs3.existsSync(configPath)) return null;
2888
+ return fs3.readFileSync(configPath, "utf8");
2117
2889
  } catch {
2118
2890
  return null;
2119
2891
  }
@@ -2257,4 +3029,4 @@ export {
2257
3029
  resolveWeCloneProxyConfigPath,
2258
3030
  buildWeCloneProxyConfig
2259
3031
  };
2260
- //# sourceMappingURL=chunk-H67QFYUK.js.map
3032
+ //# sourceMappingURL=chunk-UJBESW6X.js.map