@evomap/evolver 1.74.0 → 1.75.0

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.
@@ -1 +1,3 @@
1
- {"type":"CapabilityCandidate","id":"cand_b9a66a5c","title":"Harden session log detection and fallback behavior","source":"signals","created_at":"2026-04-28T03:36:59.386Z","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-04-28T12:53:54.871Z","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-04-28T12:54:19.044Z","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"}}
3
+ {"type":"CapabilityCandidate","id":"cand_b9a66a5c","title":"Harden session log detection and fallback behavior","source":"signals","created_at":"2026-04-28T12:54:24.039Z","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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@evomap/evolver",
3
- "version": "1.74.0",
3
+ "version": "1.75.0",
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": {
@@ -0,0 +1,254 @@
1
+ // ATP Heartbeat Signals Handler (merchant-side, heartbeat-thread)
2
+ //
3
+ // Bridges the gap for merchant nodes that never enter the evolve run() loop
4
+ // but still heartbeat normally. When Hub attaches `pending_atp_tasks` or
5
+ // `pending_deliveries` to a heartbeat response, this handler reacts directly
6
+ // from the heartbeat callback so submitDelivery lands without the run() loop.
7
+ //
8
+ // Safety posture:
9
+ // - submitDelivery (phase=deliver or pending_deliveries with result_asset_id)
10
+ // is a pure HTTP POST with a minimal auto-generated proofPayload. No LLM,
11
+ // no spawn, safe to call from any worker context.
12
+ // - Tasks that still need execution (phase=claim or phase=execute without a
13
+ // result_asset_id) cannot be completed in heartbeat-only mode because they
14
+ // require an LLM sub-session. We log them so the operator / monitor knows
15
+ // these nodes are being asked to work but cannot (Hub-side routing should
16
+ // deprioritize them, handled separately).
17
+ //
18
+ // Dedup ledger: reuses the existing autoDeliver ledger path so a node running
19
+ // both run() loop and heartbeat-only subprocesses never double-submits.
20
+
21
+ const fs = require('fs');
22
+ const path = require('path');
23
+
24
+ const { getMemoryDir } = require('../gep/paths');
25
+ const hubClient = require('./hubClient');
26
+
27
+ const LEDGER_FILENAME = 'atp-autodeliver-ledger.json'; // shared with autoDeliver
28
+ const LEDGER_MAX_ENTRIES = 500;
29
+ const HANDLER_COOLDOWN_MS = 30 * 1000; // rate-limit per-node, independent of ledger
30
+ const SUBMIT_TIMEOUT_MS = 10 * 1000;
31
+
32
+ let _inflight = false;
33
+ let _lastRunAt = 0;
34
+
35
+ function _isEnabled() {
36
+ const raw = (process.env.EVOLVER_ATP_AUTODELIVER || 'on').toLowerCase().trim();
37
+ return raw !== 'off' && raw !== '0' && raw !== 'false';
38
+ }
39
+
40
+ function _ledgerPath() {
41
+ return path.join(getMemoryDir(), LEDGER_FILENAME);
42
+ }
43
+
44
+ function _emptyLedger() {
45
+ return { version: 1, submitted: {} };
46
+ }
47
+
48
+ function _readLedger() {
49
+ try {
50
+ const p = _ledgerPath();
51
+ if (!fs.existsSync(p)) return _emptyLedger();
52
+ const raw = fs.readFileSync(p, 'utf8');
53
+ const parsed = JSON.parse(raw);
54
+ if (!parsed || typeof parsed !== 'object' || !parsed.submitted) return _emptyLedger();
55
+ return parsed;
56
+ } catch (_) {
57
+ return _emptyLedger();
58
+ }
59
+ }
60
+
61
+ function _writeLedger(ledger) {
62
+ try {
63
+ const dir = getMemoryDir();
64
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
65
+ const entries = Object.entries(ledger.submitted || {});
66
+ if (entries.length > LEDGER_MAX_ENTRIES) {
67
+ ledger.submitted = Object.fromEntries(entries.slice(-LEDGER_MAX_ENTRIES));
68
+ }
69
+ const tmp = _ledgerPath() + '.tmp';
70
+ fs.writeFileSync(tmp, JSON.stringify(ledger, null, 2));
71
+ fs.renameSync(tmp, _ledgerPath());
72
+ } catch (_) {
73
+ // Non-fatal: Hub submit is idempotent, next heartbeat will retry
74
+ }
75
+ }
76
+
77
+ function _buildProofPayload(row) {
78
+ // row shape mirrors what the Hub ships in pending_deliveries:
79
+ // { proof_id, order_id, task_id, verify_mode, created_at,
80
+ // task_status, result_asset_id, claimed_by }
81
+ return {
82
+ result: 'completed',
83
+ asset_id: row.result_asset_id || null,
84
+ completed_at: new Date().toISOString(),
85
+ pass_rate: 1.0,
86
+ submitter: 'evolver_heartbeat_deliver',
87
+ };
88
+ }
89
+
90
+ function _withTimeout(promise, ms) {
91
+ return new Promise(function (resolve) {
92
+ var done = false;
93
+ var timer = setTimeout(function () {
94
+ if (done) return;
95
+ done = true;
96
+ resolve({ ok: false, error: 'timeout', status: 0 });
97
+ }, ms);
98
+ Promise.resolve(promise).then(function (v) {
99
+ if (done) return;
100
+ done = true;
101
+ clearTimeout(timer);
102
+ resolve(v);
103
+ }, function (err) {
104
+ if (done) return;
105
+ done = true;
106
+ clearTimeout(timer);
107
+ resolve({ ok: false, error: err && err.message || String(err), status: 0 });
108
+ });
109
+ });
110
+ }
111
+
112
+ // Collect every delivery-eligible row from the two possible signal lists.
113
+ // A row is delivery-eligible iff it has a result_asset_id AND an order_id.
114
+ function _collectDeliverable(pendingDeliveries, pendingAtpTasks) {
115
+ var out = [];
116
+ if (Array.isArray(pendingDeliveries)) {
117
+ for (var i = 0; i < pendingDeliveries.length; i++) {
118
+ var r = pendingDeliveries[i];
119
+ if (r && r.order_id && r.result_asset_id) {
120
+ out.push({
121
+ order_id: r.order_id,
122
+ proof_id: r.proof_id,
123
+ task_id: r.task_id,
124
+ result_asset_id: r.result_asset_id,
125
+ source: 'pending_deliveries',
126
+ });
127
+ }
128
+ }
129
+ }
130
+ // pending_atp_tasks may include phase="execute" rows -- if the task already
131
+ // has result_asset_id (merchant finished work but never submitted), we also
132
+ // treat it as deliverable. The Hub sets result_asset_id separately, so we
133
+ // only consult what the signal itself carries (currently none on that side,
134
+ // but keep the shape future-proof).
135
+ if (Array.isArray(pendingAtpTasks)) {
136
+ for (var j = 0; j < pendingAtpTasks.length; j++) {
137
+ var t = pendingAtpTasks[j];
138
+ if (t && t.order_id && t.result_asset_id) {
139
+ out.push({
140
+ order_id: t.order_id,
141
+ proof_id: t.proof_id,
142
+ task_id: t.task_id,
143
+ result_asset_id: t.result_asset_id,
144
+ source: 'pending_atp_tasks',
145
+ });
146
+ }
147
+ }
148
+ }
149
+ // Dedup by order_id, first-wins
150
+ var seen = {};
151
+ var dedup = [];
152
+ for (var k = 0; k < out.length; k++) {
153
+ if (seen[out[k].order_id]) continue;
154
+ seen[out[k].order_id] = 1;
155
+ dedup.push(out[k]);
156
+ }
157
+ return dedup;
158
+ }
159
+
160
+ /**
161
+ * Heartbeat-thread entrypoint. Returns a promise that resolves to a small
162
+ * summary object (for logging / testing). Safe to call every heartbeat;
163
+ * internal guards (inflight, cooldown, ledger) ensure no Hub flood.
164
+ *
165
+ * @param {object} signals - { pending_atp_tasks?, pending_deliveries? }
166
+ * @returns {Promise<{submitted: number, skipped: number, failed: number}>}
167
+ */
168
+ async function handleHeartbeatSignals(signals) {
169
+ var summary = { submitted: 0, skipped: 0, failed: 0, need_work: 0 };
170
+ if (!_isEnabled()) return summary;
171
+ if (_inflight) return summary;
172
+ var now = Date.now();
173
+ if (_lastRunAt && (now - _lastRunAt) < HANDLER_COOLDOWN_MS) return summary;
174
+ if (!signals || typeof signals !== 'object') return summary;
175
+
176
+ var deliverables = _collectDeliverable(signals.pending_deliveries, signals.pending_atp_tasks);
177
+
178
+ // Log (but do not act on) tasks that cannot be submitted from heartbeat-only
179
+ // context. A human-readable warning on stdout makes the "this node is asked
180
+ // to work but can't" situation visible in supervised runs.
181
+ if (Array.isArray(signals.pending_atp_tasks)) {
182
+ for (var i = 0; i < signals.pending_atp_tasks.length; i++) {
183
+ var t = signals.pending_atp_tasks[i];
184
+ if (t && !t.result_asset_id) summary.need_work++;
185
+ }
186
+ }
187
+
188
+ if (deliverables.length === 0) {
189
+ if (summary.need_work > 0) {
190
+ console.log('[ATP-HB] ' + summary.need_work + ' ATP task(s) need work on this node but no run() loop is active. '
191
+ + 'Start Evolver with `node index.js run` to pick them up. Skipping from heartbeat-only mode.');
192
+ }
193
+ return summary;
194
+ }
195
+
196
+ _inflight = true;
197
+ _lastRunAt = now;
198
+ try {
199
+ var ledger = _readLedger();
200
+ var wrote = false;
201
+ for (var d = 0; d < deliverables.length; d++) {
202
+ var row = deliverables[d];
203
+ if (ledger.submitted && ledger.submitted[row.order_id]) {
204
+ summary.skipped++;
205
+ continue;
206
+ }
207
+ var payload = _buildProofPayload(row);
208
+ var resp = await _withTimeout(hubClient.submitDelivery(row.order_id, payload), SUBMIT_TIMEOUT_MS);
209
+ if (resp && resp.ok) {
210
+ if (!ledger.submitted) ledger.submitted = {};
211
+ ledger.submitted[row.order_id] = Date.now();
212
+ wrote = true;
213
+ summary.submitted++;
214
+ console.log('[ATP-HB] Delivered order=' + row.order_id + ' asset=' + (row.result_asset_id || 'none') + ' (via heartbeat)');
215
+ } else {
216
+ var status = resp && resp.status;
217
+ var terminal = status === 400 || status === 404 || status === 409;
218
+ if (terminal) {
219
+ if (!ledger.submitted) ledger.submitted = {};
220
+ ledger.submitted[row.order_id] = -Date.now();
221
+ wrote = true;
222
+ }
223
+ summary.failed++;
224
+ console.log('[ATP-HB] Delivery failed order=' + row.order_id + ' status=' + (status || 'n/a')
225
+ + ' err=' + String((resp && resp.error) || 'unknown').slice(0, 120));
226
+ }
227
+ }
228
+ if (wrote) _writeLedger(ledger);
229
+ } finally {
230
+ _inflight = false;
231
+ }
232
+ return summary;
233
+ }
234
+
235
+ function _resetForTests() {
236
+ _inflight = false;
237
+ _lastRunAt = 0;
238
+ }
239
+
240
+ module.exports = {
241
+ handleHeartbeatSignals,
242
+ _internals: {
243
+ buildProofPayload: _buildProofPayload,
244
+ collectDeliverable: _collectDeliverable,
245
+ readLedger: _readLedger,
246
+ writeLedger: _writeLedger,
247
+ resetForTests: _resetForTests,
248
+ constants: {
249
+ HANDLER_COOLDOWN_MS,
250
+ SUBMIT_TIMEOUT_MS,
251
+ LEDGER_FILENAME,
252
+ },
253
+ },
254
+ };