@evomap/evolver 1.89.9 → 1.89.11

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 (71) hide show
  1. package/README.ja-JP.md +1 -1
  2. package/README.ko-KR.md +1 -1
  3. package/README.md +2 -1
  4. package/README.zh-CN.md +1 -1
  5. package/assets/cover.png +0 -0
  6. package/index.js +5 -3
  7. package/package.json +1 -1
  8. package/src/adapters/scripts/evolver-session-end.js +18 -37
  9. package/src/atp/atpExecute.js +27 -71
  10. package/src/atp/serviceHelper.js +28 -36
  11. package/src/config.js +17 -0
  12. package/src/evolve/guards.js +1 -1
  13. package/src/evolve/pipeline/collect.js +1 -1
  14. package/src/evolve/pipeline/dispatch.js +1 -1
  15. package/src/evolve/pipeline/enrich.js +1 -1
  16. package/src/evolve/pipeline/hub.js +1 -1
  17. package/src/evolve/pipeline/select.js +1 -1
  18. package/src/evolve/pipeline/signals.js +1 -1
  19. package/src/evolve/utils.js +1 -1
  20. package/src/evolve.js +1 -1
  21. package/src/gep/a2aProtocol.js +1 -1
  22. package/src/gep/antiAbuseTelemetry.js +1 -1
  23. package/src/gep/autoDistillConv.js +1 -1
  24. package/src/gep/autoDistillLlm.js +1 -1
  25. package/src/gep/candidateEval.js +1 -1
  26. package/src/gep/candidates.js +1 -1
  27. package/src/gep/contentHash.js +1 -1
  28. package/src/gep/conversationSniffer.js +1 -1
  29. package/src/gep/crypto.js +1 -1
  30. package/src/gep/curriculum.js +1 -1
  31. package/src/gep/deviceId.js +1 -1
  32. package/src/gep/envFingerprint.js +1 -1
  33. package/src/gep/epigenetics.js +1 -1
  34. package/src/gep/execBridge.js +1 -1
  35. package/src/gep/explore.js +1 -1
  36. package/src/gep/hash.js +1 -1
  37. package/src/gep/hubFetch.js +1 -1
  38. package/src/gep/hubReview.js +1 -1
  39. package/src/gep/hubSearch.js +1 -1
  40. package/src/gep/hubVerify.js +1 -1
  41. package/src/gep/learningSignals.js +1 -1
  42. package/src/gep/memoryGraph.js +1 -1
  43. package/src/gep/memoryGraphAdapter.js +1 -1
  44. package/src/gep/mutation.js +1 -1
  45. package/src/gep/narrativeMemory.js +1 -1
  46. package/src/gep/oauthLogin.js +9 -3
  47. package/src/gep/openPRRegistry.js +1 -1
  48. package/src/gep/personality.js +1 -1
  49. package/src/gep/policyCheck.js +1 -1
  50. package/src/gep/privacyClient.js +10 -9
  51. package/src/gep/prompt.js +1 -1
  52. package/src/gep/recallInject.js +1 -1
  53. package/src/gep/recallVerifier.js +1 -1
  54. package/src/gep/reflection.js +1 -1
  55. package/src/gep/savingsCore.js +1 -1
  56. package/src/gep/selector.js +1 -1
  57. package/src/gep/signals.js +85 -17
  58. package/src/gep/skillDistiller.js +1 -1
  59. package/src/gep/solidify.js +1 -1
  60. package/src/gep/strategy.js +1 -1
  61. package/src/gep/tokenSavings.js +1 -1
  62. package/src/gep/workspaceKeychain.js +1 -1
  63. package/src/proxy/extensions/traceControl.js +1 -1
  64. package/src/proxy/index.js +369 -20
  65. package/src/proxy/inject.js +1 -1
  66. package/src/proxy/router/models_route.js +52 -0
  67. package/src/proxy/router/ollama_route.js +103 -0
  68. package/src/proxy/router/vertex_route.js +110 -0
  69. package/src/proxy/server/routes.js +30 -7
  70. package/src/proxy/trace/extractor.js +1 -1
  71. package/src/proxy/trace/usage.js +1 -1
package/README.ja-JP.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # 🧬 Evolver
2
2
 
3
- [![GitHub stars](https://img.shields.io/badge/Stars-8.5k-2b3137?logo=github&logoColor=white)](https://github.com/EvoMap/evolver/stargazers)
3
+ [![GitHub stars](https://img.shields.io/badge/Stars-8.7k-2b3137?logo=github&logoColor=white)](https://github.com/EvoMap/evolver/stargazers)
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
  [![npm downloads](https://img.shields.io/npm/dm/@evomap/evolver.svg)](https://www.npmjs.com/package/@evomap/evolver)
package/README.ko-KR.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # 🧬 Evolver
2
2
 
3
- [![GitHub stars](https://img.shields.io/badge/Stars-8.5k-2b3137?logo=github&logoColor=white)](https://github.com/EvoMap/evolver/stargazers)
3
+ [![GitHub stars](https://img.shields.io/badge/Stars-8.7k-2b3137?logo=github&logoColor=white)](https://github.com/EvoMap/evolver/stargazers)
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
  [![npm downloads](https://img.shields.io/npm/dm/@evomap/evolver.svg)](https://www.npmjs.com/package/@evomap/evolver)
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # 🧬 Evolver
2
2
 
3
- [![GitHub stars](https://img.shields.io/badge/Stars-8.5k-2b3137?logo=github&logoColor=white)](https://github.com/EvoMap/evolver/stargazers)
3
+ [![GitHub stars](https://img.shields.io/badge/Stars-8.7k-2b3137?logo=github&logoColor=white)](https://github.com/EvoMap/evolver/stargazers)
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
  [![npm downloads](https://img.shields.io/npm/dm/@evomap/evolver.svg)](https://www.npmjs.com/package/@evomap/evolver)
@@ -416,6 +416,7 @@ Evolver is designed to be **environment-agnostic**.
416
416
  | `EVOLVE_STRATEGY` | Evolution strategy preset (`balanced` / `innovate` / `harden` / `repair-only`) | `balanced` |
417
417
  | `A2A_HUB_URL` | [EvoMap Hub](https://evomap.ai) URL | _(unset, offline mode)_ |
418
418
  | `A2A_NODE_ID` | Your node identity on the network | _(auto-generated from device fingerprint)_ |
419
+ | `EVOMAP_HUB_IP_FAMILY` | Hub egress IP-family policy: `ipv4first` tries IPv4 first and falls back to dual-stack, `auto` uses dual-stack as the primary path, `ipv4-only` disables fallback | `ipv4first` |
419
420
  | `HEARTBEAT_INTERVAL_MS` | Hub heartbeat interval | `360000` (6 min) |
420
421
  | `MEMORY_DIR` | Memory files path | `./memory` |
421
422
  | `EVOLVE_REPORT_TOOL` | Tool name for reporting results | `message` |
package/README.zh-CN.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # 🧬 Evolver
2
2
 
3
- [![GitHub stars](https://img.shields.io/badge/Stars-8.5k-2b3137?logo=github&logoColor=white)](https://github.com/EvoMap/evolver/stargazers)
3
+ [![GitHub stars](https://img.shields.io/badge/Stars-8.7k-2b3137?logo=github&logoColor=white)](https://github.com/EvoMap/evolver/stargazers)
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
  [![npm downloads](https://img.shields.io/npm/dm/@evomap/evolver.svg)](https://www.npmjs.com/package/@evomap/evolver)
package/assets/cover.png CHANGED
Binary file
package/index.js CHANGED
@@ -2042,6 +2042,7 @@ async function main() {
2042
2042
  }
2043
2043
 
2044
2044
  const { getHubUrl, getNodeId, buildHubHeaders, sendHelloToHub, getHubNodeSecret } = require('./src/gep/a2aProtocol');
2045
+ const { hubFetch } = require('./src/gep/hubFetch');
2045
2046
 
2046
2047
  const hubUrl = getHubUrl();
2047
2048
  if (!hubUrl) {
@@ -2071,7 +2072,7 @@ async function main() {
2071
2072
 
2072
2073
  console.log('[fetch] Downloading skill: ' + skillId);
2073
2074
 
2074
- const resp = await fetch(endpoint, {
2075
+ const resp = await hubFetch(endpoint, {
2075
2076
  method: 'POST',
2076
2077
  headers: buildHubHeaders(),
2077
2078
  body: JSON.stringify({ sender_id: nodeId }),
@@ -2253,6 +2254,7 @@ async function main() {
2253
2254
 
2254
2255
  } else if (command === 'sync') {
2255
2256
  const { getHubUrl, getNodeId, buildHubHeaders, sendHelloToHub, getHubNodeSecret } = require('./src/gep/a2aProtocol');
2257
+ const { hubFetch } = require('./src/gep/hubFetch');
2256
2258
  const { upsertGene, upsertCapsule, loadGenes, loadCapsules } = require('./src/gep/assetStore');
2257
2259
  const { getGepAssetsDir, getMemoryDir } = require('./src/gep/paths');
2258
2260
 
@@ -2321,7 +2323,7 @@ async function main() {
2321
2323
  if (v != null) url += '&' + k + '=' + encodeURIComponent(v);
2322
2324
  }
2323
2325
  }
2324
- const resp = await fetch(url, {
2326
+ const resp = await hubFetch(url, {
2325
2327
  method: 'GET',
2326
2328
  headers: buildHubHeaders(),
2327
2329
  signal: AbortSignal.timeout(30000),
@@ -2438,7 +2440,7 @@ async function main() {
2438
2440
  try {
2439
2441
  let payload = asset.payload;
2440
2442
  if (!payload) {
2441
- const detailResp = await fetch(baseUrl + '/a2a/assets/' + encodeURIComponent(assetId) + '?detailed=true', {
2443
+ const detailResp = await hubFetch(baseUrl + '/a2a/assets/' + encodeURIComponent(assetId) + '?detailed=true', {
2442
2444
  method: 'GET',
2443
2445
  headers: buildHubHeaders(),
2444
2446
  signal: AbortSignal.timeout(15000),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@evomap/evolver",
3
- "version": "1.89.9",
3
+ "version": "1.89.11",
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": {
@@ -8,8 +8,6 @@
8
8
  const fs = require('fs');
9
9
  const path = require('path');
10
10
  const os = require('os');
11
- const https = require('https');
12
- const http = require('http');
13
11
  const { spawnSync } = require('child_process');
14
12
  // 10 MB — prevents RangeError on large child process output (e.g. git log/diff
15
13
  // on large repos). See GHSA reports / issue #451.
@@ -113,15 +111,17 @@ function detectSignals(text) {
113
111
  return [...new Set(signals)];
114
112
  }
115
113
 
116
- function recordToHub(outcome) {
114
+ function loadHubFetch() {
115
+ const evolverRoot = findEvolverRoot();
116
+ return require(path.join(evolverRoot, 'src', 'gep', 'hubFetch')).hubFetch;
117
+ }
118
+
119
+ async function recordToHub(outcome) {
117
120
  const hubUrl = process.env.EVOMAP_HUB_URL || process.env.A2A_HUB_URL;
118
121
  const apiKey = process.env.EVOMAP_API_KEY || process.env.A2A_NODE_SECRET;
119
122
  const nodeId = process.env.EVOMAP_NODE_ID || process.env.A2A_NODE_ID;
120
123
  if (!hubUrl || !apiKey) return false;
121
124
 
122
- // Use Node.js built-in http/https instead of curl so this works on all
123
- // platforms, including Windows where curl may not be available or may be
124
- // an older, incompatible version bundled with some environments.
125
125
  try {
126
126
  const payload = JSON.stringify({
127
127
  gene_id: outcome.geneId || 'ad_hoc',
@@ -133,37 +133,18 @@ function recordToHub(outcome) {
133
133
  });
134
134
 
135
135
  const endpoint = hubUrl.replace(/\/+$/, '') + '/a2a/evolution/record';
136
- let parsedUrl;
137
- try { parsedUrl = new URL(endpoint); } catch { return false; }
138
-
139
- const isHttps = parsedUrl.protocol === 'https:';
140
- const transport = isHttps ? https : http;
141
-
142
- return new Promise((resolve) => {
143
- const req = transport.request(
144
- {
145
- hostname: parsedUrl.hostname,
146
- port: parsedUrl.port || (isHttps ? 443 : 80),
147
- path: parsedUrl.pathname + (parsedUrl.search || ''),
148
- method: 'POST',
149
- headers: {
150
- 'Content-Type': 'application/json',
151
- 'Authorization': `Bearer ${apiKey}`,
152
- 'Content-Length': Buffer.byteLength(payload),
153
- },
154
- timeout: 8000,
155
- },
156
- (res) => {
157
- // Drain the response to free the socket; we only care about status.
158
- res.resume();
159
- resolve(res.statusCode >= 200 && res.statusCode < 300);
160
- }
161
- );
162
- req.on('error', () => resolve(false));
163
- req.on('timeout', () => { req.destroy(); resolve(false); });
164
- req.write(payload);
165
- req.end();
136
+ const hubFetch = loadHubFetch();
137
+ const res = await hubFetch(endpoint, {
138
+ method: 'POST',
139
+ headers: {
140
+ 'Content-Type': 'application/json',
141
+ 'Authorization': `Bearer ${apiKey}`,
142
+ },
143
+ body: payload,
144
+ signal: AbortSignal.timeout(8000),
166
145
  });
146
+ try { await res.text(); } catch (_) {}
147
+ return res.ok;
167
148
  } catch {
168
149
  return false;
169
150
  }
@@ -250,7 +231,7 @@ function main() {
250
231
  process.stdin.on('data', chunk => { inputData += chunk; });
251
232
  process.stdin.on('end', () => {
252
233
  if (handled) return;
253
- // recordToHub is async (uses Node.js http/https); wrap the rest of the
234
+ // recordToHub is async (uses the shared Hub fetch transport); wrap the rest of the
254
235
  // handler in an immediately-invoked async function so we can await it
255
236
  // while still honouring the watchdog timeout and the `handled` guard.
256
237
  (async () => {
@@ -22,12 +22,10 @@
22
22
  // idempotent server-side).
23
23
 
24
24
  const fs = require('fs');
25
- const http = require('http');
26
- const https = require('https');
27
25
  const crypto = require('crypto');
28
26
 
29
27
  const { computeAssetId } = require('../gep/contentHash');
30
- const { enforceHubScheme, strictHttpsAgent } = require('../gep/hubFetch');
28
+ const { hubFetch } = require('../gep/hubFetch');
31
29
  const {
32
30
  getNodeId,
33
31
  getHubUrl,
@@ -126,77 +124,35 @@ function _publishUrl() {
126
124
  return base + '/a2a/publish';
127
125
  }
128
126
 
129
- function _postJson(urlStr, body, timeoutMs) {
130
- return new Promise(function (resolve) {
131
- // Same TLS posture as hubFetch: refuse plain http:// unless
132
- // EVOMAP_HUB_ALLOW_INSECURE=1. Before this guard the function
133
- // silently fell back to `lib = http` for any non-https URL, so an
134
- // operator override `A2A_HUB_URL=http://...` would send /a2a/publish
135
- // and /a2a/task/complete in cleartext while hubFetch-routed calls
136
- // (e.g. /a2a/verify-solidify) refused the same URL — inconsistent
137
- // TLS enforcement across modules.
138
- try {
139
- enforceHubScheme(urlStr);
140
- } catch (e) {
141
- resolve({ ok: false, error: 'tls_refused: ' + (e && e.message) });
142
- return;
143
- }
144
- let parsed;
145
- try {
146
- parsed = new URL(urlStr);
147
- } catch (e) {
148
- resolve({ ok: false, error: 'invalid_url: ' + (e && e.message) });
149
- return;
150
- }
151
- const isHttps = parsed.protocol === 'https:';
152
- const lib = isHttps ? https : http;
153
- const payload = JSON.stringify(body || {});
154
- const headers = Object.assign(
155
- { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(payload) },
156
- buildHubHeaders() || {},
157
- );
158
- // Pin TLS cert verification for https calls so a globally-disabled
159
- // NODE_TLS_REJECT_UNAUTHORIZED=0 cannot weaken the Hub channel
160
- // (Cursor Security Reviewer #160 Medium). hubFetch enforces the
161
- // same via its undici dispatcher; this is the Node-native-https
162
- // equivalent.
163
- //
164
- // Skipped under EVOMAP_HUB_ALLOW_INSECURE=1 so local-dev / self-
165
- // signed mock hubs that legitimately rely on
166
- // NODE_TLS_REJECT_UNAUTHORIZED=0 still work.
167
- const requestOpts = {
168
- hostname: parsed.hostname,
169
- port: parsed.port || (isHttps ? 443 : 80),
170
- path: parsed.pathname + (parsed.search || ''),
127
+ async function _postJson(urlStr, body, timeoutMs) {
128
+ const payload = JSON.stringify(body || {});
129
+ const headers = Object.assign(
130
+ { 'Content-Type': 'application/json' },
131
+ buildHubHeaders() || {},
132
+ );
133
+
134
+ try {
135
+ const res = await hubFetch(urlStr, {
171
136
  method: 'POST',
172
137
  headers: headers,
173
- timeout: timeoutMs || PUBLISH_TIMEOUT_MS,
174
- };
175
- if (isHttps && process.env.EVOMAP_HUB_ALLOW_INSECURE !== '1') {
176
- requestOpts.agent = strictHttpsAgent;
138
+ body: payload,
139
+ signal: AbortSignal.timeout(timeoutMs || PUBLISH_TIMEOUT_MS),
140
+ });
141
+ const text = await res.text();
142
+ let data = null;
143
+ try { data = text ? JSON.parse(text) : null; } catch (e) { data = { raw: text }; }
144
+ if (res.status >= 200 && res.status < 300) {
145
+ return { ok: true, status: res.status, data };
177
146
  }
178
- const req = lib.request(
179
- requestOpts,
180
- function (res) {
181
- const chunks = [];
182
- res.on('data', function (c) { chunks.push(c); });
183
- res.on('end', function () {
184
- const text = Buffer.concat(chunks).toString('utf8');
185
- let data = null;
186
- try { data = text ? JSON.parse(text) : null; } catch (e) { data = { raw: text }; }
187
- if (res.statusCode >= 200 && res.statusCode < 300) {
188
- resolve({ ok: true, status: res.statusCode, data });
189
- } else {
190
- resolve({ ok: false, status: res.statusCode, data, error: 'http_' + res.statusCode });
191
- }
192
- });
193
- },
194
- );
195
- req.on('timeout', function () { req.destroy(new Error('timeout')); });
196
- req.on('error', function (err) { resolve({ ok: false, error: err.message }); });
197
- req.write(payload);
198
- req.end();
199
- });
147
+ return { ok: false, status: res.status, data, error: 'http_' + res.status };
148
+ } catch (err) {
149
+ const msg = (err && err.message) || String(err);
150
+ if (msg.indexOf('[hubFetch]') !== -1) {
151
+ if (/not a valid URL/i.test(msg)) return { ok: false, error: 'invalid_url: ' + msg };
152
+ return { ok: false, error: 'tls_refused: ' + msg };
153
+ }
154
+ return { ok: false, error: msg };
155
+ }
200
156
  }
201
157
 
202
158
  async function _ensureNodeSecret() {
@@ -1,6 +1,32 @@
1
1
  // ATP Service Helper -- wraps marketplace service publishing for merchant agents.
2
2
 
3
3
  const { getNodeId, buildHubHeaders, getHubUrl } = require('../gep/a2aProtocol');
4
+ const { hubFetch } = require('../gep/hubFetch');
5
+ const { HTTP_TRANSPORT_TIMEOUT_MS } = require('../config');
6
+
7
+ async function postService(endpoint, body) {
8
+ try {
9
+ const res = await hubFetch(endpoint, {
10
+ method: 'POST',
11
+ headers: Object.assign({ 'Content-Type': 'application/json' }, buildHubHeaders() || {}),
12
+ body: JSON.stringify(body),
13
+ signal: AbortSignal.timeout(HTTP_TRANSPORT_TIMEOUT_MS),
14
+ });
15
+
16
+ if (!res.ok) {
17
+ const t = await res.text();
18
+ return { ok: false, status: res.status, error: t.slice(0, 400) };
19
+ }
20
+ const data = await res.json();
21
+ return { ok: true, data };
22
+ } catch (err) {
23
+ const msg = (err && err.message) || String(err);
24
+ if (msg.indexOf('[hubFetch]') !== -1) {
25
+ return { ok: false, error: 'tls_refused: ' + msg };
26
+ }
27
+ return { ok: false, error: msg };
28
+ }
29
+ }
4
30
 
5
31
  /**
6
32
  * Publish a ServiceListing via the Hub marketplace API.
@@ -21,7 +47,6 @@ async function publishService(svc) {
21
47
 
22
48
  const nodeId = getNodeId();
23
49
  const endpoint = hubUrl.replace(/\/+$/, '') + '/a2a/service/publish';
24
- const timeout = require('../config').HTTP_TRANSPORT_TIMEOUT_MS;
25
50
 
26
51
  const body = {
27
52
  sender_id: nodeId,
@@ -35,23 +60,7 @@ async function publishService(svc) {
35
60
  recipe_id: svc.recipeId,
36
61
  };
37
62
 
38
- try {
39
- const res = await fetch(endpoint, {
40
- method: 'POST',
41
- headers: buildHubHeaders(),
42
- body: JSON.stringify(body),
43
- signal: AbortSignal.timeout(timeout),
44
- });
45
-
46
- if (!res.ok) {
47
- const t = await res.text();
48
- return { ok: false, status: res.status, error: t.slice(0, 400) };
49
- }
50
- const data = await res.json();
51
- return { ok: true, data };
52
- } catch (err) {
53
- return { ok: false, error: err.message };
54
- }
63
+ return postService(endpoint, body);
55
64
  }
56
65
 
57
66
  /**
@@ -66,7 +75,6 @@ async function updateService(listingId, updates) {
66
75
 
67
76
  const nodeId = getNodeId();
68
77
  const endpoint = hubUrl.replace(/\/+$/, '') + '/a2a/service/update';
69
- const timeout = require('../config').HTTP_TRANSPORT_TIMEOUT_MS;
70
78
 
71
79
  const body = {
72
80
  sender_id: nodeId,
@@ -74,23 +82,7 @@ async function updateService(listingId, updates) {
74
82
  ...updates,
75
83
  };
76
84
 
77
- try {
78
- const res = await fetch(endpoint, {
79
- method: 'POST',
80
- headers: buildHubHeaders(),
81
- body: JSON.stringify(body),
82
- signal: AbortSignal.timeout(timeout),
83
- });
84
-
85
- if (!res.ok) {
86
- const t = await res.text();
87
- return { ok: false, status: res.status, error: t.slice(0, 400) };
88
- }
89
- const data = await res.json();
90
- return { ok: true, data };
91
- } catch (err) {
92
- return { ok: false, error: err.message };
93
- }
85
+ return postService(endpoint, body);
94
86
  }
95
87
 
96
88
  module.exports = {
package/src/config.js CHANGED
@@ -202,6 +202,20 @@ function reuseAttributionMode() {
202
202
  return v === 'shadow' ? 'shadow' : 'off';
203
203
  }
204
204
 
205
+ // Opt-in Hub reuse-OUTCOME reporting (P4-a Slice B, client side). When 'on', the
206
+ // evolver POSTs {signals, status, used_asset_ids} to the Hub's /a2a/memory/record
207
+ // so the reuse-reward attribution pipeline (which reads THAT endpoint, not the
208
+ // inert reuse_attribution blob on /a2a/memory/event) finally gets data.
209
+ // MONEY-ADJACENT and charges Hub credits -> default 'off'. The Hub still
210
+ // re-verifies every used_asset_id against its own AssetFetcher rows before
211
+ // crediting. Also requires EVOLVER_REUSE_ATTRIBUTION=shadow, which is what builds
212
+ // the cycle-correlated, timestamp-guarded attribution this report consumes.
213
+ const OUTCOME_REPORT_MODE = envStr('EVOLVER_OUTCOME_REPORT', 'off');
214
+ function outcomeReportMode() {
215
+ const v = String(process.env.EVOLVER_OUTCOME_REPORT || OUTCOME_REPORT_MODE || 'off').toLowerCase().trim();
216
+ return v === 'on' || v === 'enforce' || v === 'true' ? 'on' : 'off';
217
+ }
218
+
205
219
  // --- Anti-abuse telemetry (privacy-preserving heartbeat summary) ---
206
220
  // Enabled by default. In heartbeat mode, clients attach a small
207
221
  // `meta.anti_abuse` envelope with low-sensitive hashes, source-confidence
@@ -306,6 +320,9 @@ module.exports = {
306
320
  // Reuse attribution (P4-a Slice A)
307
321
  REUSE_ATTRIBUTION_MODE,
308
322
  reuseAttributionMode,
323
+ // Reuse-outcome Hub reporting (P4-a Slice B, opt-in)
324
+ OUTCOME_REPORT_MODE,
325
+ outcomeReportMode,
309
326
  // Anti-abuse telemetry
310
327
  ANTI_ABUSE_TELEMETRY_MODE,
311
328
  antiAbuseTelemetryMode,