@evomap/evolver 1.87.1 → 1.87.2

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 (54) hide show
  1. package/assets/gep/candidates.jsonl +1 -0
  2. package/assets/gep/capsules.json +4 -0
  3. package/assets/gep/events.jsonl +0 -0
  4. package/assets/gep/failed_capsules.json +4 -0
  5. package/assets/gep/genes.json +245 -0
  6. package/assets/gep/genes.jsonl +0 -0
  7. package/index.js +30 -11
  8. package/package.json +1 -1
  9. package/src/atp/autoBuyer.js +90 -11
  10. package/src/atp/cli.js +98 -0
  11. package/src/atp/cliAutobuyPrompt.js +59 -52
  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/forceUpdate.js +2 -1
  22. package/src/gep/a2aProtocol.js +1 -1
  23. package/src/gep/candidateEval.js +1 -1
  24. package/src/gep/candidates.js +1 -1
  25. package/src/gep/contentHash.js +1 -1
  26. package/src/gep/crypto.js +1 -1
  27. package/src/gep/curriculum.js +1 -1
  28. package/src/gep/deviceId.js +1 -1
  29. package/src/gep/envFingerprint.js +1 -1
  30. package/src/gep/epigenetics.js +1 -1
  31. package/src/gep/explore.js +1 -1
  32. package/src/gep/hash.js +1 -1
  33. package/src/gep/hubFetch.js +1 -1
  34. package/src/gep/hubReview.js +1 -1
  35. package/src/gep/hubSearch.js +1 -1
  36. package/src/gep/hubVerify.js +1 -1
  37. package/src/gep/learningSignals.js +1 -1
  38. package/src/gep/memoryGraph.js +1 -1
  39. package/src/gep/memoryGraphAdapter.js +1 -1
  40. package/src/gep/mutation.js +1 -1
  41. package/src/gep/narrativeMemory.js +1 -1
  42. package/src/gep/openPRRegistry.js +1 -1
  43. package/src/gep/personality.js +1 -1
  44. package/src/gep/policyCheck.js +1 -1
  45. package/src/gep/prompt.js +1 -1
  46. package/src/gep/recallVerifier.js +1 -1
  47. package/src/gep/reflection.js +1 -1
  48. package/src/gep/selector.js +1 -1
  49. package/src/gep/skillDistiller.js +1 -1
  50. package/src/gep/solidify.js +1 -1
  51. package/src/gep/strategy.js +1 -1
  52. package/src/gep/workspaceKeychain.js +1 -1
  53. package/src/proxy/index.js +29 -9
  54. package/src/proxy/router/messages_route.js +80 -5
@@ -0,0 +1 @@
1
+ {"type":"CapabilityCandidate","id":"cand_b9a66a5c","title":"Harden session log detection and fallback behavior","source":"signals","created_at":"2026-05-27T12:51:59.052Z","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"}}
@@ -0,0 +1,4 @@
1
+ {
2
+ "version": 1,
3
+ "capsules": []
4
+ }
File without changes
@@ -0,0 +1,4 @@
1
+ {
2
+ "version": 1,
3
+ "failed_capsules": []
4
+ }
@@ -0,0 +1,245 @@
1
+ {
2
+ "version": 2,
3
+ "genes": [
4
+ {
5
+ "type": "Gene",
6
+ "id": "gene_gep_repair_from_errors",
7
+ "category": "repair",
8
+ "signals_match": [
9
+ "error|错误|异常|エラー|오류",
10
+ "exception|异常|例外|예외",
11
+ "failed|失败|失敗|실패|fail",
12
+ "unstable|不稳定|不安定|불안정",
13
+ "log_error",
14
+ "test_failure"
15
+ ],
16
+ "preconditions": [
17
+ "signals contains error-related indicators"
18
+ ],
19
+ "strategy": [
20
+ "Extract structured signals from logs and user instructions",
21
+ "Select an existing Gene by signals match (no improvisation)",
22
+ "Estimate blast radius (files, lines) before editing",
23
+ "Apply smallest reversible patch",
24
+ "Validate using declared validation steps; rollback on failure",
25
+ "Solidify knowledge: append EvolutionEvent, update Gene/Capsule store"
26
+ ],
27
+ "constraints": {
28
+ "max_files": 20,
29
+ "forbidden_paths": [
30
+ ".git",
31
+ "node_modules"
32
+ ]
33
+ },
34
+ "validation": [
35
+ "node scripts/validate-modules.js ./src/evolve ./src/gep/solidify ./src/gep/policyCheck ./src/gep/selector ./src/gep/memoryGraph ./src/gep/assetStore",
36
+ "node scripts/validate-suite.js"
37
+ ]
38
+ },
39
+ {
40
+ "type": "Gene",
41
+ "id": "gene_gep_optimize_prompt_and_assets",
42
+ "category": "optimize",
43
+ "signals_match": [
44
+ "protocol|协议|プロトコル|프로토콜",
45
+ "gep",
46
+ "prompt|提示词|提示|プロンプト|프롬프트",
47
+ "audit|审计|監査|감사",
48
+ "reusable|可复用|再利用|재사용"
49
+ ],
50
+ "preconditions": [
51
+ "need stricter, auditable evolution protocol outputs"
52
+ ],
53
+ "strategy": [
54
+ "Extract signals and determine selection rationale via Selector JSON",
55
+ "Prefer reusing existing Gene/Capsule; only create if no match exists",
56
+ "Refactor prompt assembly to embed assets (genes, capsules, parent event)",
57
+ "Reduce noise and ambiguity; enforce strict output schema",
58
+ "Validate by running node index.js run and ensuring no runtime errors",
59
+ "Solidify: record EvolutionEvent, update Gene definitions, create Capsule on success"
60
+ ],
61
+ "constraints": {
62
+ "max_files": 20,
63
+ "forbidden_paths": [
64
+ ".git",
65
+ "node_modules"
66
+ ]
67
+ },
68
+ "validation": [
69
+ "node scripts/validate-modules.js ./src/evolve ./src/gep/prompt ./src/gep/contentHash ./src/gep/skillDistiller",
70
+ "node scripts/validate-suite.js"
71
+ ]
72
+ },
73
+ {
74
+ "type": "Gene",
75
+ "id": "gene_gep_innovate_from_opportunity",
76
+ "category": "innovate",
77
+ "signals_match": [
78
+ "user_feature_request|功能请求|機能リクエスト|기능요청",
79
+ "user_improvement_suggestion|改进建议|改善提案|개선제안",
80
+ "perf_bottleneck|性能瓶颈|パフォーマンス|성능병목",
81
+ "capability_gap|能力缺口|機能ギャップ|역량공백",
82
+ "stable_success_plateau",
83
+ "external_opportunity|外部机会|外部機会|외부기회",
84
+ "bounty_task"
85
+ ],
86
+ "preconditions": [
87
+ "at least one opportunity signal is present",
88
+ "no active log_error signals (stability first)"
89
+ ],
90
+ "strategy": [
91
+ "Extract opportunity signals and identify the specific user need or system gap",
92
+ "Search existing Genes and Capsules for partial matches (avoid reinventing)",
93
+ "Design a minimal, testable implementation plan (prefer small increments)",
94
+ "Estimate blast radius; innovate changes may touch more files but must stay within constraints",
95
+ "Implement the change with clear validation criteria",
96
+ "Validate using declared validation steps; rollback on failure",
97
+ "Solidify: record EvolutionEvent with intent=innovate, create new Gene if pattern is novel, create Capsule on success"
98
+ ],
99
+ "constraints": {
100
+ "max_files": 25,
101
+ "forbidden_paths": [
102
+ ".git",
103
+ "node_modules"
104
+ ]
105
+ },
106
+ "validation": [
107
+ "node scripts/validate-modules.js ./src/evolve ./src/gep/solidify ./src/gep/policyCheck ./src/gep/mutation ./src/gep/personality",
108
+ "node scripts/validate-suite.js"
109
+ ]
110
+ },
111
+ {
112
+ "type": "Gene",
113
+ "id": "gene_gep_optimize_tool_usage",
114
+ "summary": "Optimize tool execution patterns by reducing redundant exec calls, improving tool selection strategy, and enforcing tool-use constraints to prevent bypass.",
115
+ "category": "optimize",
116
+ "signals_match": [
117
+ "high_tool_usage:exec",
118
+ "repeated_tool_usage:exec",
119
+ "tool_bypass|工具绕过|ツール迂回|도구우회",
120
+ "tool_loop|工具循环|ツールループ|도구반복",
121
+ "high_tool_usage"
122
+ ],
123
+ "preconditions": [
124
+ "agent repeatedly invokes the same tool (especially exec) without progress",
125
+ "tool execution bypass patterns detected",
126
+ "no active error signals (errors would take repair priority)"
127
+ ],
128
+ "strategy": [
129
+ "Analyze tool usage patterns to identify the root cause of repetition (wrong tool, missing context, or lack of guardrails)",
130
+ "Introduce strategy-level guardrails: prefer single-shot commands, batch related operations, add explicit retry limits",
131
+ "If tool_bypass detected, strengthen constraint enforcement in prompt assembly or tool routing",
132
+ "Estimate blast radius; changes should target tool routing, prompt constraints, or signal deduplication logic",
133
+ "Validate by confirming no regressions in existing tool tests and signal extraction accuracy",
134
+ "Solidify: record EvolutionEvent with intent=optimize, update Capsule on success"
135
+ ],
136
+ "constraints": {
137
+ "max_files": 15,
138
+ "forbidden_paths": [
139
+ ".git",
140
+ "node_modules"
141
+ ]
142
+ },
143
+ "validation": [
144
+ "node scripts/validate-modules.js ./src/gep/signals ./src/evolve",
145
+ "node scripts/validate-suite.js"
146
+ ],
147
+ "routing_hint": {
148
+ "tier": "mid",
149
+ "reasoning_level": "medium"
150
+ }
151
+ },
152
+ {
153
+ "type": "Gene",
154
+ "id": "gene_distilled_s2g-env-vars",
155
+ "summary": "Vercel environment variable expert guidance. Use when working with .env files, vercel env commands, OIDC tokens, or managing environment-specific configuration.",
156
+ "category": "optimize",
157
+ "signals_match": [
158
+ "use_when_working_with",
159
+ "env_files",
160
+ "vercel_env_commands",
161
+ "oidc_tokens",
162
+ "vercel_env_pull",
163
+ "env_local_overwrite",
164
+ "oidc_token_expiry",
165
+ "dotenv_cli"
166
+ ],
167
+ "preconditions": [
168
+ "Skill env-vars has just been executed locally"
169
+ ],
170
+ "strategy": [
171
+ "Identify the dominant trigger signals from the Skill description.",
172
+ "Apply the smallest targeted change that satisfies the Skill workflow.",
173
+ "Run the Skill validation commands and abort if any fails."
174
+ ],
175
+ "constraints": {
176
+ "max_files": 12,
177
+ "forbidden_paths": [
178
+ ".git",
179
+ "node_modules"
180
+ ]
181
+ },
182
+ "validation": [
183
+ "node --version"
184
+ ],
185
+ "routing_hint": {
186
+ "tier": "cheap",
187
+ "reasoning_level": "low"
188
+ },
189
+ "schema_version": "1.6.0",
190
+ "_source": {
191
+ "kind": "skill2gep",
192
+ "skill_name": "env-vars",
193
+ "skill_platform": "vercel",
194
+ "skill_hash": "ba0bdb4db2",
195
+ "rationale_paper": "Wang, Ren, Zhang. From Procedural Skills to Strategy Genes. arXiv:2604.15097",
196
+ "paper_scope": "code-science (arXiv:2604.15097, 45 tasks, Gemini 3.1 Pro/Flash Lite)",
197
+ "claims_outside_scope": "assumption",
198
+ "quality_heuristics": {
199
+ "strategy_steps": 0,
200
+ "avoid_count": 0,
201
+ "validation_declared_count": 0,
202
+ "validation_runnable_count": 0,
203
+ "validation_fallback_used": true,
204
+ "signals_extracted": 4,
205
+ "preconditions_extracted": 0
206
+ }
207
+ },
208
+ "asset_id": "sha256:1501bc37fbefb18630c4dc8a95d8cdc1ed32bec4a465dc3223280ae907e07297"
209
+ },
210
+ {
211
+ "type": "Gene",
212
+ "id": "gene_tool_integrity",
213
+ "category": "repair",
214
+ "signals_match": [
215
+ "tool_bypass|工具绕过|ツール迂回|도구우회"
216
+ ],
217
+ "preconditions": [
218
+ "agent used shell/exec to perform an action that a registered tool can handle"
219
+ ],
220
+ "strategy": [
221
+ "Always prefer registered tools over ad-hoc scripts or shell workarounds",
222
+ "If a registered tool fails, report the actual error honestly and attempt to fix the root cause",
223
+ "Never fabricate explanations -- describe actual actions transparently",
224
+ "Do not create temporary scripts in extension or project directories"
225
+ ],
226
+ "constraints": {
227
+ "max_files": 4,
228
+ "forbidden_paths": [
229
+ ".git",
230
+ "node_modules"
231
+ ]
232
+ },
233
+ "validation": [
234
+ "node scripts/validate-suite.js"
235
+ ],
236
+ "anti_patterns": [
237
+ "tool_bypass"
238
+ ],
239
+ "routing_hint": {
240
+ "tier": "cheap",
241
+ "reasoning_level": "low"
242
+ }
243
+ }
244
+ ]
245
+ }
File without changes
package/index.js CHANGED
@@ -491,10 +491,11 @@ async function main() {
491
491
  console.warn('[ATP] Auto-init failed: ' + (atpInitErr && atpInitErr.message || atpInitErr));
492
492
  }
493
493
 
494
- // ATP: capability-gap auto-buyer. Default ON as of ATP liquidity
495
- // unlock; disable with EVOLVER_ATP_AUTOBUY=off. Also starts the
496
- // merchant-side auto-deliver daemon so claimed ATP tasks actually
497
- // call submitDelivery and settle instead of expiring.
494
+ // ATP: capability-gap auto-buyer. OPT-IN as of consent-required
495
+ // change new installs do not auto-spend until the user explicitly
496
+ // runs `evolver atp enable` or answers `y` at the first-run prompt.
497
+ // Also starts the merchant-side auto-deliver daemon so claimed ATP
498
+ // tasks actually call submitDelivery and settle instead of expiring.
498
499
  try {
499
500
  try {
500
501
  const { runPrompt } = require('./src/atp/cliAutobuyPrompt');
@@ -502,16 +503,31 @@ async function main() {
502
503
  } catch (promptErr) {
503
504
  console.warn('[ATP-AutoBuyer] first-run prompt failed: ' + (promptErr && promptErr.message || promptErr));
504
505
  }
505
- const autoBuyRaw = (process.env.EVOLVER_ATP_AUTOBUY || 'on').toLowerCase().trim();
506
- const autoBuyOn = autoBuyRaw !== 'off' && autoBuyRaw !== '0' && autoBuyRaw !== 'false';
507
- if (autoBuyOn) {
506
+ const { autoBuyer } = require('./src/atp');
507
+ const consent = autoBuyer.getConsent();
508
+ if (consent.enabled) {
508
509
  const hubUrl = process.env.A2A_HUB_URL || process.env.EVOMAP_HUB_URL || '';
509
510
  if (hubUrl) {
510
- const { autoBuyer } = require('./src/atp');
511
511
  autoBuyer.start({
512
512
  dailyCap: Number(process.env.ATP_AUTOBUY_DAILY_CAP_CREDITS) || undefined,
513
513
  perOrderCap: Number(process.env.ATP_AUTOBUY_PER_ORDER_CAP_CREDITS) || undefined,
514
514
  });
515
+ if (consent.source === 'default') {
516
+ // First-run on a non-TTY (daemon, hook, CI) where the prompt
517
+ // could not fire AND no env override + no ack file. autoBuyer
518
+ // is starting with the default-on policy — surface a single
519
+ // WARN per process so users see what is happening and how to
520
+ // opt out, instead of discovering it via a credit balance dip.
521
+ let safeHubUrl;
522
+ try { safeHubUrl = new URL(hubUrl).origin; }
523
+ catch { safeHubUrl = '(configured)'; }
524
+ console.warn('[ATP-AutoBuyer] ATP auto-spend is ON (default for new installs).');
525
+ console.warn(' Hub: ' + safeHubUrl + ' Caps: ' +
526
+ (process.env.ATP_AUTOBUY_DAILY_CAP_CREDITS || '50') + ' credits/day, ' +
527
+ (process.env.ATP_AUTOBUY_PER_ORDER_CAP_CREDITS || '10') + '/order' +
528
+ ' (cold-start half-cap for the first 5 min).');
529
+ console.warn(' To opt out: evolver atp disable (or EVOLVER_ATP_AUTOBUY=off)');
530
+ }
515
531
  } else {
516
532
  console.warn('[ATP-AutoBuyer] autobuy enabled but no hub URL configured, skipping.');
517
533
  }
@@ -1815,7 +1831,7 @@ async function main() {
1815
1831
  process.exit(1);
1816
1832
  }
1817
1833
 
1818
- } else if (command === 'buy' || command === 'orders' || command === 'verify') {
1834
+ } else if (command === 'buy' || command === 'orders' || command === 'verify' || command === 'atp') {
1819
1835
  try {
1820
1836
  const atpCli = require('./src/atp/cli');
1821
1837
  const subArgs = args.slice(1); // drop the command token (e.g. "buy") itself
@@ -1827,9 +1843,12 @@ async function main() {
1827
1843
  } else if (command === 'orders') {
1828
1844
  parsed = atpCli.parseOrdersArgs(subArgs);
1829
1845
  runner = atpCli.runOrders;
1830
- } else {
1846
+ } else if (command === 'verify') {
1831
1847
  parsed = atpCli.parseVerifyArgs(subArgs);
1832
1848
  runner = atpCli.runVerify;
1849
+ } else {
1850
+ parsed = atpCli.parseAtpArgs(subArgs);
1851
+ runner = atpCli.runAtp;
1833
1852
  }
1834
1853
  if (!parsed.ok) {
1835
1854
  console.error('[ATP] ' + parsed.error);
@@ -1844,7 +1863,7 @@ async function main() {
1844
1863
  }
1845
1864
 
1846
1865
  } else {
1847
- console.log(`Usage: node index.js [run|/evolve|solidify|review|distill|fetch|sync|asset-log|webui|setup-hooks|buy|orders|verify|atp-complete] [--loop]
1866
+ console.log(`Usage: node index.js [run|/evolve|solidify|review|distill|fetch|sync|asset-log|webui|setup-hooks|buy|orders|verify|atp|atp-complete] [--loop]
1848
1867
  - fetch flags:
1849
1868
  - --skill=<id> | -s <id> (skill ID to download)
1850
1869
  - --out=<dir> (output directory, default: ./skills/<skill_id>)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@evomap/evolver",
3
- "version": "1.87.1",
3
+ "version": "1.87.2",
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": {
@@ -1,12 +1,19 @@
1
- // ATP Auto-Buyer (opt-out, default ON as of ATP liquidity unlock)
1
+ // ATP Auto-Buyer (opt-in: requires explicit consent before auto-spending)
2
2
  // Converts capability gaps into ATP orders with strict budget caps and
3
- // 24h question-level deduplication. Disable by setting
4
- // EVOLVER_ATP_AUTOBUY=off. Budget caps:
3
+ // 24h question-level deduplication. Budget caps:
5
4
  // ATP_AUTOBUY_DAILY_CAP_CREDITS (default 50)
6
5
  // ATP_AUTOBUY_PER_ORDER_CAP_CREDITS (default 10)
7
6
  // Cold-start safety: the first 5 minutes after process start use a half-cap
8
7
  // to protect against misconfiguration loops on restart storms.
9
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
+ //
10
17
  // Integration contract:
11
18
  // 1) Call start({ dailyCap, perOrderCap }) once at Evolver boot. The
12
19
  // evolve loop does this at the top of every cycle; start() is
@@ -31,6 +38,7 @@ const DEFAULT_ORDER_TIMEOUT_MS = 3000;
31
38
  const COLD_START_WINDOW_MS = 5 * 60 * 1000;
32
39
  const DEDUP_TTL_MS = 24 * 60 * 60 * 1000;
33
40
  const LEDGER_FILENAME = 'atp-autobuyer-ledger.json';
41
+ const ACK_FILENAME = 'atp-autobuy-ack.json';
34
42
 
35
43
  let _started = false;
36
44
  let _startedAt = 0;
@@ -44,18 +52,57 @@ function _ledgerPath() {
44
52
  return path.join(getMemoryDir(), LEDGER_FILENAME);
45
53
  }
46
54
 
55
+ function _ackPath() {
56
+ return path.join(getMemoryDir(), ACK_FILENAME);
57
+ }
58
+
47
59
  function _todayKey(now) {
48
60
  const d = new Date(typeof now === 'number' ? now : Date.now());
49
61
  return d.toISOString().slice(0, 10); // UTC YYYY-MM-DD
50
62
  }
51
63
 
52
- function _isEnabled() {
53
- // Default ON: the evolve loop starts autoBuyer at the top of every cycle
54
- // so new users get ATP buyer routing out of the box. Disable by setting
55
- // EVOLVER_ATP_AUTOBUY=off. Budget caps (DAILY_CAP + PER_ORDER_CAP) keep
56
- // the downside bounded even when this is on.
57
- const raw = (process.env.EVOLVER_ATP_AUTOBUY || 'on').toLowerCase().trim();
58
- return raw !== 'off' && raw !== '0' && raw !== 'false';
64
+ function _readAck() {
65
+ try {
66
+ const p = _ackPath();
67
+ if (!fs.existsSync(p)) return null;
68
+ const parsed = JSON.parse(fs.readFileSync(p, 'utf8'));
69
+ if (!parsed || typeof parsed !== 'object') return null;
70
+ if (typeof parsed.enabled !== 'boolean') return null;
71
+ return parsed;
72
+ } catch (_) {
73
+ return null;
74
+ }
75
+ }
76
+
77
+ // Resolve consent. Returns:
78
+ // { enabled: true, source: 'env'|'ack'|'default' }
79
+ // { enabled: false, source: 'env'|'ack' }
80
+ // The `default` case is the new-install path (no env override, no ack file):
81
+ // auto-spend defaults ON, gated by the daily/per-order caps and the cold-start
82
+ // half-cap window. The first-run prompt and `evolver atp disable` remain the
83
+ // opt-out paths for users who do not want auto-spend; once an explicit ack is
84
+ // recorded the source flips to 'ack' and the user's choice (either way) wins
85
+ // over this default.
86
+ function getConsent() {
87
+ const envRaw = process.env.EVOLVER_ATP_AUTOBUY;
88
+ if (typeof envRaw === 'string') {
89
+ // Trim BEFORE the length check so whitespace-only values
90
+ // (e.g. EVOLVER_ATP_AUTOBUY=" ") count as unset, matching the
91
+ // classify() helper in cliAutobuyPrompt.js. Without this alignment a
92
+ // whitespace value would skip the prompt in classify (treats as unset
93
+ // → 'eligible') but still enter this env branch, trim to "", fail to
94
+ // match 'off'/'0'/'false', and silently return enabled=true.
95
+ const s = envRaw.trim().toLowerCase();
96
+ if (s.length > 0) {
97
+ const enabled = s !== 'off' && s !== '0' && s !== 'false';
98
+ return { enabled, source: 'env' };
99
+ }
100
+ }
101
+ const ack = _readAck();
102
+ if (ack) {
103
+ return { enabled: ack.enabled === true, source: 'ack' };
104
+ }
105
+ return { enabled: true, source: 'default' };
59
106
  }
60
107
 
61
108
  function _emptyLedger() {
@@ -125,7 +172,8 @@ function _effectiveCap(value, now) {
125
172
 
126
173
  function start(opts) {
127
174
  if (_started) return;
128
- if (!_isEnabled()) return;
175
+ const consent = getConsent();
176
+ if (!consent.enabled) return;
129
177
  _started = true;
130
178
  _startedAt = Date.now();
131
179
  const dailyCap = Math.max(0, Math.floor(Number((opts && opts.dailyCap) || process.env.ATP_AUTOBUY_DAILY_CAP_CREDITS) || DEFAULT_DAILY_CAP));
@@ -213,6 +261,24 @@ async function considerOrder(opts) {
213
261
  return { ok: false, error: (result && result.error) || 'unknown_error' };
214
262
  }
215
263
 
264
+ // Write the consent ack file. Used by `evolver atp enable|disable` and the
265
+ // first-run prompt. `enabled=true` persists opt-in; `enabled=false` persists
266
+ // explicit opt-out so the prompt does not re-ask next session. Atomic write
267
+ // via .tmp + rename so a crash mid-write never produces a corrupt ack file.
268
+ function setConsent(enabled) {
269
+ const dir = getMemoryDir();
270
+ try { if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true }); } catch (_) {}
271
+ const body = {
272
+ enabled: !!enabled,
273
+ acknowledged_at: new Date().toISOString(),
274
+ version: 1,
275
+ };
276
+ const tmp = _ackPath() + '.tmp';
277
+ fs.writeFileSync(tmp, JSON.stringify(body, null, 2) + '\n', 'utf8');
278
+ fs.renameSync(tmp, _ackPath());
279
+ return body;
280
+ }
281
+
216
282
  // Test-only reset, not exported by default.
217
283
  function _resetForTests() {
218
284
  _started = false;
@@ -225,10 +291,20 @@ function _resetForTests() {
225
291
  }
226
292
 
227
293
  module.exports = {
294
+ // Lifecycle.
228
295
  start,
229
296
  stop,
230
297
  isStarted,
231
298
  considerOrder,
299
+ // Consent surface — public API. Production callers (CLI runAtp,
300
+ // cliAutobuyPrompt, the daemon run loop) MUST use these, not the
301
+ // __internals duplicates below, so the "test-only" contract on
302
+ // __internals stays honest (Bugbot PR #141 R6).
303
+ getConsent,
304
+ setConsent,
305
+ getAckPath: _ackPath,
306
+ readAck: _readAck,
307
+ ACK_FILENAME,
232
308
  // Exposed for tests and diagnostics only; callers should not depend on
233
309
  // these internals in production code paths.
234
310
  __internals: {
@@ -237,12 +313,15 @@ module.exports = {
237
313
  questionHash: _questionHash,
238
314
  effectiveCap: _effectiveCap,
239
315
  resetForTests: _resetForTests,
316
+ ackPath: _ackPath,
317
+ readAck: _readAck,
240
318
  constants: {
241
319
  DEFAULT_DAILY_CAP,
242
320
  DEFAULT_PER_ORDER_CAP,
243
321
  COLD_START_WINDOW_MS,
244
322
  DEDUP_TTL_MS,
245
323
  LEDGER_FILENAME,
324
+ ACK_FILENAME,
246
325
  },
247
326
  },
248
327
  };
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
  };