@evomap/evolver 1.88.2 → 1.88.3

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 (57) hide show
  1. package/index.js +11 -0
  2. package/package.json +1 -1
  3. package/src/adapters/scripts/_runtimePaths.js +160 -36
  4. package/src/config.js +6 -0
  5. package/src/evolve/guards.js +1 -1
  6. package/src/evolve/pipeline/collect.js +1 -1
  7. package/src/evolve/pipeline/dispatch.js +1 -1
  8. package/src/evolve/pipeline/enrich.js +1 -1
  9. package/src/evolve/pipeline/hub.js +1 -1
  10. package/src/evolve/pipeline/select.js +1 -1
  11. package/src/evolve/pipeline/signals.js +1 -1
  12. package/src/evolve/utils.js +1 -1
  13. package/src/evolve.js +1 -1
  14. package/src/gep/a2aProtocol.js +1 -1
  15. package/src/gep/autoDistillConv.js +1 -1
  16. package/src/gep/autoDistillLlm.js +1 -1
  17. package/src/gep/candidateEval.js +1 -1
  18. package/src/gep/candidates.js +1 -1
  19. package/src/gep/contentHash.js +1 -1
  20. package/src/gep/conversationSniffer.js +1 -1
  21. package/src/gep/crypto.js +1 -1
  22. package/src/gep/curriculum.js +1 -1
  23. package/src/gep/deviceId.js +1 -1
  24. package/src/gep/envFingerprint.js +1 -1
  25. package/src/gep/epigenetics.js +1 -1
  26. package/src/gep/execBridge.js +1 -1
  27. package/src/gep/explore.js +1 -1
  28. package/src/gep/hash.js +1 -1
  29. package/src/gep/hubFetch.js +1 -1
  30. package/src/gep/hubReview.js +1 -1
  31. package/src/gep/hubSearch.js +1 -1
  32. package/src/gep/hubVerify.js +1 -1
  33. package/src/gep/learningSignals.js +1 -1
  34. package/src/gep/memoryGraph.js +1 -1
  35. package/src/gep/memoryGraphAdapter.js +1 -1
  36. package/src/gep/mutation.js +1 -1
  37. package/src/gep/narrativeMemory.js +1 -1
  38. package/src/gep/openPRRegistry.js +1 -1
  39. package/src/gep/personality.js +1 -1
  40. package/src/gep/policyCheck.js +1 -1
  41. package/src/gep/prompt.js +1 -1
  42. package/src/gep/recallInject.js +1 -1
  43. package/src/gep/recallVerifier.js +1 -1
  44. package/src/gep/reflection.js +1 -1
  45. package/src/gep/sanitize.js +5 -0
  46. package/src/gep/selector.js +1 -1
  47. package/src/gep/skillDistiller.js +1 -1
  48. package/src/gep/solidify.js +1 -1
  49. package/src/gep/strategy.js +1 -1
  50. package/src/gep/workspaceKeychain.js +1 -1
  51. package/src/proxy/extensions/traceControl.js +1 -0
  52. package/src/proxy/index.js +24 -3
  53. package/src/proxy/inject.js +1 -0
  54. package/src/proxy/lifecycle/manager.js +1 -0
  55. package/src/proxy/mailbox/store.js +1 -0
  56. package/src/proxy/router/messages_route.js +57 -8
  57. package/src/proxy/trace/extractor.js +1 -0
package/index.js CHANGED
@@ -1185,6 +1185,17 @@ async function main() {
1185
1185
  hubUrl: process.env.A2A_HUB_URL,
1186
1186
  });
1187
1187
  console.log('[Proxy] Started on ' + proxyInfo.url);
1188
+ try {
1189
+ const { injectProxyEnv } = require('./src/proxy/inject');
1190
+ const injected = injectProxyEnv(proxyInfo);
1191
+ if (injected.injected) {
1192
+ console.log('[Proxy] Auto-injected client env for Claude Code/Codex/Cursor. Set EVOMAP_PROXY_AUTO_INJECT=off to disable.');
1193
+ } else {
1194
+ console.log('[Proxy] Auto-inject skipped: ' + injected.reason);
1195
+ }
1196
+ } catch (injectErr) {
1197
+ console.warn('[Proxy] Auto-inject failed: ' + (injectErr && injectErr.message || injectErr));
1198
+ }
1188
1199
  const { registerMailboxTransport } = require('./src/gep/mailboxTransport');
1189
1200
  registerMailboxTransport();
1190
1201
  process.env.A2A_TRANSPORT = 'mailbox';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@evomap/evolver",
3
- "version": "1.88.2",
3
+ "version": "1.88.3",
4
4
  "description": "A GEP-powered self-evolution engine for AI agents. Features automated log analysis and Genome Evolution Protocol (GEP) for auditable, reusable evolution assets.",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -31,6 +31,137 @@ function isEvolverPackageJson(filePath) {
31
31
  }
32
32
  }
33
33
 
34
+ // Scan a "versions dir" used by Node version managers (NVM, fnm, Volta, asdf)
35
+ // and append each `<versions-dir>/<version>/<subdir>/node_modules` to `out`.
36
+ // Skips silently when the versions dir does not exist (typical case — most
37
+ // users have at most one version manager). Most-recent-mtime first so the
38
+ // active version is preferred when a user has multiple Node versions
39
+ // installed.
40
+ function _scanVersionedNodeModules(versionsDir, subdir, out) {
41
+ let entries;
42
+ try {
43
+ entries = fs.readdirSync(versionsDir, { withFileTypes: true });
44
+ } catch {
45
+ return;
46
+ }
47
+ const dirs = entries
48
+ .filter((e) => e.isDirectory && e.isDirectory())
49
+ .map((e) => {
50
+ const full = path.join(versionsDir, e.name);
51
+ let mtime = 0;
52
+ try { mtime = fs.statSync(full).mtimeMs; } catch {}
53
+ return { full, mtime };
54
+ })
55
+ .sort((a, b) => b.mtime - a.mtime);
56
+ for (const d of dirs) {
57
+ out.push(path.join(d.full, subdir, 'node_modules'));
58
+ }
59
+ }
60
+
61
+ // Build the require.resolve paths array. All entries are user/system-scoped
62
+ // install roots — process.cwd() is intentionally excluded for the same
63
+ // prompt-injection reason as the original allowlist (see comment in
64
+ // findEvolverRoot below).
65
+ function _buildInstallSearchPaths() {
66
+ const home = os.homedir();
67
+ // Env-derived bases must be ABSOLUTE. A relative override (e.g. NVM_DIR='.nvm')
68
+ // or empty value would resolve against process.cwd() and let a hostile
69
+ // workspace plant a fake @evomap/evolver in the require.resolve allowlist —
70
+ // the prompt-injection surface PR #94 closed. isAbsolute is platform-matched
71
+ // (path.win32 recognises C:\ ...) so the guard holds on Windows too; a
72
+ // non-absolute override falls through to the trusted home/system default.
73
+ const _pathFlavor = process.platform === 'win32' ? path.win32 : path.posix;
74
+ const absEnv = (v) => (v && _pathFlavor.isAbsolute(v)) ? v : null;
75
+ const paths = [
76
+ // npm global with `npm config set prefix` overrides
77
+ path.join(home, '.npm-global', 'lib', 'node_modules'),
78
+ path.join(home, '.local', 'lib', 'node_modules'),
79
+ // System-wide (apt/yum nodejs, Intel Mac Homebrew)
80
+ '/usr/lib/node_modules',
81
+ '/usr/local/lib/node_modules',
82
+ // Apple Silicon Homebrew (default since macOS Big Sur on M1/M2/M3/M4 —
83
+ // the majority of Mac dev hardware sold since 2021). Without this
84
+ // entry, `npm install -g @evomap/evolver` on an Apple Silicon Mac
85
+ // lands at /opt/homebrew/lib/node_modules/@evomap/evolver and the
86
+ // hook scripts cannot find the package -> additionalContext is empty
87
+ // -> evolution memory never reaches the LLM.
88
+ '/opt/homebrew/lib/node_modules',
89
+ // Linuxbrew (Homebrew on Linux — niche but real).
90
+ '/home/linuxbrew/.linuxbrew/lib/node_modules',
91
+ ];
92
+ // Per-user Node version managers. Each manager has its own on-disk layout
93
+ // and its own base-dir env override; the version subdirectory is dynamic
94
+ // (e.g. `~/.nvm/versions/node/v22.15.0`) so we scan and append each
95
+ // version's node_modules. These were missing from the original hard-coded
96
+ // list even though NVM in particular is extremely common across all OSes.
97
+
98
+ // NVM. Globals are per-version under `<NVM_DIR>/versions/node/<ver>/lib`.
99
+ // NVM_DIR defaults to ~/.nvm but is frequently relocated.
100
+ const nvmDir = absEnv(process.env.NVM_DIR) || path.join(home, '.nvm');
101
+ _scanVersionedNodeModules(path.join(nvmDir, 'versions', 'node'), 'lib', paths);
102
+
103
+ // fnm. Each version lives under `<base>/node-versions/<ver>/installation/`,
104
+ // and fnm does NOT override the npm prefix, so globals are at
105
+ // `installation/lib/node_modules`. The base dir is XDG-first
106
+ // (`$XDG_DATA_HOME/fnm`, i.e. ~/.local/share/fnm on Linux and
107
+ // ~/Library/Application Support/fnm on macOS); `~/.fnm` is only the legacy
108
+ // fallback. `$FNM_DIR` overrides everything. Scan all candidate bases;
109
+ // _scanVersionedNodeModules silently skips the ones that don't exist.
110
+ const fnmSub = path.join('installation', 'lib');
111
+ const fnmBases = [];
112
+ if (absEnv(process.env.FNM_DIR)) fnmBases.push(process.env.FNM_DIR);
113
+ if (absEnv(process.env.XDG_DATA_HOME)) fnmBases.push(path.join(process.env.XDG_DATA_HOME, 'fnm'));
114
+ fnmBases.push(path.join(home, '.local', 'share', 'fnm')); // Linux XDG default
115
+ fnmBases.push(path.join(home, 'Library', 'Application Support', 'fnm')); // macOS default
116
+ fnmBases.push(path.join(home, '.fnm')); // legacy
117
+ for (const base of fnmBases) {
118
+ _scanVersionedNodeModules(path.join(base, 'node-versions'), fnmSub, paths);
119
+ }
120
+
121
+ // Volta does NOT store global packages alongside the Node image. It
122
+ // sandboxes each `npm install -g`'d package under
123
+ // `<VOLTA_HOME>/tools/image/packages/<name>/lib/node_modules` (the scope
124
+ // becomes a real nested directory). Because we know the package name, this
125
+ // is a single fixed path rather than a version scan. VOLTA_HOME defaults to
126
+ // ~/.volta on macOS/Linux but to %LOCALAPPDATA%\Volta on Windows (where the
127
+ // globals actually live, and hook processes often don't inherit VOLTA_HOME).
128
+ // Verified against volta-cli/volta `volta-layout` v4 (`package_image_dir`) +
129
+ // `package/manager.rs` (`source_dir` = lib/node_modules).
130
+ const voltaHome = absEnv(process.env.VOLTA_HOME)
131
+ || (process.platform === 'win32'
132
+ ? path.join(absEnv(process.env.LOCALAPPDATA) || path.join(home, 'AppData', 'Local'), 'Volta')
133
+ : path.join(home, '.volta'));
134
+ paths.push(path.join(voltaHome, 'tools', 'image', 'packages', '@evomap', 'evolver', 'lib', 'node_modules'));
135
+
136
+ // asdf. Globals are per-version under `<data>/installs/nodejs/<ver>/`.
137
+ // asdf-nodejs dropped the `.npm` prefix override in PR #228 (Sept 2022),
138
+ // so modern installs use plain `lib/node_modules`; older installs (never
139
+ // re-created) still use `.npm/lib/node_modules`. Scan both. `$ASDF_DATA_DIR`
140
+ // overrides the ~/.asdf default (asdf 0.16+ Go rewrite).
141
+ const asdfData = absEnv(process.env.ASDF_DATA_DIR) || path.join(home, '.asdf');
142
+ const asdfVersions = path.join(asdfData, 'installs', 'nodejs');
143
+ _scanVersionedNodeModules(asdfVersions, 'lib', paths); // modern (post-#228)
144
+ _scanVersionedNodeModules(asdfVersions, path.join('.npm', 'lib'), paths); // legacy (pre-#228)
145
+
146
+ // Windows: `npm install -g` puts packages under %APPDATA%\npm\node_modules
147
+ // (most common; same convention as `npm config get prefix` default on win32),
148
+ // %ProgramFiles%\nodejs\node_modules (system-wide installer), or
149
+ // %ProgramFiles(x86)%\nodejs\node_modules (32-bit Node on a 64-bit host).
150
+ // Conditional expansion keeps the POSIX base list untouched on Linux/macOS.
151
+ if (process.platform === 'win32') {
152
+ const appdata = absEnv(process.env.APPDATA) || path.join(home, 'AppData', 'Roaming');
153
+ paths.push(path.join(appdata, 'npm', 'node_modules'));
154
+ if (absEnv(process.env.ProgramFiles)) {
155
+ paths.push(path.join(process.env.ProgramFiles, 'nodejs', 'node_modules'));
156
+ }
157
+ if (absEnv(process.env['ProgramFiles(x86)'])) {
158
+ paths.push(path.join(process.env['ProgramFiles(x86)'], 'nodejs', 'node_modules'));
159
+ }
160
+ }
161
+
162
+ return paths;
163
+ }
164
+
34
165
  function findEvolverRoot() {
35
166
  if (process.env.EVOLVER_ROOT) {
36
167
  const explicit = process.env.EVOLVER_ROOT;
@@ -57,39 +188,18 @@ function findEvolverRoot() {
57
188
  // be selected here and control `findMemoryGraph()` -> the memory graph
58
189
  // contents become attacker-controlled prompt-injection material in
59
190
  // `evolver-session-start.js`'s `additionalContext`. Restrict to trusted,
60
- // user/system-scoped install roots.
191
+ // user/system-scoped install roots (built in `_buildInstallSearchPaths`).
61
192
  try {
62
- // Windows: `npm install -g` puts packages under %APPDATA%\npm\node_modules
63
- // (most common), %ProgramFiles%\nodejs\node_modules (system-wide installer),
64
- // or %ProgramFiles(x86)%\nodejs\node_modules. Build the extra Windows paths
65
- // conditionally so the POSIX base list stays intact.
66
- const _winPaths = process.platform === 'win32'
67
- ? [
68
- path.join(
69
- process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming'),
70
- 'npm', 'node_modules'
71
- ),
72
- ...(process.env.ProgramFiles
73
- ? [path.join(process.env.ProgramFiles, 'nodejs', 'node_modules')]
74
- : []),
75
- ...(process.env['ProgramFiles(x86)']
76
- ? [path.join(process.env['ProgramFiles(x86)'], 'nodejs', 'node_modules')]
77
- : []),
78
- ]
79
- : [];
80
-
193
+ // Allowlist of trusted user/system-scoped install roots. Built by
194
+ // _buildInstallSearchPaths() above so the list is one source of truth
195
+ // (Apple Silicon Homebrew, Linuxbrew, NVM / fnm / Volta / asdf,
196
+ // and Windows %APPDATA%\npm + %ProgramFiles%\nodejs install layouts).
197
+ // process.cwd() is intentionally excluded: a hostile workspace can plant
198
+ // its own node_modules/@evomap/evolver/package.json which would then
199
+ // control findMemoryGraph() and feed attacker-controlled content into
200
+ // evolver-session-start.js's additionalContext.
81
201
  const pkgJson = require.resolve('@evomap/evolver/package.json', {
82
- // Do NOT include process.cwd() — a hostile workspace can plant its own
83
- // node_modules/@evomap/evolver to gain control over the memory graph path
84
- // (prompt-injection surface: evolver-session-start.js additionalContext).
85
- // Only trust user/system-scoped install roots.
86
- paths: [
87
- path.join(os.homedir(), '.npm-global', 'lib', 'node_modules'),
88
- path.join(os.homedir(), '.local', 'lib', 'node_modules'),
89
- '/usr/lib/node_modules',
90
- '/usr/local/lib/node_modules',
91
- ..._winPaths,
92
- ],
202
+ paths: _buildInstallSearchPaths(),
93
203
  });
94
204
  if (pkgJson && isEvolverPackageJson(pkgJson)) {
95
205
  return path.dirname(pkgJson);
@@ -293,10 +403,10 @@ function findMemoryGraph(evolverRoot) {
293
403
  }
294
404
 
295
405
  // Is `dir` inside a git work tree? Cheap, no-shell `git rev-parse`. Returns
296
- // false on any error (git missing, not a repo, timeout) and never throws — the
297
- // session-start hook uses this only to decide whether to surface a one-line
298
- // "evolver needs a git workspace" notice, so a false negative just suppresses
299
- // the notice rather than breaking anything.
406
+ // false on any error (git missing, not a repo, timeout) and never throws.
407
+ // The session-start hook uses this only to decide whether to surface a
408
+ // one-line "evolver needs a git workspace" notice, so a false negative just
409
+ // suppresses the notice rather than breaking anything.
300
410
  function isGitWorkspace(dir) {
301
411
  try {
302
412
  const res = spawnSync('git', ['rev-parse', '--is-inside-work-tree'], {
@@ -312,4 +422,18 @@ function isGitWorkspace(dir) {
312
422
  }
313
423
  }
314
424
 
315
- module.exports = { findEvolverRoot, findMemoryGraph, resolveProjectDir, resolveWorkspaceId, isGitWorkspace };
425
+ module.exports = {
426
+ findEvolverRoot,
427
+ findMemoryGraph,
428
+ resolveProjectDir,
429
+ resolveWorkspaceId,
430
+ isGitWorkspace,
431
+ // Test-only: exposes the install-path builder so the test suite can
432
+ // verify Apple Silicon Homebrew + version-manager (NVM/fnm/Volta/asdf)
433
+ // + Windows %APPDATA%\npm paths are included without going through the
434
+ // full require.resolve chain (which depends on a real filesystem layout).
435
+ __internals: {
436
+ buildInstallSearchPaths: _buildInstallSearchPaths,
437
+ scanVersionedNodeModules: _scanVersionedNodeModules,
438
+ },
439
+ };
package/src/config.js CHANGED
@@ -136,9 +136,14 @@ const REPAIR_LOOP_THRESHOLD = envInt('EVOLVER_REPAIR_LOOP_THRESHOLD', 3);
136
136
  //
137
137
  // GENE_BAN_PER_KEY_ATTEMPTS: minimum attempts on the same signal key
138
138
  // GENE_BAN_BEST_THRESHOLD: best success rate at or below which the Gene is banned
139
+ // GENE_INERT_BAN_STREAK: consecutive inert (stable_no_error, zero-work) outcomes
140
+ // on a signal key after which a Gene with no real
141
+ // success is banned, so --loop selection explores
142
+ // instead of re-running a do-nothing gene (#562)
139
143
  // GENE_EPIGENETIC_HARD_BOOST: epigenetic boost at or below which the Gene is hard-suppressed
140
144
  const GENE_BAN_PER_KEY_ATTEMPTS = envInt('EVOLVER_GENE_BAN_PER_KEY_ATTEMPTS', 4);
141
145
  const GENE_BAN_BEST_THRESHOLD = envFloat('EVOLVER_GENE_BAN_BEST_THRESHOLD', 0.15);
146
+ const GENE_INERT_BAN_STREAK = envInt('EVOLVER_GENE_INERT_BAN_STREAK', 8);
142
147
  const GENE_EPIGENETIC_HARD_BOOST = envFloat('EVOLVER_GENE_EPIGENETIC_HARD_BOOST', -0.3);
143
148
  const SESSION_ARCHIVE_TRIGGER = envInt('EVOLVER_SESSION_ARCHIVE_TRIGGER', 100);
144
149
  const SESSION_ARCHIVE_KEEP = envInt('EVOLVER_SESSION_ARCHIVE_KEEP', 50);
@@ -243,6 +248,7 @@ module.exports = {
243
248
  REPAIR_LOOP_THRESHOLD,
244
249
  GENE_BAN_PER_KEY_ATTEMPTS,
245
250
  GENE_BAN_BEST_THRESHOLD,
251
+ GENE_INERT_BAN_STREAK,
246
252
  GENE_EPIGENETIC_HARD_BOOST,
247
253
  SESSION_ARCHIVE_TRIGGER,
248
254
  SESSION_ARCHIVE_KEEP,