@evomap/evolver 1.78.10 → 1.79.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/README.ja-JP.md CHANGED
@@ -4,6 +4,7 @@
4
4
  [![License: GPL-3.0](https://img.shields.io/badge/License-GPL--3.0-blue.svg)](https://opensource.org/licenses/GPL-3.0)
5
5
  [![Node.js >= 18](https://img.shields.io/badge/Node.js-%3E%3D%2018-green.svg)](https://nodejs.org/)
6
6
  [![GitHub last commit](https://img.shields.io/github/last-commit/EvoMap/evolver)](https://github.com/EvoMap/evolver/commits/main)
7
+ [![npm downloads](https://img.shields.io/npm/dm/@evomap/evolver.svg)](https://www.npmjs.com/package/@evomap/evolver)
7
8
  [![GitHub issues](https://img.shields.io/github/issues/EvoMap/evolver)](https://github.com/EvoMap/evolver/issues)
8
9
  [![arXiv](https://img.shields.io/badge/arXiv-2604.15097-b31b1b.svg)](https://arxiv.org/abs/2604.15097)
9
10
 
package/README.ko-KR.md CHANGED
@@ -4,6 +4,7 @@
4
4
  [![License: GPL-3.0](https://img.shields.io/badge/License-GPL--3.0-blue.svg)](https://opensource.org/licenses/GPL-3.0)
5
5
  [![Node.js >= 18](https://img.shields.io/badge/Node.js-%3E%3D%2018-green.svg)](https://nodejs.org/)
6
6
  [![GitHub last commit](https://img.shields.io/github/last-commit/EvoMap/evolver)](https://github.com/EvoMap/evolver/commits/main)
7
+ [![npm downloads](https://img.shields.io/npm/dm/@evomap/evolver.svg)](https://www.npmjs.com/package/@evomap/evolver)
7
8
  [![GitHub issues](https://img.shields.io/github/issues/EvoMap/evolver)](https://github.com/EvoMap/evolver/issues)
8
9
  [![arXiv](https://img.shields.io/badge/arXiv-2604.15097-b31b1b.svg)](https://arxiv.org/abs/2604.15097)
9
10
 
package/README.md CHANGED
@@ -4,6 +4,7 @@
4
4
  [![License: GPL-3.0](https://img.shields.io/badge/License-GPL--3.0-blue.svg)](https://opensource.org/licenses/GPL-3.0)
5
5
  [![Node.js >= 18](https://img.shields.io/badge/Node.js-%3E%3D%2018-green.svg)](https://nodejs.org/)
6
6
  [![GitHub last commit](https://img.shields.io/github/last-commit/EvoMap/evolver)](https://github.com/EvoMap/evolver/commits/main)
7
+ [![npm downloads](https://img.shields.io/npm/dm/@evomap/evolver.svg)](https://www.npmjs.com/package/@evomap/evolver)
7
8
  [![GitHub issues](https://img.shields.io/github/issues/EvoMap/evolver)](https://github.com/EvoMap/evolver/issues)
8
9
  [![arXiv](https://img.shields.io/badge/arXiv-2604.15097-b31b1b.svg)](https://arxiv.org/abs/2604.15097)
9
10
 
package/README.zh-CN.md CHANGED
@@ -4,6 +4,7 @@
4
4
  [![License: GPL-3.0](https://img.shields.io/badge/License-GPL--3.0-blue.svg)](https://opensource.org/licenses/GPL-3.0)
5
5
  [![Node.js >= 18](https://img.shields.io/badge/Node.js-%3E%3D%2018-green.svg)](https://nodejs.org/)
6
6
  [![GitHub last commit](https://img.shields.io/github/last-commit/EvoMap/evolver)](https://github.com/EvoMap/evolver/commits/main)
7
+ [![npm downloads](https://img.shields.io/npm/dm/@evomap/evolver.svg)](https://www.npmjs.com/package/@evomap/evolver)
7
8
  [![GitHub issues](https://img.shields.io/github/issues/EvoMap/evolver)](https://github.com/EvoMap/evolver/issues)
8
9
  [![arXiv](https://img.shields.io/badge/arXiv-2604.15097-b31b1b.svg)](https://arxiv.org/abs/2604.15097)
9
10
 
@@ -1 +1,2 @@
1
- {"type":"CapabilityCandidate","id":"cand_b9a66a5c","title":"Harden session log detection and fallback behavior","source":"signals","created_at":"2026-05-05T01:31:11.544Z","signals":["memory_missing","user_missing","session_logs_missing"],"tags":["memory_missing","user_missing","session_logs_missing","area:memory"],"shape":{"title":"Harden session log detection and fallback behavior","input":"Recent session transcript + memory snippets + user instructions","output":"A safe, auditable evolution patch guided by GEP assets","invariants":"Protocol order, small reversible patches, validation, append-only events","params":"Signals: memory_missing, user_missing, session_logs_missing","failure_points":"Missing signals, over-broad changes, skipped validation, missing knowledge solidification","evidence":"Signal present: session_logs_missing"}}
1
+ {"type":"CapabilityCandidate","id":"cand_b9a66a5c","title":"Harden session log detection and fallback behavior","source":"signals","created_at":"2026-05-06T01:46:54.543Z","signals":["memory_missing","user_missing","session_logs_missing"],"tags":["memory_missing","user_missing","session_logs_missing","area:memory"],"shape":{"title":"Harden session log detection and fallback behavior","input":"Recent session transcript + memory snippets + user instructions","output":"A safe, auditable evolution patch guided by GEP assets","invariants":"Protocol order, small reversible patches, validation, append-only events","params":"Signals: memory_missing, user_missing, session_logs_missing","failure_points":"Missing signals, over-broad changes, skipped validation, missing knowledge solidification","evidence":"Signal present: session_logs_missing"}}
2
+ {"type":"CapabilityCandidate","id":"cand_b9a66a5c","title":"Harden session log detection and fallback behavior","source":"signals","created_at":"2026-05-06T01:47:23.332Z","signals":["memory_missing","user_missing","session_logs_missing"],"tags":["memory_missing","user_missing","session_logs_missing","area:memory"],"shape":{"title":"Harden session log detection and fallback behavior","input":"Recent session transcript + memory snippets + user instructions","output":"A safe, auditable evolution patch guided by GEP assets","invariants":"Protocol order, small reversible patches, validation, append-only events","params":"Signals: memory_missing, user_missing, session_logs_missing","failure_points":"Missing signals, over-broad changes, skipped validation, missing knowledge solidification","evidence":"Signal present: session_logs_missing"}}
package/index.js CHANGED
@@ -104,6 +104,79 @@ function parseMs(v, fallback) {
104
104
  return fallback;
105
105
  }
106
106
 
107
+ function parseBoolEnv(v, fallback) {
108
+ if (v == null) return fallback;
109
+ const s = String(v).toLowerCase().trim();
110
+ if (s === '' ) return fallback;
111
+ if (s === 'false' || s === '0' || s === 'off' || s === 'no') return false;
112
+ if (s === 'true' || s === '1' || s === 'on' || s === 'yes') return true;
113
+ return fallback;
114
+ }
115
+
116
+ class CycleTimeoutError extends Error {
117
+ constructor(timeoutMs, phase, cycleNum) {
118
+ super('Cycle hard-timeout exceeded after ' + timeoutMs + 'ms (cycle=' + cycleNum + ', phase=' + phase + ')');
119
+ this.name = 'CycleTimeoutError';
120
+ this.code = 'CYCLE_TIMEOUT';
121
+ this.timeoutMs = timeoutMs;
122
+ this.phase = phase;
123
+ this.cycleNum = cycleNum;
124
+ }
125
+ }
126
+
127
+ // Issue #528: on Windows, child_process.spawn(detached: true, windowsHide: true)
128
+ // allocates a new conhost window every time -- windowsHide is silently ignored
129
+ // in detached mode. So suicide-respawn (cycles >= max, RSS over budget, or the
130
+ // new cycle hard-timeout) opens a new cmd popup on every restart. We now skip
131
+ // the in-process detached spawn on Windows by default and rely on an external
132
+ // supervisor (feishu-evolver-wrapper >= 1.10.0, NSSM, pm2-windows, etc.) to
133
+ // respawn the daemon on non-zero exit. Users who insist can opt back in with
134
+ // EVOLVER_SUICIDE_WINDOWS=true (and accept the popups).
135
+ function spawnReplacementProcess({ reason, args, logPath }) {
136
+ const isWindows = process.platform === 'win32';
137
+ const allowOnWindows = parseBoolEnv(process.env.EVOLVER_SUICIDE_WINDOWS, false);
138
+ if (isWindows && !allowOnWindows) {
139
+ console.log(
140
+ '[Daemon] Skipping in-process respawn on Windows (' + reason + '). ' +
141
+ 'Native Node spawn(detached, windowsHide) opens a cmd popup on every restart (Issue #528). ' +
142
+ 'Set EVOLVER_SUICIDE_WINDOWS=true to opt back in. ' +
143
+ 'Recommended: run evolver under feishu-evolver-wrapper >= 1.10.0, NSSM, or pm2-windows so the supervisor restarts on exit.'
144
+ );
145
+ return { spawned: false, reason: 'windows_default_skip' };
146
+ }
147
+ try {
148
+ const logFd = fs.openSync(logPath, 'a');
149
+ const spawnOpts = {
150
+ detached: true,
151
+ stdio: ['ignore', logFd, logFd],
152
+ env: process.env,
153
+ windowsHide: true,
154
+ };
155
+ const child = spawn(process.execPath, [__filename, ...args], spawnOpts);
156
+ child.unref();
157
+ return { spawned: true };
158
+ } catch (e) {
159
+ console.error('[Daemon] Spawn-replacement failed (' + reason + '): ' + (e && e.message || e));
160
+ return { spawned: false, reason: 'spawn_error', error: e };
161
+ }
162
+ }
163
+
164
+ // Atomic write of the cycle_progress.json file. Wrapper polls this file every
165
+ // 60s; if updated_at goes stale beyond EVOLVE_INNER_STUCK_TIMEOUT_SEC the
166
+ // wrapper treats the inner core as zombie and SIGKILLs it. See Issue #19 (the
167
+ // 22-day stuck-cycle incident) and the cross-repo timeout plan for context.
168
+ function writeCycleProgressAtomic(progressPath, fields) {
169
+ try {
170
+ const data = Object.assign({}, fields, { updated_at: Date.now() });
171
+ const tmp = progressPath + '.tmp.' + process.pid;
172
+ fs.writeFileSync(tmp, JSON.stringify(data, null, 2) + '\n', 'utf8');
173
+ fs.renameSync(tmp, progressPath);
174
+ return true;
175
+ } catch (e) {
176
+ return false;
177
+ }
178
+ }
179
+
107
180
  function getLastSignals(statePath) {
108
181
  try {
109
182
  const st = readJsonSafe(statePath);
@@ -255,6 +328,7 @@ async function main() {
255
328
 
256
329
  const { getEvolutionDir, getEvolverLogPath } = require('./src/gep/paths');
257
330
  const solidifyStatePath = path.join(getEvolutionDir(), 'evolution_solidify_state.json');
331
+ const cycleProgressPath = path.join(getEvolutionDir(), 'cycle_progress.json');
258
332
 
259
333
  const minSleepMs = parseMs(process.env.EVOLVER_MIN_SLEEP_MS, 2000);
260
334
  const maxSleepMs = parseMs(process.env.EVOLVER_MAX_SLEEP_MS, 300000);
@@ -270,6 +344,15 @@ async function main() {
270
344
  const maxRssMb = parseMs(process.env.EVOLVER_MAX_RSS_MB, 500) || 500;
271
345
  const suicideEnabled = String(process.env.EVOLVER_SUICIDE || '').toLowerCase() !== 'false';
272
346
 
347
+ // Issue #19: hard timeout around evolve.run() to break out of zombie
348
+ // cycles (e.g. unclosed socket / stuck LLM call). On timeout we throw
349
+ // CycleTimeoutError, log diagnostic stderr, and force suicide-respawn
350
+ // so the wrapper sees a fresh PID + cycle. Also write cycle_progress
351
+ // every progressUpdateMs so the wrapper has a true heartbeat to poll.
352
+ const cycleTimeoutEnabled = parseBoolEnv(process.env.EVOLVER_CYCLE_TIMEOUT_ENABLED, true);
353
+ const cycleTimeoutMs = parseMs(process.env.EVOLVER_CYCLE_TIMEOUT_MS, 2700000); // 45 min default
354
+ const progressUpdateMs = parseMs(process.env.EVOLVER_PROGRESS_UPDATE_MS, 60000); // 1 min default
355
+
273
356
  // Start hub heartbeat (keeps node alive independently of evolution cycles)
274
357
  try {
275
358
  if (process.env.EVOMAP_PROXY === '1' || process.env.A2A_TRANSPORT === 'mailbox') {
@@ -388,8 +471,46 @@ async function main() {
388
471
 
389
472
  const t0 = Date.now();
390
473
  let ok = false;
474
+ // Issue #19: write progress at cycle start, refresh it every
475
+ // progressUpdateMs (default 60s) while evolve.run() is active, and
476
+ // wrap evolve.run() with Promise.race(timeout) so a hung internal
477
+ // call cannot freeze the daemon for days.
478
+ writeCycleProgressAtomic(cycleProgressPath, {
479
+ pid: process.pid,
480
+ outer_cycle: cycleCount,
481
+ inner_cycle: cycleCount,
482
+ started_at: t0,
483
+ phase: 'evolve.run',
484
+ });
485
+ let progressTicker = null;
486
+ if (progressUpdateMs > 0) {
487
+ progressTicker = setInterval(function () {
488
+ writeCycleProgressAtomic(cycleProgressPath, {
489
+ pid: process.pid,
490
+ outer_cycle: cycleCount,
491
+ inner_cycle: cycleCount,
492
+ started_at: t0,
493
+ phase: 'evolve.run',
494
+ });
495
+ }, progressUpdateMs);
496
+ if (typeof progressTicker.unref === 'function') progressTicker.unref();
497
+ }
498
+ let cycleTimeoutHandle = null;
499
+ let cycleTimedOut = false;
391
500
  try {
392
- await evolve.run();
501
+ const evolvePromise = evolve.run();
502
+ if (cycleTimeoutEnabled && cycleTimeoutMs > 0) {
503
+ const timeoutPromise = new Promise(function (_, reject) {
504
+ cycleTimeoutHandle = setTimeout(function () {
505
+ cycleTimedOut = true;
506
+ reject(new CycleTimeoutError(cycleTimeoutMs, 'evolve.run', cycleCount));
507
+ }, cycleTimeoutMs);
508
+ if (cycleTimeoutHandle && typeof cycleTimeoutHandle.unref === 'function') cycleTimeoutHandle.unref();
509
+ });
510
+ await Promise.race([evolvePromise, timeoutPromise]);
511
+ } else {
512
+ await evolvePromise;
513
+ }
393
514
  ok = true;
394
515
 
395
516
  if (String(process.env.EVOLVE_BRIDGE || '').toLowerCase() === 'false') {
@@ -403,7 +524,29 @@ async function main() {
403
524
  }
404
525
  } catch (error) {
405
526
  const msg = error && error.message ? String(error.message) : String(error);
527
+ if (error && error.code === 'CYCLE_TIMEOUT') {
528
+ console.error('[Daemon] ' + msg);
529
+ if (progressTicker) { clearInterval(progressTicker); progressTicker = null; }
530
+ if (cycleTimeoutHandle) { clearTimeout(cycleTimeoutHandle); cycleTimeoutHandle = null; }
531
+ writeCycleProgressAtomic(cycleProgressPath, {
532
+ pid: process.pid,
533
+ outer_cycle: cycleCount,
534
+ inner_cycle: cycleCount,
535
+ started_at: t0,
536
+ phase: 'cycle_timeout_respawn',
537
+ });
538
+ spawnReplacementProcess({
539
+ reason: 'cycle_hard_timeout',
540
+ args: args,
541
+ logPath: getEvolverLogPath(),
542
+ });
543
+ releaseLock();
544
+ process.exit(1);
545
+ }
406
546
  console.error(`Evolution cycle failed: ${msg}`);
547
+ } finally {
548
+ if (progressTicker) { clearInterval(progressTicker); progressTicker = null; }
549
+ if (cycleTimeoutHandle) { clearTimeout(cycleTimeoutHandle); cycleTimeoutHandle = null; }
407
550
  }
408
551
  const dt = Date.now() - t0;
409
552
 
@@ -451,25 +594,32 @@ async function main() {
451
594
  if (isVerbose) console.warn('[OMLS] Scheduler error: ' + (e.message || e));
452
595
  }
453
596
 
454
- // Suicide check (memory leak protection)
597
+ // Suicide check (memory leak protection). On Windows the
598
+ // in-process respawn opens a cmd popup (Issue #528), so by default
599
+ // we delegate to an external supervisor by exiting with a non-zero
600
+ // code instead. See spawnReplacementProcess() for the policy.
455
601
  if (suicideEnabled) {
456
602
  const memMb = process.memoryUsage().rss / 1024 / 1024;
457
603
  if (cycleCount >= maxCyclesPerProcess || memMb > maxRssMb) {
458
604
  console.log(`[Daemon] Restarting self (cycles=${cycleCount}, rssMb=${memMb.toFixed(0)})`);
459
- try {
460
- const logFd = fs.openSync(getEvolverLogPath(), 'a');
461
- const spawnOpts = {
462
- detached: true,
463
- stdio: ['ignore', logFd, logFd],
464
- env: process.env,
465
- windowsHide: true,
466
- };
467
- const child = spawn(process.execPath, [__filename, ...args], spawnOpts);
468
- child.unref();
605
+ const result = spawnReplacementProcess({
606
+ reason: 'max_cycles_or_rss',
607
+ args: args,
608
+ logPath: getEvolverLogPath(),
609
+ });
610
+ if (result.spawned) {
469
611
  releaseLock();
470
612
  process.exit(0);
471
- } catch (spawnErr) {
472
- console.error('[Daemon] Spawn failed, continuing current process:', spawnErr.message);
613
+ } else if (result.reason === 'windows_default_skip') {
614
+ console.log('[Daemon] Exiting with code 1 to let external supervisor respawn.');
615
+ releaseLock();
616
+ process.exit(1);
617
+ } else {
618
+ // Non-Windows spawn error: keep the lock and fall through to
619
+ // the next iteration of the loop instead of leaving the daemon
620
+ // dead. This matches the pre-1.79.1 behavior where a failed
621
+ // spawn was logged and the process continued running.
622
+ console.error('[Daemon] Spawn failed, continuing current process.');
473
623
  }
474
624
  }
475
625
  }
@@ -496,6 +646,13 @@ async function main() {
496
646
  const signals = getLastSignals(solidifyStatePath).join(',');
497
647
  console.log(`[Verbose] cycle=${cycleCount} ok=${ok} dt=${dt}ms sleep=${totalSleepMs}ms (base=${currentSleepMs} jitter=${jitter} sat=${saturationMultiplier}x) rss=${memMb}MB signals=[${signals}]`);
498
648
  }
649
+ writeCycleProgressAtomic(cycleProgressPath, {
650
+ pid: process.pid,
651
+ outer_cycle: cycleCount,
652
+ inner_cycle: cycleCount,
653
+ started_at: t0,
654
+ phase: 'sleep',
655
+ });
499
656
  await sleepMs(totalSleepMs);
500
657
 
501
658
  } catch (loopErr) {
@@ -1581,4 +1738,8 @@ module.exports = {
1581
1738
  readJsonSafe,
1582
1739
  rejectPendingRun,
1583
1740
  isPendingSolidify,
1741
+ parseBoolEnv,
1742
+ CycleTimeoutError,
1743
+ writeCycleProgressAtomic,
1744
+ spawnReplacementProcess,
1584
1745
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@evomap/evolver",
3
- "version": "1.78.10",
3
+ "version": "1.79.1",
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": {