@evomap/evolver 1.87.1 → 1.87.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 (63) hide show
  1. package/README.ja-JP.md +1 -1
  2. package/README.ko-KR.md +1 -1
  3. package/README.md +9 -8
  4. package/README.zh-CN.md +9 -8
  5. package/index.js +30 -11
  6. package/package.json +1 -1
  7. package/scripts/build_binaries.js +31 -7
  8. package/src/atp/atpExecute.js +35 -8
  9. package/src/atp/autoBuyer.js +155 -21
  10. package/src/atp/autoDeliver.js +16 -0
  11. package/src/atp/cli.js +98 -0
  12. package/src/atp/cliAutobuyPrompt.js +57 -64
  13. package/src/atp/hubClient.js +42 -4
  14. package/src/evolve/guards.js +1 -1
  15. package/src/evolve/pipeline/collect.js +1 -1
  16. package/src/evolve/pipeline/dispatch.js +1 -1
  17. package/src/evolve/pipeline/enrich.js +1 -1
  18. package/src/evolve/pipeline/hub.js +1 -1
  19. package/src/evolve/pipeline/select.js +1 -1
  20. package/src/evolve/pipeline/signals.js +1 -1
  21. package/src/evolve/utils.js +1 -1
  22. package/src/evolve.js +1 -1
  23. package/src/forceUpdate.js +2 -1
  24. package/src/gep/a2aProtocol.js +1 -1
  25. package/src/gep/assetStore.js +52 -5
  26. package/src/gep/candidateEval.js +1 -1
  27. package/src/gep/candidates.js +1 -1
  28. package/src/gep/contentHash.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/explore.js +1 -1
  35. package/src/gep/hash.js +1 -1
  36. package/src/gep/hubFetch.js +1 -1
  37. package/src/gep/hubReview.js +1 -1
  38. package/src/gep/hubSearch.js +1 -1
  39. package/src/gep/hubVerify.js +1 -1
  40. package/src/gep/learningSignals.js +1 -1
  41. package/src/gep/memoryGraph.js +1 -1
  42. package/src/gep/memoryGraphAdapter.js +1 -1
  43. package/src/gep/mutation.js +1 -1
  44. package/src/gep/narrativeMemory.js +1 -1
  45. package/src/gep/openPRRegistry.js +1 -1
  46. package/src/gep/paths.js +6 -2
  47. package/src/gep/personality.js +1 -1
  48. package/src/gep/policyCheck.js +1 -1
  49. package/src/gep/prompt.js +1 -1
  50. package/src/gep/recallVerifier.js +1 -1
  51. package/src/gep/reflection.js +1 -1
  52. package/src/gep/sanitize.js +57 -3
  53. package/src/gep/selector.js +1 -1
  54. package/src/gep/selfPR.js +34 -1
  55. package/src/gep/skill2gep.js +108 -29
  56. package/src/gep/skillDistiller.js +1 -1
  57. package/src/gep/solidify.js +1 -1
  58. package/src/gep/strategy.js +1 -1
  59. package/src/gep/workspaceKeychain.js +1 -1
  60. package/src/proxy/index.js +29 -9
  61. package/src/proxy/lifecycle/manager.js +97 -37
  62. package/src/proxy/router/messages_route.js +105 -5
  63. package/src/proxy/sync/engine.js +68 -31
package/src/atp/cli.js CHANGED
@@ -118,6 +118,27 @@ function parseVerifyArgs(args) {
118
118
  return { ok: true, opts: { orderId, action } };
119
119
  }
120
120
 
121
+ // `evolver atp <enable|disable|status>` — manage autoBuyer consent ack file.
122
+ // Non-TTY users (daemon, CI, hooks) opt in here instead of the interactive
123
+ // first-run prompt. Returns { ok, opts: { sub } } or { ok: false, error }.
124
+ function parseAtpArgs(args) {
125
+ if (!Array.isArray(args) || args.length === 0) {
126
+ return { ok: false, error: 'atp requires <enable|disable|status>' };
127
+ }
128
+ let sub = null;
129
+ for (let i = 0; i < args.length; i++) {
130
+ const a = args[i];
131
+ if (typeof a === 'string' && !a.startsWith('--')) {
132
+ sub = a.toLowerCase();
133
+ break;
134
+ }
135
+ }
136
+ if (!sub || !['enable', 'disable', 'status'].includes(sub)) {
137
+ return { ok: false, error: 'atp subcommand must be enable|disable|status (got: ' + sub + ')' };
138
+ }
139
+ return { ok: true, opts: { sub } };
140
+ }
141
+
121
142
  async function runBuy(opts, deps) {
122
143
  const atp = (deps && deps.atp) || require('./index');
123
144
  const consumerAgent = atp.consumerAgent;
@@ -226,6 +247,80 @@ async function runVerify(opts, deps) {
226
247
  }
227
248
  }
228
249
 
250
+ // Subcommand runner for `evolver atp enable|disable|status`. Writes the ack
251
+ // file via autoBuyer.setConsent so subsequent daemon starts pick it up. Does
252
+ // NOT mutate process.env — env override wins over the ack file by design,
253
+ // and we don't want a transient CLI invocation to silently shadow operator
254
+ // shell config.
255
+ // Detect whether an EVOLVER_ATP_AUTOBUY env override is currently set AND
256
+ // would supersede the ack we are about to write. Returns the effective env
257
+ // boolean (true=on, false=off) or null if env is unset / whitespace-only.
258
+ // Mirrors the trim-before-length-check rule from autoBuyer.getConsent so
259
+ // the CLI and the runtime agree on what "set" means (Bugbot PR #141).
260
+ function _envOverride() {
261
+ const raw = process.env.EVOLVER_ATP_AUTOBUY;
262
+ if (typeof raw !== 'string') return null;
263
+ const s = raw.trim().toLowerCase();
264
+ if (s.length === 0) return null;
265
+ return !(s === 'off' || s === '0' || s === 'false');
266
+ }
267
+
268
+ async function runAtp(opts, deps) {
269
+ const autoBuyer = (deps && deps.autoBuyer) || require('./autoBuyer');
270
+ const log = (deps && deps.log) || console.log;
271
+ const err = (deps && deps.err) || console.error;
272
+
273
+ try {
274
+ if (opts.sub === 'enable') {
275
+ const body = autoBuyer.setConsent(true);
276
+ log('[ATP] auto-spend ENABLED (consent recorded ' + body.acknowledged_at + ').');
277
+ log(' Caps: daily=' + (process.env.ATP_AUTOBUY_DAILY_CAP_CREDITS || '50') +
278
+ ', per-order=' + (process.env.ATP_AUTOBUY_PER_ORDER_CAP_CREDITS || '10') + ' credits.');
279
+ log(' Disable: evolver atp disable (or EVOLVER_ATP_AUTOBUY=off)');
280
+ // Loud warning if an env override will silently undo the ack at
281
+ // runtime. Bugbot PR #141 Medium: without this the user gets a
282
+ // false confirmation and real credits keep flowing the wrong way.
283
+ const envEnabled = _envOverride();
284
+ if (envEnabled === false) {
285
+ log('');
286
+ log('[ATP] WARNING: EVOLVER_ATP_AUTOBUY=' + process.env.EVOLVER_ATP_AUTOBUY +
287
+ ' is set in your environment and will OVERRIDE the ack file at runtime.');
288
+ log(' Auto-spend will stay OFF until you unset it (env wins over the ack file).');
289
+ return { exitCode: 0, data: body, envOverride: 'off' };
290
+ }
291
+ return { exitCode: 0, data: body };
292
+ }
293
+ if (opts.sub === 'disable') {
294
+ const body = autoBuyer.setConsent(false);
295
+ log('[ATP] auto-spend DISABLED (consent recorded ' + body.acknowledged_at + ').');
296
+ log(' Re-enable: evolver atp enable');
297
+ const envEnabled = _envOverride();
298
+ if (envEnabled === true) {
299
+ log('');
300
+ log('[ATP] WARNING: EVOLVER_ATP_AUTOBUY=' + process.env.EVOLVER_ATP_AUTOBUY +
301
+ ' is set in your environment and will OVERRIDE the ack file at runtime.');
302
+ log(' Auto-spend will stay ON (and continue charging credits) until you unset it.');
303
+ return { exitCode: 0, data: body, envOverride: 'on' };
304
+ }
305
+ return { exitCode: 0, data: body };
306
+ }
307
+ // status
308
+ const consent = autoBuyer.getConsent();
309
+ const ackPath = autoBuyer.getAckPath();
310
+ log('[ATP] auto-spend: ' + (consent.enabled ? 'ENABLED' : 'DISABLED') +
311
+ ' (source: ' + consent.source + ')');
312
+ log(' ack file: ' + ackPath);
313
+ if (consent.source === 'default') {
314
+ log(' Default policy (no ack file, no env override). Run `evolver atp disable`');
315
+ log(' to opt out, or `evolver atp enable` to record explicit opt-in.');
316
+ }
317
+ return { exitCode: 0, data: consent };
318
+ } catch (e) {
319
+ err('[ATP] atp command error: ' + (e && e.message || e));
320
+ return { exitCode: 1, error: String(e) };
321
+ }
322
+ }
323
+
229
324
  function printUsage() {
230
325
  return [
231
326
  'ATP subcommands:',
@@ -234,6 +329,7 @@ function printUsage() {
234
329
  ' evolver orders [--role=consumer|merchant] [--status=pending|verified|disputed|settled]',
235
330
  ' [--limit=N] [--json]',
236
331
  ' evolver verify <orderId> [--action=confirm|ai_judge]',
332
+ ' evolver atp <enable|disable|status> -- manage auto-spend consent',
237
333
  ].join('\n');
238
334
  }
239
335
 
@@ -241,8 +337,10 @@ module.exports = {
241
337
  parseBuyArgs,
242
338
  parseOrdersArgs,
243
339
  parseVerifyArgs,
340
+ parseAtpArgs,
244
341
  runBuy,
245
342
  runOrders,
246
343
  runVerify,
344
+ runAtp,
247
345
  printUsage,
248
346
  };
@@ -9,56 +9,26 @@
9
9
  * - ack file memory/atp-autobuy-ack.json does not exist (already decided)
10
10
  *
11
11
  * Outcomes:
12
- * - user answers y -> enabled=true written, session opts in immediately
13
- * - user answers n -> enabled=false written, prompt never shown again
14
- * - user answers later -> no file written, prompt shown next time
12
+ * - user answers y -> autoBuyer.setConsent(true) opts in for future sessions
13
+ * - user answers n -> autoBuyer.setConsent(false) prompt never shown again
14
+ * - user answers later -> no ack written, prompt shown next session
15
15
  * - any non-TTY/ack branch -> silent no-op
16
+ *
17
+ * setConsent failures (FS permission, disk full) are surfaced to the user
18
+ * via the output stream and returned as `reason: 'ack_write_failed'`; the
19
+ * decision field still reflects what the user typed.
16
20
  */
17
21
 
18
- const fs = require("fs");
19
- const path = require("path");
20
22
  const readline = require("readline");
23
+ const autoBuyer = require("./autoBuyer");
21
24
 
22
- const ACK_FILE_NAME = "atp-autobuy-ack.json";
23
-
24
- function _getMemoryDir() {
25
- try {
26
- return require("../gep/paths").getMemoryDir();
27
- } catch (_) {
28
- return process.env.MEMORY_DIR || path.join(process.cwd(), "memory");
29
- }
30
- }
31
-
32
- function _getAckPath() {
33
- return path.join(_getMemoryDir(), ACK_FILE_NAME);
34
- }
35
-
36
- function _readAck() {
37
- try {
38
- const raw = fs.readFileSync(_getAckPath(), "utf8");
39
- const parsed = JSON.parse(raw);
40
- if (!parsed || typeof parsed !== "object") return null;
41
- return parsed;
42
- } catch (_) {
43
- return null;
44
- }
45
- }
46
-
47
- function _writeAck(enabled) {
48
- const p = _getAckPath();
49
- try {
50
- fs.mkdirSync(path.dirname(p), { recursive: true });
51
- const body = {
52
- enabled: !!enabled,
53
- acknowledged_at: new Date().toISOString(),
54
- version: 1,
55
- };
56
- fs.writeFileSync(p, JSON.stringify(body, null, 2) + "\n", "utf8");
57
- return true;
58
- } catch (_) {
59
- return false;
60
- }
61
- }
25
+ // All ack file plumbing lives on autoBuyer (filename constant, path
26
+ // resolution, read with strict validation, atomic write via tmp+rename).
27
+ // cliAutobuyPrompt always reaches it through the public surface so the
28
+ // two modules cannot diverge on schema or validation — pre-consolidation
29
+ // drift bit us twice (Bugbot PR #141: duplicate writers + lenient-vs-
30
+ // strict reader). No __internals re-export here either: tests import
31
+ // autoBuyer directly so a future rename trips a single set of asserts.
62
32
 
63
33
  /**
64
34
  * @returns {"ack_present"|"env_set"|"non_tty"|"eligible"}
@@ -71,7 +41,7 @@ function classify(env, stdin) {
71
41
  if (!stdin || !stdin.isTTY) {
72
42
  return "non_tty";
73
43
  }
74
- if (_readAck()) {
44
+ if (autoBuyer.readAck()) {
75
45
  return "ack_present";
76
46
  }
77
47
  return "eligible";
@@ -116,11 +86,12 @@ async function runPrompt(opts) {
116
86
 
117
87
  try {
118
88
  output.write("\n");
119
- output.write("[ATP-AutoBuyer] Your evolver can automatically place small-priced\n");
120
- output.write("ATP orders when it detects a capability gap (default ON).\n");
121
- output.write(" - daily hard cap: ATP_AUTOBUY_DAILY_CAP_CREDITS (default applies)\n");
122
- output.write(" - per-order cap: ATP_AUTOBUY_PER_ORDER_CAP_CREDITS\n");
123
- output.write(" - set EVOLVER_ATP_AUTOBUY=off and restart to disable at any time.\n");
89
+ output.write("[ATP-AutoBuyer] Your evolver will automatically place small-priced\n");
90
+ output.write("ATP orders when it detects a capability gap. This spends real\n");
91
+ output.write("credits on the EvoMap hub and is ON by default for new installs.\n");
92
+ output.write(" - daily hard cap: ATP_AUTOBUY_DAILY_CAP_CREDITS (default 50)\n");
93
+ output.write(" - per-order cap: ATP_AUTOBUY_PER_ORDER_CAP_CREDITS (default 10)\n");
94
+ output.write(" - change later: evolver atp enable | evolver atp disable\n");
124
95
  output.write("\n");
125
96
  } catch (_) {
126
97
  return { prompted: false, decision: null, reason: "io_error" };
@@ -128,7 +99,7 @@ async function runPrompt(opts) {
128
99
 
129
100
  let answer;
130
101
  try {
131
- answer = await ask("Keep autoBuyer enabled for this session? [y/n/later] ", {
102
+ answer = await ask("Keep ATP auto-spend ON for future sessions? [y=keep enabled / n=disable / later=ask again next session] ", {
132
103
  input,
133
104
  output,
134
105
  });
@@ -136,26 +107,48 @@ async function runPrompt(opts) {
136
107
  return { prompted: true, decision: null, reason: "ask_error" };
137
108
  }
138
109
 
110
+ // For y/n we persist via autoBuyer.setConsent (atomic tmp+rename, throws
111
+ // on FS failure). If the write fails we MUST tell the user — for the 'n'
112
+ // path especially, since auto-spend is default-ON and a failed disable
113
+ // means the user typed "off" but the runtime keeps charging credits
114
+ // (Bugbot PR #141 Medium: unchecked ack write). Do NOT mutate process.env
115
+ // on success: that would double-signal and shadow any explicit operator
116
+ // preference set later.
117
+ function _persistConsent(enabled, decision) {
118
+ try {
119
+ autoBuyer.setConsent(enabled);
120
+ return { prompted: true, decision, reason: enabled ? "user_accepted" : "user_declined" };
121
+ } catch (err) {
122
+ try {
123
+ output.write("[ATP-AutoBuyer] WARN: failed to persist consent: " + (err && err.message || err) + "\n");
124
+ if (enabled) {
125
+ output.write(" Auto-spend will keep using the default-on policy until\n");
126
+ output.write(" the ack is saved (capped at the configured caps).\n");
127
+ } else {
128
+ output.write(" Auto-spend will STAY ON (default policy) until your opt-out\n");
129
+ output.write(" can be saved — your decline was not persisted.\n");
130
+ }
131
+ output.write(" Check disk space and write permissions on the memory dir, then run\n");
132
+ output.write(" `evolver atp " + (enabled ? "enable" : "disable") + "` to retry.\n");
133
+ } catch (_) { /* output stream is broken too — nothing more we can do */ }
134
+ return { prompted: true, decision, reason: "ack_write_failed" };
135
+ }
136
+ }
137
+
139
138
  if (answer === "y" || answer === "yes") {
140
- _writeAck(true);
141
- env.EVOLVER_ATP_AUTOBUY = "on";
142
- return { prompted: true, decision: "yes", reason: "user_accepted" };
139
+ return _persistConsent(true, "yes");
143
140
  }
144
141
  if (answer === "n" || answer === "no") {
145
- _writeAck(false);
146
- env.EVOLVER_ATP_AUTOBUY = "off";
147
- return { prompted: true, decision: "no", reason: "user_declined" };
142
+ return _persistConsent(false, "no");
148
143
  }
144
+ // Postpone: no ack written, so autoBuyer.getConsent() returns
145
+ // {enabled: true, source: 'default'} this session. Auto-spend keeps
146
+ // running under the default policy with caps; the prompt will fire again
147
+ // next interactive session so the user can confirm or opt out.
149
148
  return { prompted: true, decision: "later", reason: "user_postponed" };
150
149
  }
151
150
 
152
151
  module.exports = {
153
152
  runPrompt,
154
153
  classify,
155
- __internals: {
156
- ACK_FILE_NAME,
157
- _readAck,
158
- _writeAck,
159
- _getAckPath,
160
- },
161
154
  };
@@ -16,6 +16,7 @@
16
16
 
17
17
  const http = require('http');
18
18
  const { getHubUrl, buildHubHeaders, getNodeId } = require('../gep/a2aProtocol');
19
+ const { hubFetch } = require('../gep/hubFetch');
19
20
  const { getProxyUrl, getProxyToken } = require('../proxy/server/settings');
20
21
 
21
22
  function _isProxyMode() {
@@ -68,12 +69,33 @@ function _proxyRequest(method, path, body, timeoutMs) {
68
69
  });
69
70
  }
70
71
 
72
+ // Route through hubFetch() rather than the global `fetch()` for two
73
+ // reasons (both flagged by Cursor reviewers on PR #160):
74
+ //
75
+ // 1. Dispatcher mixing (Bugbot HIGH): `strictUndiciAgent` is an Agent
76
+ // from the *installed* `undici` package, but `global.fetch` is
77
+ // backed by Node's *internal* undici. Passing one to the other
78
+ // throws `UND_ERR_INVALID_ARG: invalid onRequestStart method` at
79
+ // request time — exactly the failure mode the comment at the top
80
+ // of hubFetch.js calls out. hubFetch already routes through
81
+ // `undici.fetch` from the same package as its Agent, so all calls
82
+ // that go through hubFetch are immune.
83
+ //
84
+ // 2. Case-sensitive scheme check (Security Reviewer MEDIUM): a hand-
85
+ // rolled `endpoint.startsWith('https:')` would skip the strict
86
+ // dispatcher for `HTTPS://...`. hubFetch's `_validateHubUrl` uses
87
+ // `new URL(url).protocol`, which normalises to lowercase, so
88
+ // routing through it eliminates the bug class.
89
+ //
90
+ // Routing through hubFetch also inherits the URL-scheme enforcement and
91
+ // the EVOMAP_HUB_ALLOW_INSECURE escape hatch automatically; we no
92
+ // longer need the explicit `enforceHubScheme` guard here.
71
93
  function _hubPost(pathSuffix, body, timeoutMs) {
72
94
  const hubUrl = getHubUrl();
73
95
  if (!hubUrl) return Promise.resolve({ ok: false, error: 'no_hub_url' });
74
96
  const endpoint = hubUrl.replace(/\/+$/, '') + pathSuffix;
75
97
  const timeout = timeoutMs || require('../config').HTTP_TRANSPORT_TIMEOUT_MS;
76
- return fetch(endpoint, {
98
+ return hubFetch(endpoint, {
77
99
  method: 'POST',
78
100
  headers: buildHubHeaders(),
79
101
  body: JSON.stringify(body),
@@ -83,7 +105,17 @@ function _hubPost(pathSuffix, body, timeoutMs) {
83
105
  if (!res.ok) return res.text().then(function (t) { return { ok: false, status: res.status, error: t.slice(0, 400) }; });
84
106
  return res.json().then(function (data) { return { ok: true, data: data }; });
85
107
  })
86
- .catch(function (err) { return { ok: false, error: err.message }; });
108
+ .catch(function (err) {
109
+ // hubFetch throws synchronously (rejected Promise) when the URL
110
+ // fails scheme validation in secure mode. Translate to the same
111
+ // structured envelope the previous in-line guard produced so the
112
+ // caller contract is unchanged.
113
+ const msg = (err && err.message) || String(err);
114
+ if (msg.indexOf('[hubFetch]') !== -1) {
115
+ return { ok: false, error: 'tls_refused: ' + msg };
116
+ }
117
+ return { ok: false, error: msg };
118
+ });
87
119
  }
88
120
 
89
121
  function _hubGet(pathSuffix, timeoutMs) {
@@ -91,7 +123,7 @@ function _hubGet(pathSuffix, timeoutMs) {
91
123
  if (!hubUrl) return Promise.resolve({ ok: false, error: 'no_hub_url' });
92
124
  const endpoint = hubUrl.replace(/\/+$/, '') + pathSuffix;
93
125
  const timeout = timeoutMs || require('../config').HTTP_TRANSPORT_TIMEOUT_MS;
94
- return fetch(endpoint, {
126
+ return hubFetch(endpoint, {
95
127
  method: 'GET',
96
128
  headers: buildHubHeaders(),
97
129
  signal: AbortSignal.timeout(timeout),
@@ -100,7 +132,13 @@ function _hubGet(pathSuffix, timeoutMs) {
100
132
  if (!res.ok) return res.text().then(function (t) { return { ok: false, status: res.status, error: t.slice(0, 400) }; });
101
133
  return res.json().then(function (data) { return { ok: true, data: data }; });
102
134
  })
103
- .catch(function (err) { return { ok: false, error: err.message }; });
135
+ .catch(function (err) {
136
+ const msg = (err && err.message) || String(err);
137
+ if (msg.indexOf('[hubFetch]') !== -1) {
138
+ return { ok: false, error: 'tls_refused: ' + msg };
139
+ }
140
+ return { ok: false, error: msg };
141
+ });
104
142
  }
105
143
 
106
144
  // Dispatcher: choose proxy or direct hub based on env + proxy availability.