@evomap/evolver 1.90.0 → 2.0.0-beta.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.
Files changed (231) hide show
  1. package/README.md +27 -564
  2. package/bin/evolver-llm-proxy.js +3 -0
  3. package/bin/evolver-mcp.js +2 -0
  4. package/bin/evolver-proxy.js +3 -0
  5. package/bin/evolver.js +4 -0
  6. package/index.js +1 -3670
  7. package/package.json +32 -62
  8. package/CONTRIBUTING.md +0 -19
  9. package/LICENSE +0 -641
  10. package/README.ja-JP.md +0 -521
  11. package/README.ko-KR.md +0 -520
  12. package/README.zh-CN.md +0 -531
  13. package/SKILL.md +0 -365
  14. package/assets/cover.png +0 -0
  15. package/assets/gep/genes.seed.json +0 -496
  16. package/conformance/savings-core/constants.json +0 -30
  17. package/conformance/savings-core/golden-vectors.json +0 -333
  18. package/scripts/a2a_export.js +0 -63
  19. package/scripts/a2a_ingest.js +0 -79
  20. package/scripts/a2a_promote.js +0 -118
  21. package/scripts/analyze_by_skill.js +0 -121
  22. package/scripts/build_binaries.js +0 -479
  23. package/scripts/check-changelog.js +0 -166
  24. package/scripts/extract_log.js +0 -85
  25. package/scripts/generate_history.js +0 -75
  26. package/scripts/gep_append_event.js +0 -96
  27. package/scripts/gep_personality_report.js +0 -234
  28. package/scripts/human_report.js +0 -147
  29. package/scripts/recall-verify-report.js +0 -234
  30. package/scripts/recover_loop.js +0 -61
  31. package/scripts/refresh_stars_badge.js +0 -168
  32. package/scripts/seed-merchants.js +0 -91
  33. package/scripts/skill2recipes.js +0 -118
  34. package/scripts/suggest_version.js +0 -89
  35. package/scripts/validate-modules.js +0 -38
  36. package/scripts/validate-suite.js +0 -78
  37. package/src/adapters/claudeCode.js +0 -194
  38. package/src/adapters/codex.js +0 -216
  39. package/src/adapters/cursor.js +0 -91
  40. package/src/adapters/hookAdapter.js +0 -469
  41. package/src/adapters/kiro.js +0 -195
  42. package/src/adapters/opencode.js +0 -326
  43. package/src/adapters/scripts/_lockPaths.js +0 -74
  44. package/src/adapters/scripts/_memoryFiltering.js +0 -35
  45. package/src/adapters/scripts/_runtimePaths.js +0 -440
  46. package/src/adapters/scripts/evolver-session-end.js +0 -321
  47. package/src/adapters/scripts/evolver-session-start.js +0 -587
  48. package/src/adapters/scripts/evolver-signal-detect.js +0 -98
  49. package/src/adapters/scripts/evolver-task-recall.js +0 -173
  50. package/src/atp/atpExecute.js +0 -283
  51. package/src/atp/atpTaskPickup.js +0 -233
  52. package/src/atp/autoBuyer.js +0 -382
  53. package/src/atp/autoDeliver.js +0 -215
  54. package/src/atp/cli.js +0 -354
  55. package/src/atp/cliAutobuyPrompt.js +0 -154
  56. package/src/atp/consumerAgent.js +0 -157
  57. package/src/atp/defaultHandler.js +0 -69
  58. package/src/atp/heartbeatSignalsHandler.js +0 -254
  59. package/src/atp/hubClient.js +0 -317
  60. package/src/atp/index.js +0 -38
  61. package/src/atp/merchantAgent.js +0 -118
  62. package/src/atp/protocol.js +0 -41
  63. package/src/atp/questionComposer.js +0 -133
  64. package/src/atp/serviceHelper.js +0 -92
  65. package/src/canary.js +0 -13
  66. package/src/config.js +0 -351
  67. package/src/evolve/guards.js +0 -1
  68. package/src/evolve/pipeline/collect.js +0 -1
  69. package/src/evolve/pipeline/dispatch.js +0 -1
  70. package/src/evolve/pipeline/enrich.js +0 -1
  71. package/src/evolve/pipeline/hub.js +0 -1
  72. package/src/evolve/pipeline/select.js +0 -1
  73. package/src/evolve/pipeline/signals.js +0 -1
  74. package/src/evolve/utils.js +0 -1
  75. package/src/evolve.js +0 -1
  76. package/src/experiment/agentRunner.js +0 -229
  77. package/src/experiment/cli.js +0 -159
  78. package/src/experiment/comparison.js +0 -233
  79. package/src/experiment/metrics.js +0 -75
  80. package/src/forceUpdate.js +0 -991
  81. package/src/gep/a2a.js +0 -173
  82. package/src/gep/a2aProtocol.js +0 -1
  83. package/src/gep/analyzer.js +0 -35
  84. package/src/gep/antiAbuseTelemetry.js +0 -1
  85. package/src/gep/assetCallLog.js +0 -197
  86. package/src/gep/assetStore.js +0 -723
  87. package/src/gep/assets.js +0 -36
  88. package/src/gep/autoDistillConv.js +0 -1
  89. package/src/gep/autoDistillLlm.js +0 -1
  90. package/src/gep/bridge.js +0 -138
  91. package/src/gep/candidateEval.js +0 -1
  92. package/src/gep/candidates.js +0 -1
  93. package/src/gep/claimNudge.js +0 -121
  94. package/src/gep/cliContracts.js +0 -1190
  95. package/src/gep/contentHash.js +0 -1
  96. package/src/gep/conversationDistiller.js +0 -1
  97. package/src/gep/conversationSniffer.js +0 -1
  98. package/src/gep/crypto.js +0 -1
  99. package/src/gep/curriculum.js +0 -1
  100. package/src/gep/deviceId.js +0 -1
  101. package/src/gep/directoryClient.js +0 -115
  102. package/src/gep/envFingerprint.js +0 -1
  103. package/src/gep/epigenetics.js +0 -1
  104. package/src/gep/execBridge.js +0 -1
  105. package/src/gep/executionTrace.js +0 -291
  106. package/src/gep/explore.js +0 -1
  107. package/src/gep/featureFlags.js +0 -121
  108. package/src/gep/gitOps.js +0 -265
  109. package/src/gep/hash.js +0 -1
  110. package/src/gep/hostErrorClassifier.js +0 -34
  111. package/src/gep/hubFetch.js +0 -1
  112. package/src/gep/hubReview.js +0 -1
  113. package/src/gep/hubSearch.js +0 -1
  114. package/src/gep/hubVerify.js +0 -1
  115. package/src/gep/idleScheduler.js +0 -400
  116. package/src/gep/issueReporter.js +0 -416
  117. package/src/gep/learningSignals.js +0 -1
  118. package/src/gep/llmReview.js +0 -92
  119. package/src/gep/localStateAwareness.js +0 -243
  120. package/src/gep/mailboxTransport.js +0 -119
  121. package/src/gep/memoryGraph.js +0 -1
  122. package/src/gep/memoryGraphAdapter.js +0 -1
  123. package/src/gep/mutation.js +0 -1
  124. package/src/gep/narrativeMemory.js +0 -1
  125. package/src/gep/oauthLogin.js +0 -181
  126. package/src/gep/openPRRegistry.js +0 -1
  127. package/src/gep/paths.js +0 -522
  128. package/src/gep/personality.js +0 -1
  129. package/src/gep/policyCheck.js +0 -1
  130. package/src/gep/portable.js +0 -103
  131. package/src/gep/privacyClient.js +0 -235
  132. package/src/gep/prompt.js +0 -1
  133. package/src/gep/questionGenerator.js +0 -518
  134. package/src/gep/recallInject.js +0 -1
  135. package/src/gep/recallVerifier.js +0 -1
  136. package/src/gep/reflection.js +0 -1
  137. package/src/gep/sanitize.js +0 -264
  138. package/src/gep/savingsCore.js +0 -1
  139. package/src/gep/schemas/capsule.js +0 -170
  140. package/src/gep/schemas/gene.js +0 -154
  141. package/src/gep/schemas/index.js +0 -8
  142. package/src/gep/schemas/protocol.js +0 -51
  143. package/src/gep/schemas/task.js +0 -74
  144. package/src/gep/selector.js +0 -1
  145. package/src/gep/selfPR.js +0 -469
  146. package/src/gep/signals.js +0 -776
  147. package/src/gep/skill2gep.js +0 -1056
  148. package/src/gep/skill2gepAudit.js +0 -303
  149. package/src/gep/skill2recipes.js +0 -511
  150. package/src/gep/skillDistiller.js +0 -1
  151. package/src/gep/skillPublisher.js +0 -358
  152. package/src/gep/solidify.js +0 -1
  153. package/src/gep/strategy.js +0 -1
  154. package/src/gep/taskReceiver.js +0 -575
  155. package/src/gep/tokenSavings.js +0 -1
  156. package/src/gep/trajectoryExport.js +0 -1
  157. package/src/gep/validationReport.js +0 -55
  158. package/src/gep/validator/index.js +0 -411
  159. package/src/gep/validator/reporter.js +0 -210
  160. package/src/gep/validator/sandboxExecutor.js +0 -480
  161. package/src/gep/validator/stakeBootstrap.js +0 -357
  162. package/src/gep/workspaceKeychain.js +0 -1
  163. package/src/ops/cleanup.js +0 -80
  164. package/src/ops/commentary.js +0 -60
  165. package/src/ops/health_check.js +0 -104
  166. package/src/ops/index.js +0 -11
  167. package/src/ops/innovation.js +0 -67
  168. package/src/ops/lifecycle.js +0 -798
  169. package/src/ops/self_repair.js +0 -76
  170. package/src/ops/skills_monitor.js +0 -147
  171. package/src/ops/trigger.js +0 -33
  172. package/src/proxy/clientSettings.js +0 -405
  173. package/src/proxy/envelope.js +0 -59
  174. package/src/proxy/extensions/dmHandler.js +0 -45
  175. package/src/proxy/extensions/sessionHandler.js +0 -141
  176. package/src/proxy/extensions/skillUpdater.js +0 -64
  177. package/src/proxy/extensions/traceControl.js +0 -1
  178. package/src/proxy/index.js +0 -1395
  179. package/src/proxy/inject.js +0 -1
  180. package/src/proxy/lifecycle/manager.js +0 -1568
  181. package/src/proxy/mailbox/state.js +0 -207
  182. package/src/proxy/mailbox/store.js +0 -602
  183. package/src/proxy/router/cache_passthrough.js +0 -26
  184. package/src/proxy/router/features.js +0 -84
  185. package/src/proxy/router/gemini_route.js +0 -154
  186. package/src/proxy/router/messages_route.js +0 -535
  187. package/src/proxy/router/model_router.js +0 -113
  188. package/src/proxy/router/models_route.js +0 -52
  189. package/src/proxy/router/ollama_route.js +0 -103
  190. package/src/proxy/router/responses_route.js +0 -170
  191. package/src/proxy/router/vertex_route.js +0 -110
  192. package/src/proxy/server/http.js +0 -363
  193. package/src/proxy/server/routes.js +0 -558
  194. package/src/proxy/server/settings.js +0 -115
  195. package/src/proxy/sync/engine.js +0 -179
  196. package/src/proxy/sync/inbound.js +0 -211
  197. package/src/proxy/sync/outbound.js +0 -361
  198. package/src/proxy/task/monitor.js +0 -131
  199. package/src/proxy/trace/extractor.js +0 -1
  200. package/src/proxy/trace/usage.js +0 -1
  201. package/src/solo/breaker.js +0 -25
  202. package/src/solo/gitGuard.js +0 -65
  203. package/src/webui/client/clientJs/assets.js +0 -111
  204. package/src/webui/client/clientJs/bootstrap.js +0 -92
  205. package/src/webui/client/clientJs/common.js +0 -77
  206. package/src/webui/client/clientJs/i18n.js +0 -366
  207. package/src/webui/client/clientJs/index.js +0 -35
  208. package/src/webui/client/clientJs/interactions.js +0 -351
  209. package/src/webui/client/clientJs/overview.js +0 -152
  210. package/src/webui/client/clientJs/personality.js +0 -285
  211. package/src/webui/client/clientJs/pipelines.js +0 -330
  212. package/src/webui/client/indexHtml.js +0 -221
  213. package/src/webui/client/static.js +0 -23
  214. package/src/webui/client/stylesCss.js +0 -639
  215. package/src/webui/client/vendor/README.md +0 -15
  216. package/src/webui/client/vendor/echarts.min.js +0 -45
  217. package/src/webui/index.js +0 -14
  218. package/src/webui/observer/assets.js +0 -146
  219. package/src/webui/observer/index.js +0 -37
  220. package/src/webui/observer/interactions.js +0 -127
  221. package/src/webui/observer/jsonl.js +0 -75
  222. package/src/webui/observer/paths.js +0 -46
  223. package/src/webui/observer/personality.js +0 -43
  224. package/src/webui/observer/pipelineEvents.js +0 -58
  225. package/src/webui/observer/redact.js +0 -63
  226. package/src/webui/observer/runs.js +0 -356
  227. package/src/webui/observer/safety.js +0 -57
  228. package/src/webui/observer/skills.js +0 -70
  229. package/src/webui/observer/status.js +0 -71
  230. package/src/webui/server/http.js +0 -138
  231. package/src/webui/server/routes.js +0 -41
@@ -1,382 +0,0 @@
1
- // ATP Auto-Buyer (opt-in: requires explicit consent before auto-spending)
2
- // Converts capability gaps into ATP orders with strict budget caps and
3
- // 24h question-level deduplication. Budget caps:
4
- // ATP_AUTOBUY_DAILY_CAP_CREDITS (default 50)
5
- // ATP_AUTOBUY_PER_ORDER_CAP_CREDITS (default 10)
6
- // Cold-start safety: the first 5 minutes after process start use a half-cap
7
- // to protect against misconfiguration loops on restart storms.
8
- //
9
- // Consent resolution (in order):
10
- // 1. EVOLVER_ATP_AUTOBUY=on|off env — explicit operator override wins.
11
- // 2. ack file at <memory>/atp-autobuy-ack.json with `{enabled: bool}` —
12
- // written by first-run prompt (cliAutobuyPrompt) or `evolver atp
13
- // enable|disable`.
14
- // 3. No signal → OFF. New installs never auto-spend before the user has
15
- // explicitly opted in (consumer protection: ATP spends real credits).
16
- //
17
- // Integration contract:
18
- // 1) Call start({ dailyCap, perOrderCap }) once at Evolver boot. The
19
- // evolve loop does this at the top of every cycle; start() is
20
- // idempotent so the repeated call is a no-op.
21
- // 2) Call considerOrder({ signals, question, capabilities, budget, ... })
22
- // from the evolve loop whenever a capability gap is detected.
23
- // 3) Result shape: { ok, skipped?, reason?, data?, error? }.
24
- //
25
- // Failure modes are non-fatal; every external call is wrapped with a 3s
26
- // timeout race so loop cadence is never blocked by network issues.
27
-
28
- const crypto = require('crypto');
29
- const fs = require('fs');
30
- const path = require('path');
31
-
32
- const { getMemoryDir } = require('../gep/paths');
33
- const hubClient = require('./hubClient');
34
-
35
- const DEFAULT_DAILY_CAP = 50;
36
- const DEFAULT_PER_ORDER_CAP = 10;
37
- const DEFAULT_ORDER_TIMEOUT_MS = 3000;
38
- const COLD_START_WINDOW_MS = 5 * 60 * 1000;
39
- // Successful orders dedup for 24h so the same capability gap is only paid for
40
- // once per day. Failed orders dedup for 5 minutes only — long enough to
41
- // absorb tight retry loops (the original goal of "don't hammer the hub")
42
- // without making the user wait 24h to retry a question after a transient
43
- // 503/network blip.
44
- const DEDUP_TTL_MS = 24 * 60 * 60 * 1000;
45
- const DEDUP_FAILURE_TTL_MS = 5 * 60 * 1000;
46
- const LEDGER_FILENAME = 'atp-autobuyer-ledger.json';
47
- const ACK_FILENAME = 'atp-autobuy-ack.json';
48
-
49
- let _started = false;
50
- let _startedAt = 0;
51
- let _config = {
52
- dailyCap: DEFAULT_DAILY_CAP,
53
- perOrderCap: DEFAULT_PER_ORDER_CAP,
54
- timeoutMs: DEFAULT_ORDER_TIMEOUT_MS,
55
- };
56
-
57
- function _ledgerPath() {
58
- return path.join(getMemoryDir(), LEDGER_FILENAME);
59
- }
60
-
61
- function _ackPath() {
62
- return path.join(getMemoryDir(), ACK_FILENAME);
63
- }
64
-
65
- function _todayKey(now) {
66
- const d = new Date(typeof now === 'number' ? now : Date.now());
67
- return d.toISOString().slice(0, 10); // UTC YYYY-MM-DD
68
- }
69
-
70
- function _readAck() {
71
- try {
72
- const p = _ackPath();
73
- if (!fs.existsSync(p)) return null;
74
- const parsed = JSON.parse(fs.readFileSync(p, 'utf8'));
75
- if (!parsed || typeof parsed !== 'object') return null;
76
- if (typeof parsed.enabled !== 'boolean') return null;
77
- return parsed;
78
- } catch (_) {
79
- return null;
80
- }
81
- }
82
-
83
- // Resolve consent. Returns:
84
- // { enabled: true, source: 'env'|'ack'|'default' }
85
- // { enabled: false, source: 'env'|'ack' }
86
- // The `default` case is the new-install path (no env override, no ack file):
87
- // auto-spend defaults ON, gated by the daily/per-order caps and the cold-start
88
- // half-cap window. The first-run prompt and `evolver atp disable` remain the
89
- // opt-out paths for users who do not want auto-spend; once an explicit ack is
90
- // recorded the source flips to 'ack' and the user's choice (either way) wins
91
- // over this default.
92
- function getConsent() {
93
- const envRaw = process.env.EVOLVER_ATP_AUTOBUY;
94
- if (typeof envRaw === 'string') {
95
- // Trim BEFORE the length check so whitespace-only values
96
- // (e.g. EVOLVER_ATP_AUTOBUY=" ") count as unset, matching the
97
- // classify() helper in cliAutobuyPrompt.js. Without this alignment a
98
- // whitespace value would skip the prompt in classify (treats as unset
99
- // → 'eligible') but still enter this env branch, trim to "", fail to
100
- // match 'off'/'0'/'false', and silently return enabled=true.
101
- const s = envRaw.trim().toLowerCase();
102
- if (s.length > 0) {
103
- const enabled = s !== 'off' && s !== '0' && s !== 'false';
104
- return { enabled, source: 'env' };
105
- }
106
- }
107
- const ack = _readAck();
108
- if (ack) {
109
- return { enabled: ack.enabled === true, source: 'ack' };
110
- }
111
- return { enabled: true, source: 'default' };
112
- }
113
-
114
- function _emptyLedger() {
115
- return { version: 1, dayKey: _todayKey(), spent: 0, dedup: {} };
116
- }
117
-
118
- function _readLedger() {
119
- try {
120
- const p = _ledgerPath();
121
- if (!fs.existsSync(p)) return _emptyLedger();
122
- const raw = fs.readFileSync(p, 'utf8');
123
- const parsed = JSON.parse(raw);
124
- if (!parsed || typeof parsed !== 'object') return _emptyLedger();
125
- if (!parsed.dayKey || !parsed.dedup) return _emptyLedger();
126
- return parsed;
127
- } catch (_) {
128
- return _emptyLedger();
129
- }
130
- }
131
-
132
- function _writeLedger(ledger) {
133
- try {
134
- const dir = getMemoryDir();
135
- if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
136
- const tmp = _ledgerPath() + '.tmp';
137
- fs.writeFileSync(tmp, JSON.stringify(ledger, null, 2));
138
- fs.renameSync(tmp, _ledgerPath());
139
- } catch (_) {
140
- // Non-fatal: ledger persistence failure means next process restart
141
- // re-reads previous ledger (so existing caps still apply).
142
- }
143
- }
144
-
145
- function _rotateIfNewDay(ledger, now) {
146
- const today = _todayKey(now);
147
- if (ledger.dayKey !== today) {
148
- return { version: 1, dayKey: today, spent: 0, dedup: ledger.dedup || {} };
149
- }
150
- return ledger;
151
- }
152
-
153
- function _pruneDedup(ledger, now) {
154
- const nowMs = typeof now === 'number' ? now : Date.now();
155
- const out = {};
156
- const src = ledger.dedup || {};
157
- const keys = Object.keys(src);
158
- for (let i = 0; i < keys.length; i++) {
159
- const k = keys[i];
160
- const entry = src[k];
161
- // Legacy ledgers written by older versions stored plain timestamps; treat
162
- // them as successful orders (the original behaviour) so an upgrade does
163
- // not suddenly forget recent dedups.
164
- if (typeof entry === 'number') {
165
- if (entry >= nowMs - DEDUP_TTL_MS) out[k] = entry;
166
- } else if (entry && typeof entry.ts === 'number') {
167
- const ttl = entry.failed ? DEDUP_FAILURE_TTL_MS : DEDUP_TTL_MS;
168
- if (entry.ts >= nowMs - ttl) out[k] = entry;
169
- }
170
- }
171
- ledger.dedup = out;
172
- return ledger;
173
- }
174
-
175
- function _questionHash(opts) {
176
- const caps = Array.isArray(opts.capabilities) ? opts.capabilities.slice().sort().join(',') : '';
177
- const q = (opts.question || '').slice(0, 2000);
178
- return crypto.createHash('sha256').update(caps + '|' + q).digest('hex').slice(0, 24);
179
- }
180
-
181
- function _effectiveCap(value, now) {
182
- const n = Number(value);
183
- if (!Number.isFinite(n) || n < 0) return 0;
184
- const within = _startedAt > 0 && (now - _startedAt) < COLD_START_WINDOW_MS;
185
- return within ? Math.floor(n / 2) : Math.floor(n);
186
- }
187
-
188
- function start(opts) {
189
- if (_started) return;
190
- const consent = getConsent();
191
- if (!consent.enabled) return;
192
- _started = true;
193
- _startedAt = Date.now();
194
- const dailyCap = Math.max(0, Math.floor(Number((opts && opts.dailyCap) || process.env.ATP_AUTOBUY_DAILY_CAP_CREDITS) || DEFAULT_DAILY_CAP));
195
- const perOrderCap = Math.max(0, Math.floor(Number((opts && opts.perOrderCap) || process.env.ATP_AUTOBUY_PER_ORDER_CAP_CREDITS) || DEFAULT_PER_ORDER_CAP));
196
- const timeoutMs = Math.max(500, Math.floor(Number((opts && opts.timeoutMs) || DEFAULT_ORDER_TIMEOUT_MS)));
197
- _config = { dailyCap, perOrderCap, timeoutMs };
198
- let ledger = _readLedger();
199
- ledger = _rotateIfNewDay(ledger, _startedAt);
200
- ledger = _pruneDedup(ledger, _startedAt);
201
- _writeLedger(ledger);
202
- console.log('[ATP-AutoBuyer] Started (dailyCap=' + dailyCap + ', perOrderCap=' + perOrderCap + ', cold-start half-cap for ' + (COLD_START_WINDOW_MS / 1000) + 's)');
203
- }
204
-
205
- function stop() {
206
- _started = false;
207
- _startedAt = 0;
208
- }
209
-
210
- function isStarted() {
211
- return _started;
212
- }
213
-
214
- function _withTimeout(promise, timeoutMs) {
215
- return Promise.race([
216
- promise,
217
- new Promise(function (resolve) {
218
- setTimeout(function () { resolve({ ok: false, error: 'autobuyer_timeout' }); }, timeoutMs);
219
- }),
220
- ]);
221
- }
222
-
223
- // Single-flight queue: serialize the read → cap-check → placeOrder → write
224
- // pipeline so two concurrent considerOrder() calls cannot both pass the cap
225
- // check on the same ledger snapshot and silently double-spend.
226
- //
227
- // Without this, two parallel calls (e.g. user runs Claude Code in two tabs
228
- // through the same proxy, or two capability gaps fire in the same tick) both
229
- // read spent=40, both compute remaining=10, both await placeOrder, both
230
- // increment to spent=50, and write — silently exceeding the daily cap by one
231
- // full order each. autoBuyer is single-process so an in-memory queue is
232
- // sufficient; a file lock would only be needed if multiple OS processes
233
- // shared the same ledger file (not the current deployment model).
234
- let _orderQueue = Promise.resolve();
235
-
236
- function considerOrder(opts) {
237
- if (!_started) return Promise.resolve({ ok: false, skipped: true, reason: 'not_started' });
238
- if (!opts || !Array.isArray(opts.capabilities) || opts.capabilities.length === 0) {
239
- return Promise.resolve({ ok: false, skipped: true, reason: 'no_capabilities' });
240
- }
241
- const next = _orderQueue.then(
242
- () => _considerOrderSerialized(opts),
243
- () => _considerOrderSerialized(opts), // never let a prior rejection break the chain
244
- );
245
- // Swallow rejection on the queue tail so a single thrown error here does
246
- // not poison every subsequent call; the original `next` promise still
247
- // surfaces the error to the caller.
248
- _orderQueue = next.then(() => {}, () => {});
249
- return next;
250
- }
251
-
252
- async function _considerOrderSerialized(opts) {
253
- const now = Date.now();
254
- let ledger = _readLedger();
255
- ledger = _rotateIfNewDay(ledger, now);
256
- ledger = _pruneDedup(ledger, now);
257
-
258
- const hash = _questionHash(opts);
259
- if (ledger.dedup[hash]) {
260
- return { ok: false, skipped: true, reason: 'dedup_hit', hash: hash };
261
- }
262
-
263
- const dailyCap = _effectiveCap(_config.dailyCap, now);
264
- const perOrderCap = _effectiveCap(_config.perOrderCap, now);
265
- const remaining = Math.max(0, dailyCap - (ledger.spent || 0));
266
- if (remaining <= 0) {
267
- console.warn('[ATP-AutoBuyer] Daily cap reached, skipping order (spent=' + ledger.spent + ', cap=' + dailyCap + ')');
268
- return { ok: false, skipped: true, reason: 'daily_cap_reached', spent: ledger.spent, cap: dailyCap };
269
- }
270
-
271
- const requested = Math.max(1, Math.floor(Number(opts.budget) || perOrderCap));
272
- const budget = Math.min(requested, perOrderCap, remaining);
273
- if (budget <= 0) {
274
- return { ok: false, skipped: true, reason: 'budget_clamped_to_zero' };
275
- }
276
-
277
- const orderOpts = {
278
- capabilities: opts.capabilities,
279
- budget: budget,
280
- routingMode: opts.routingMode || 'fastest',
281
- verifyMode: opts.verifyMode || 'auto',
282
- question: opts.question,
283
- signals: opts.signals,
284
- minReputation: opts.minReputation,
285
- };
286
-
287
- const result = await _withTimeout(hubClient.placeOrder(orderOpts), _config.timeoutMs);
288
-
289
- if (result && result.ok) {
290
- ledger.spent = (ledger.spent || 0) + budget;
291
- ledger.dedup[hash] = { ts: now, failed: false };
292
- _writeLedger(ledger);
293
- console.log('[ATP-AutoBuyer] Order placed: ' + (result.data && result.data.order_id) + ' budget=' + budget + ' remaining_today=' + Math.max(0, dailyCap - ledger.spent));
294
- return { ok: true, data: result.data, spent: budget };
295
- }
296
-
297
- // On failure record a SHORT-TTL dedup entry (5 min) so we don't hammer the
298
- // hub for the same capability gap inside a tight retry loop, but the user
299
- // can retry the same question once the transient error clears — far better
300
- // than the previous 24h block for a single 503.
301
- ledger.dedup[hash] = { ts: now, failed: true };
302
- _writeLedger(ledger);
303
- return { ok: false, error: (result && result.error) || 'unknown_error' };
304
- }
305
-
306
- // Write the consent ack file. Used by `evolver atp enable|disable` and the
307
- // first-run prompt. `enabled=true` persists opt-in; `enabled=false` persists
308
- // explicit opt-out so the prompt does not re-ask next session. Atomic write
309
- // via .tmp + rename so a crash mid-write never produces a corrupt ack file.
310
- function setConsent(enabled) {
311
- const dir = getMemoryDir();
312
- const body = {
313
- enabled: !!enabled,
314
- acknowledged_at: new Date().toISOString(),
315
- version: 1,
316
- };
317
- const tmp = _ackPath() + '.tmp';
318
- // Single try/catch over the whole pipeline. Previously the mkdirSync was
319
- // wrapped in its own swallowing try/catch, so an EACCES on the parent dir
320
- // would surface to the caller as a confusing ENOENT from writeFileSync.
321
- // Surface the original error verbatim and best-effort clean up any
322
- // partial .tmp file so a retry from a TTY prompt sees a clean slate.
323
- try {
324
- if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
325
- fs.writeFileSync(tmp, JSON.stringify(body, null, 2) + '\n', 'utf8');
326
- fs.renameSync(tmp, _ackPath());
327
- } catch (err) {
328
- try { fs.unlinkSync(tmp); } catch (_) {}
329
- throw err;
330
- }
331
- return body;
332
- }
333
-
334
- // Test-only reset, not exported by default.
335
- function _resetForTests() {
336
- _started = false;
337
- _startedAt = 0;
338
- _config = {
339
- dailyCap: DEFAULT_DAILY_CAP,
340
- perOrderCap: DEFAULT_PER_ORDER_CAP,
341
- timeoutMs: DEFAULT_ORDER_TIMEOUT_MS,
342
- };
343
- _orderQueue = Promise.resolve();
344
- }
345
-
346
- module.exports = {
347
- // Lifecycle.
348
- start,
349
- stop,
350
- isStarted,
351
- considerOrder,
352
- // Consent surface — public API. Production callers (CLI runAtp,
353
- // cliAutobuyPrompt, the daemon run loop) MUST use these, not the
354
- // __internals duplicates below, so the "test-only" contract on
355
- // __internals stays honest (Bugbot PR #141 R6).
356
- getConsent,
357
- setConsent,
358
- getAckPath: _ackPath,
359
- readAck: _readAck,
360
- ACK_FILENAME,
361
- // Exposed for tests and diagnostics only; callers should not depend on
362
- // these internals in production code paths. Ack-file helpers
363
- // (getAckPath / readAck / ACK_FILENAME) are intentionally NOT mirrored
364
- // here — production and tests both go through the public surface above,
365
- // so a single source of truth survives schema changes (Bugbot PR #141 R6
366
- // follow-up: keep "test-only" honest, no production caller may reach in).
367
- __internals: {
368
- readLedger: _readLedger,
369
- writeLedger: _writeLedger,
370
- questionHash: _questionHash,
371
- effectiveCap: _effectiveCap,
372
- resetForTests: _resetForTests,
373
- constants: {
374
- DEFAULT_DAILY_CAP,
375
- DEFAULT_PER_ORDER_CAP,
376
- COLD_START_WINDOW_MS,
377
- DEDUP_TTL_MS,
378
- DEDUP_FAILURE_TTL_MS,
379
- LEDGER_FILENAME,
380
- },
381
- },
382
- };
@@ -1,215 +0,0 @@
1
- // ATP Auto-Deliver (opt-out, merchant-side)
2
- // Closes the ATP settlement loop for Evolver merchants by auto-calling
3
- // submitDelivery for every claimed task that carries an atp_order_id.
4
- //
5
- // Without this module, an ATP order sits in `pending` until the 7-day escrow
6
- // timeout refunds the buyer: the Hub routes the task to a merchant node and
7
- // marks it claimed, but nothing in the Evolver runtime actually calls
8
- // /a2a/atp/deliver. This was the root cause of the 0-settled-in-13-days
9
- // pipeline stall observed in prod on 2026-04-27.
10
- //
11
- // Integration contract:
12
- // 1) Call start({ pollMs }) once at Evolver boot. Default ON.
13
- // Disable by setting EVOLVER_ATP_AUTODELIVER=off.
14
- // 2) The module polls /a2a/task/my every pollMs milliseconds, finds tasks
15
- // with atp_order_id + a `result_asset_id` (meaning the task already
16
- // completed through solidify), and submits a minimal proofPayload.
17
- // 3) Each submitted order is remembered in a local ledger so we never
18
- // double-submit, even across restarts.
19
- //
20
- // Dedup ledger lives alongside autoBuyer's ledger under memory/.
21
- // Failure modes are non-fatal: network errors are logged, not thrown.
22
-
23
- const fs = require('fs');
24
- const path = require('path');
25
-
26
- const { getMemoryDir } = require('../gep/paths');
27
- const hubClient = require('./hubClient');
28
-
29
- const DEFAULT_POLL_MS = 60 * 1000; // 1 min
30
- const MIN_POLL_MS = 15 * 1000;
31
- const LEDGER_FILENAME = 'atp-autodeliver-ledger.json';
32
- const LEDGER_MAX_ENTRIES = 500;
33
-
34
- let _started = false;
35
- let _pollInterval = null;
36
- let _pollMs = DEFAULT_POLL_MS;
37
- let _inflight = false;
38
-
39
- function _ledgerPath() {
40
- return path.join(getMemoryDir(), LEDGER_FILENAME);
41
- }
42
-
43
- function _isEnabled() {
44
- const raw = (process.env.EVOLVER_ATP_AUTODELIVER || 'on').toLowerCase().trim();
45
- return raw !== 'off' && raw !== '0' && raw !== 'false';
46
- }
47
-
48
- function _emptyLedger() {
49
- return { version: 1, submitted: {} };
50
- }
51
-
52
- function _readLedger() {
53
- try {
54
- const p = _ledgerPath();
55
- if (!fs.existsSync(p)) return _emptyLedger();
56
- const raw = fs.readFileSync(p, 'utf8');
57
- const parsed = JSON.parse(raw);
58
- if (!parsed || typeof parsed !== 'object' || !parsed.submitted) return _emptyLedger();
59
- return parsed;
60
- } catch (_) {
61
- return _emptyLedger();
62
- }
63
- }
64
-
65
- function _writeLedger(ledger) {
66
- try {
67
- const dir = getMemoryDir();
68
- if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
69
- // Bound the ledger size so it cannot grow without limit on long-running
70
- // merchants. Keep the most-recent entries by insertion order.
71
- const entries = Object.entries(ledger.submitted || {});
72
- if (entries.length > LEDGER_MAX_ENTRIES) {
73
- const trimmed = Object.fromEntries(entries.slice(-LEDGER_MAX_ENTRIES));
74
- ledger.submitted = trimmed;
75
- }
76
- const tmp = _ledgerPath() + '.tmp';
77
- fs.writeFileSync(tmp, JSON.stringify(ledger, null, 2));
78
- fs.renameSync(tmp, _ledgerPath());
79
- } catch (_) {
80
- // Non-fatal: next poll will re-attempt from Hub state. Hub-side
81
- // submitDelivery is itself idempotent per order id.
82
- }
83
- }
84
-
85
- function _buildProofPayload(task) {
86
- // Minimal evidence the Hub's auto verifier will accept. Matches the shape
87
- // documented in /a2a/atp/deliver: result/output/pass_rate/signals.
88
- const now = new Date().toISOString();
89
- return {
90
- result: 'completed',
91
- asset_id: task.result_asset_id || null,
92
- completed_at: task.claimed_at || now,
93
- pass_rate: 1.0,
94
- signals: Array.isArray(task.signals) ? task.signals.slice(0, 10) : [],
95
- submitter: 'evolver_auto_deliver',
96
- };
97
- }
98
-
99
- async function _tick() {
100
- if (_inflight) return;
101
- _inflight = true;
102
- try {
103
- const result = await hubClient.listMyTasks(20);
104
- if (!result || !result.ok || !result.data) return;
105
- const tasks = Array.isArray(result.data.tasks) ? result.data.tasks : [];
106
- if (tasks.length === 0) return;
107
-
108
- const ledger = _readLedger();
109
- let wroteLedger = false;
110
-
111
- for (const task of tasks) {
112
- const orderId = task && task.atp_order_id;
113
- if (!orderId) continue;
114
- if (ledger.submitted[orderId]) continue;
115
- // Only deliver once the task has a result asset (i.e. solidify finished).
116
- if (!task.result_asset_id) continue;
117
- // Don't try to deliver on already-terminal statuses.
118
- if (task.status && task.status !== 'claimed' && task.status !== 'completed') continue;
119
-
120
- const proofPayload = _buildProofPayload(task);
121
- const resp = await hubClient.submitDelivery(orderId, proofPayload);
122
- if (resp && resp.ok) {
123
- ledger.submitted[orderId] = Date.now();
124
- wroteLedger = true;
125
- console.log('[ATP-AutoDeliver] Delivered order=' + orderId + ' asset=' + (task.result_asset_id || 'none'));
126
- } else {
127
- // Record terminal-ish errors in the ledger so we do not hammer the
128
- // same order every minute. Everything else (transient network) is
129
- // retried on the next tick.
130
- const err = (resp && resp.error) || 'unknown_error';
131
- const status = resp && resp.status;
132
- const terminal = status === 400 || status === 404 || status === 409;
133
- if (terminal) {
134
- ledger.submitted[orderId] = -Date.now();
135
- wroteLedger = true;
136
- }
137
- console.log('[ATP-AutoDeliver] Delivery failed order=' + orderId + ' status=' + (status || 'n/a') + ' err=' + String(err).slice(0, 120));
138
- }
139
- }
140
-
141
- if (wroteLedger) _writeLedger(ledger);
142
- } catch (err) {
143
- console.log('[ATP-AutoDeliver] Tick threw (non-fatal): ' + (err && err.message || err));
144
- } finally {
145
- _inflight = false;
146
- }
147
- }
148
-
149
- function start(opts) {
150
- if (_started) return;
151
- if (!_isEnabled()) return;
152
- const requested = Number((opts && opts.pollMs) || process.env.ATP_AUTODELIVER_POLL_MS || DEFAULT_POLL_MS);
153
- _pollMs = Math.max(MIN_POLL_MS, Math.floor(requested) || DEFAULT_POLL_MS);
154
- _started = true;
155
- _pollInterval = setInterval(function () {
156
- _tick().catch(function () { /* swallowed in _tick */ });
157
- }, _pollMs);
158
- // .unref() so this background poller does NOT keep the Node event
159
- // loop alive on its own. `evolver run` (single-shot) writes its
160
- // artifacts and expects to exit — without unref, the setInterval
161
- // handle pins the process and the run sits as a residual `node`
162
- // process until manually killed (public issue #553). `evolver --loop`
163
- // (daemon) keeps the foreground evolve loop alive on its own
164
- // schedule, so an unref'd poller still polls — unref only changes
165
- // whether THIS handle alone keeps the loop alive, not whether the
166
- // handle fires.
167
- if (_pollInterval && typeof _pollInterval.unref === 'function') {
168
- _pollInterval.unref();
169
- }
170
- // Do not await -- fire the first tick asynchronously so start() returns
171
- // immediately. This matches the autoBuyer start() semantics.
172
- _tick().catch(function () { /* swallowed in _tick */ });
173
- console.log('[ATP-AutoDeliver] Started (pollMs=' + _pollMs + ')');
174
- }
175
-
176
- function stop() {
177
- if (_pollInterval) {
178
- clearInterval(_pollInterval);
179
- _pollInterval = null;
180
- }
181
- _started = false;
182
- }
183
-
184
- function isStarted() {
185
- return _started;
186
- }
187
-
188
- function _resetForTests() {
189
- stop();
190
- _inflight = false;
191
- _pollMs = DEFAULT_POLL_MS;
192
- }
193
-
194
- module.exports = {
195
- start,
196
- stop,
197
- isStarted,
198
- __internals: {
199
- tick: _tick,
200
- readLedger: _readLedger,
201
- writeLedger: _writeLedger,
202
- buildProofPayload: _buildProofPayload,
203
- resetForTests: _resetForTests,
204
- // Test-only accessor for the active poll Timeout. Used to assert
205
- // the timer was `.unref()`ed so it does not pin the Node event
206
- // loop (regression guard for public issue #553).
207
- getPollIntervalForTest: () => _pollInterval,
208
- constants: {
209
- DEFAULT_POLL_MS,
210
- MIN_POLL_MS,
211
- LEDGER_FILENAME,
212
- LEDGER_MAX_ENTRIES,
213
- },
214
- },
215
- };