@evomap/evolver 1.89.20 → 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 -3586
  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/skills/_meta/SKILL.md +0 -41
  38. package/skills/index.json +0 -14
  39. package/src/adapters/claudeCode.js +0 -194
  40. package/src/adapters/codex.js +0 -216
  41. package/src/adapters/cursor.js +0 -91
  42. package/src/adapters/hookAdapter.js +0 -469
  43. package/src/adapters/kiro.js +0 -195
  44. package/src/adapters/opencode.js +0 -326
  45. package/src/adapters/scripts/_lockPaths.js +0 -74
  46. package/src/adapters/scripts/_memoryFiltering.js +0 -35
  47. package/src/adapters/scripts/_runtimePaths.js +0 -440
  48. package/src/adapters/scripts/evolver-session-end.js +0 -321
  49. package/src/adapters/scripts/evolver-session-start.js +0 -587
  50. package/src/adapters/scripts/evolver-signal-detect.js +0 -98
  51. package/src/adapters/scripts/evolver-task-recall.js +0 -173
  52. package/src/atp/atpExecute.js +0 -283
  53. package/src/atp/atpTaskPickup.js +0 -233
  54. package/src/atp/autoBuyer.js +0 -382
  55. package/src/atp/autoDeliver.js +0 -215
  56. package/src/atp/cli.js +0 -354
  57. package/src/atp/cliAutobuyPrompt.js +0 -154
  58. package/src/atp/consumerAgent.js +0 -157
  59. package/src/atp/defaultHandler.js +0 -69
  60. package/src/atp/heartbeatSignalsHandler.js +0 -254
  61. package/src/atp/hubClient.js +0 -317
  62. package/src/atp/index.js +0 -38
  63. package/src/atp/merchantAgent.js +0 -118
  64. package/src/atp/protocol.js +0 -41
  65. package/src/atp/questionComposer.js +0 -133
  66. package/src/atp/serviceHelper.js +0 -92
  67. package/src/canary.js +0 -13
  68. package/src/config.js +0 -351
  69. package/src/evolve/guards.js +0 -1
  70. package/src/evolve/pipeline/collect.js +0 -1
  71. package/src/evolve/pipeline/dispatch.js +0 -1
  72. package/src/evolve/pipeline/enrich.js +0 -1
  73. package/src/evolve/pipeline/hub.js +0 -1
  74. package/src/evolve/pipeline/select.js +0 -1
  75. package/src/evolve/pipeline/signals.js +0 -1
  76. package/src/evolve/utils.js +0 -1
  77. package/src/evolve.js +0 -1
  78. package/src/experiment/agentRunner.js +0 -229
  79. package/src/experiment/cli.js +0 -159
  80. package/src/experiment/comparison.js +0 -233
  81. package/src/experiment/metrics.js +0 -75
  82. package/src/forceUpdate.js +0 -991
  83. package/src/gep/a2a.js +0 -173
  84. package/src/gep/a2aProtocol.js +0 -1
  85. package/src/gep/analyzer.js +0 -35
  86. package/src/gep/antiAbuseTelemetry.js +0 -1
  87. package/src/gep/assetCallLog.js +0 -197
  88. package/src/gep/assetStore.js +0 -723
  89. package/src/gep/assets.js +0 -36
  90. package/src/gep/autoDistillConv.js +0 -1
  91. package/src/gep/autoDistillLlm.js +0 -1
  92. package/src/gep/bridge.js +0 -138
  93. package/src/gep/candidateEval.js +0 -1
  94. package/src/gep/candidates.js +0 -1
  95. package/src/gep/claimNudge.js +0 -121
  96. package/src/gep/cliContracts.js +0 -1190
  97. package/src/gep/contentHash.js +0 -1
  98. package/src/gep/conversationDistiller.js +0 -1
  99. package/src/gep/conversationSniffer.js +0 -1
  100. package/src/gep/crypto.js +0 -1
  101. package/src/gep/curriculum.js +0 -1
  102. package/src/gep/deviceId.js +0 -1
  103. package/src/gep/directoryClient.js +0 -115
  104. package/src/gep/envFingerprint.js +0 -1
  105. package/src/gep/epigenetics.js +0 -1
  106. package/src/gep/execBridge.js +0 -1
  107. package/src/gep/executionTrace.js +0 -291
  108. package/src/gep/explore.js +0 -1
  109. package/src/gep/featureFlags.js +0 -121
  110. package/src/gep/gitOps.js +0 -265
  111. package/src/gep/hash.js +0 -1
  112. package/src/gep/hostErrorClassifier.js +0 -34
  113. package/src/gep/hubFetch.js +0 -1
  114. package/src/gep/hubReview.js +0 -1
  115. package/src/gep/hubSearch.js +0 -1
  116. package/src/gep/hubVerify.js +0 -1
  117. package/src/gep/idleScheduler.js +0 -400
  118. package/src/gep/issueReporter.js +0 -416
  119. package/src/gep/learningSignals.js +0 -1
  120. package/src/gep/llmReview.js +0 -92
  121. package/src/gep/localStateAwareness.js +0 -243
  122. package/src/gep/mailboxTransport.js +0 -119
  123. package/src/gep/memoryGraph.js +0 -1
  124. package/src/gep/memoryGraphAdapter.js +0 -1
  125. package/src/gep/mutation.js +0 -1
  126. package/src/gep/narrativeMemory.js +0 -1
  127. package/src/gep/oauthLogin.js +0 -181
  128. package/src/gep/openPRRegistry.js +0 -1
  129. package/src/gep/paths.js +0 -522
  130. package/src/gep/personality.js +0 -1
  131. package/src/gep/policyCheck.js +0 -1
  132. package/src/gep/portable.js +0 -103
  133. package/src/gep/privacyClient.js +0 -235
  134. package/src/gep/prompt.js +0 -1
  135. package/src/gep/questionGenerator.js +0 -518
  136. package/src/gep/recallInject.js +0 -1
  137. package/src/gep/recallVerifier.js +0 -1
  138. package/src/gep/reflection.js +0 -1
  139. package/src/gep/sanitize.js +0 -264
  140. package/src/gep/savingsCore.js +0 -1
  141. package/src/gep/schemas/capsule.js +0 -170
  142. package/src/gep/schemas/gene.js +0 -154
  143. package/src/gep/schemas/index.js +0 -8
  144. package/src/gep/schemas/protocol.js +0 -51
  145. package/src/gep/schemas/task.js +0 -74
  146. package/src/gep/selector.js +0 -1
  147. package/src/gep/selfPR.js +0 -469
  148. package/src/gep/signals.js +0 -776
  149. package/src/gep/skill2gep.js +0 -1056
  150. package/src/gep/skill2gepAudit.js +0 -303
  151. package/src/gep/skill2recipes.js +0 -511
  152. package/src/gep/skillDistiller.js +0 -1
  153. package/src/gep/skillPublisher.js +0 -358
  154. package/src/gep/solidify.js +0 -1
  155. package/src/gep/strategy.js +0 -1
  156. package/src/gep/taskReceiver.js +0 -575
  157. package/src/gep/tokenSavings.js +0 -1
  158. package/src/gep/trajectoryExport.js +0 -1
  159. package/src/gep/validationReport.js +0 -55
  160. package/src/gep/validator/index.js +0 -411
  161. package/src/gep/validator/reporter.js +0 -210
  162. package/src/gep/validator/sandboxExecutor.js +0 -480
  163. package/src/gep/validator/stakeBootstrap.js +0 -357
  164. package/src/gep/workspaceKeychain.js +0 -1
  165. package/src/ops/cleanup.js +0 -80
  166. package/src/ops/commentary.js +0 -60
  167. package/src/ops/health_check.js +0 -104
  168. package/src/ops/index.js +0 -11
  169. package/src/ops/innovation.js +0 -67
  170. package/src/ops/lifecycle.js +0 -798
  171. package/src/ops/self_repair.js +0 -76
  172. package/src/ops/skills_monitor.js +0 -147
  173. package/src/ops/trigger.js +0 -33
  174. package/src/proxy/clientSettings.js +0 -405
  175. package/src/proxy/envelope.js +0 -59
  176. package/src/proxy/extensions/dmHandler.js +0 -45
  177. package/src/proxy/extensions/sessionHandler.js +0 -141
  178. package/src/proxy/extensions/skillUpdater.js +0 -64
  179. package/src/proxy/extensions/traceControl.js +0 -1
  180. package/src/proxy/index.js +0 -1395
  181. package/src/proxy/inject.js +0 -1
  182. package/src/proxy/lifecycle/manager.js +0 -1568
  183. package/src/proxy/mailbox/state.js +0 -207
  184. package/src/proxy/mailbox/store.js +0 -590
  185. package/src/proxy/router/cache_passthrough.js +0 -26
  186. package/src/proxy/router/features.js +0 -84
  187. package/src/proxy/router/gemini_route.js +0 -154
  188. package/src/proxy/router/messages_route.js +0 -535
  189. package/src/proxy/router/model_router.js +0 -113
  190. package/src/proxy/router/models_route.js +0 -52
  191. package/src/proxy/router/ollama_route.js +0 -103
  192. package/src/proxy/router/responses_route.js +0 -170
  193. package/src/proxy/router/vertex_route.js +0 -110
  194. package/src/proxy/server/http.js +0 -363
  195. package/src/proxy/server/routes.js +0 -558
  196. package/src/proxy/server/settings.js +0 -115
  197. package/src/proxy/sync/engine.js +0 -179
  198. package/src/proxy/sync/inbound.js +0 -211
  199. package/src/proxy/sync/outbound.js +0 -320
  200. package/src/proxy/task/monitor.js +0 -131
  201. package/src/proxy/trace/extractor.js +0 -1
  202. package/src/proxy/trace/usage.js +0 -1
  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,173 +0,0 @@
1
- #!/usr/bin/env node
2
- // evolver-task-recall.js
3
- // UserPromptSubmit hook: on each user prompt, find GEP assets (Hub genes +
4
- // local genes) that match the task and inject a distilled hint so a GENERAL
5
- // agent benefits from prior distilled capabilities without manually calling
6
- // MCP tools. This is the per-harness shell around src/gep/recallInject.js.
7
- //
8
- // Input (stdin JSON, Claude Code UserPromptSubmit):
9
- // { "prompt": "...", "session_id", "cwd", "transcript_path", ... }
10
- // Output (stdout JSON, exit 0 ALWAYS):
11
- // enforce + match -> { agent_message, additionalContext,
12
- // hookSpecificOutput: { hookEventName, additionalContext } }
13
- // everything else -> {} (injects nothing)
14
- //
15
- // DESIGN CONTRACT (the fail-open core — see also recallInject.js invariants):
16
- // - DEFAULT off. off finishes {} WITHOUT parsing the prompt body.
17
- // - shadow computes + logs what WOULD inject but injects nothing.
18
- // - enforce injects the distilled hint.
19
- // - FAIL-OPEN: any error/timeout/empty -> exactly one finish({}). The hook
20
- // blocks the user's prompt, so it must NEVER hang or crash the session.
21
- // Claude Code's timeout fail-open behaviour is UNDOCUMENTED, so we own the
22
- // deadline ourselves with a single watchdog + latch (never rely on the
23
- // host to kill us gracefully).
24
- // - STDOUT is a single JSON object. Any stray console.log() from a
25
- // transitively-required module (e.g. signals._mergeSignals, hubSearch
26
- // fetch-cost log, assetStore seeding) would corrupt it — so we redirect
27
- // console.* to stderr before requiring anything heavy.
28
-
29
- 'use strict';
30
-
31
- // --- stdout-poison defense: route all console.* to stderr ------------------
32
- // Modules we require (hubSearch, signals, assetStore, …) call console.log,
33
- // which writes to stdout. The hook contract is ONE JSON object on stdout, so
34
- // we redirect every console method to stderr. stderr on exit 0 is not fed to
35
- // the model for UserPromptSubmit; on non-2 exit only its first line shows in
36
- // the transcript as a hook-error notice — acceptable and we exit 0 anyway.
37
- for (const m of ['log', 'info', 'warn', 'error', 'debug']) {
38
- try { console[m] = function () { try { process.stderr.write(''); } catch (_) {} }; } catch (_) {}
39
- }
40
-
41
- const path = require('path');
42
-
43
- // ---- Timing budget, coherent with the host kill ---------------------------
44
- // The host (Claude Code) kills this process at the hook's `timeout` (5s in
45
- // buildClaudeHooks -> 5000ms). Our OWN absolute watchdog MUST fire comfortably
46
- // BEFORE that, or the host could kill us mid-write and break the fail-open
47
- // stdout contract. So:
48
- // ABSOLUTE_DEADLINE_MS (3300) < host 5000ms -> ~1.7s headroom for finish().
49
- // EVOLVER_RECALL_TIMEOUT_MS is the Hub SEARCH budget only (clamped well under
50
- // the absolute deadline). The actual budget handed to the search is computed
51
- // DYNAMICALLY at search-start as (deadline - already-elapsed - safety), so
52
- // slow stdin/require can never let the search run past the watchdog (which
53
- // would otherwise spuriously return {} — Bugbot #183 medium findings).
54
- const T0 = Date.now();
55
- const ABSOLUTE_DEADLINE_MS = 3300; // watchdog; strictly < host timeout (5000ms)
56
- const SEARCH_SAFETY_MS = 250; // leave room for finish() after the search
57
- const MIN_SEARCH_MS = 300; // below this, skip the search (finish {})
58
-
59
- function getSearchBudgetMs() {
60
- const n = parseInt(process.env.EVOLVER_RECALL_TIMEOUT_MS, 10);
61
- // Hard cap at 2800 so even a max-budget search starts and ends before the
62
- // 3300ms watchdog under any realistic startup cost.
63
- return Number.isFinite(n) && n >= 500 && n <= 2800 ? n : 2000;
64
- }
65
-
66
- function getMode() {
67
- const v = String(process.env.EVOLVER_RECALL_MODE || '').toLowerCase().trim();
68
- return v === 'shadow' || v === 'enforce' ? v : 'off';
69
- }
70
-
71
- let handled = false;
72
- let watchdog = null;
73
-
74
- // Single-writer latch: exactly one stdout write, exactly one exit. Mirrors the
75
- // proven pattern in evolver-signal-detect.js / evolver-session-end.js.
76
- function finish(obj) {
77
- if (handled) return;
78
- handled = true;
79
- if (watchdog) { try { clearTimeout(watchdog); } catch (_) {} }
80
- try { process.stdout.write(JSON.stringify(obj || {})); } catch (_) {}
81
- process.exit(0);
82
- }
83
-
84
- function main() {
85
- const mode = getMode();
86
-
87
- // Absolute watchdog, armed at process entry and INDEPENDENT of the search
88
- // budget. Fires at ABSOLUTE_DEADLINE_MS (3300ms) — strictly under the host's
89
- // timeout (5000ms) so the host can never kill us mid-write. If stdin never
90
- // closes OR anything hangs, we emit {} and exit cleanly first.
91
- watchdog = setTimeout(() => finish({}), ABSOLUTE_DEADLINE_MS);
92
-
93
- let buf = '';
94
- try {
95
- process.stdin.setEncoding('utf8');
96
- } catch (_) { /* some hosts pass no stdin */ }
97
- process.stdin.on('data', (c) => { buf += c; });
98
- process.stdin.on('error', () => finish({}));
99
- process.stdin.on('end', () => {
100
- if (handled) return;
101
-
102
- // off: do NOT even parse the prompt body (privacy: nothing read/sent).
103
- if (mode === 'off') return finish({});
104
-
105
- let prompt = '';
106
- let sessionId = '';
107
- try {
108
- const input = buf.trim() ? JSON.parse(buf) : {};
109
- prompt = String(input.prompt || '').trim();
110
- sessionId = String(input.session_id || input.sessionId || '').trim();
111
- } catch (_) {
112
- return finish({});
113
- }
114
- if (prompt.length < 8) return finish({}); // trivial prompt -> skip
115
-
116
- // Heavy require INSIDE try/catch: a broken require graph must fail open,
117
- // not crash the user's prompt (this is also why the e2e test runs the
118
- // copied hook with mode=off and asserts exit 0 + parseable stdout).
119
- let core;
120
- try {
121
- const { findEvolverRoot } = require('./_runtimePaths');
122
- const root = findEvolverRoot();
123
- if (!root) return finish({});
124
- core = require(path.join(root, 'src', 'gep', 'recallInject.js'));
125
- } catch (_) {
126
- return finish({});
127
- }
128
-
129
- // Pass the ABSOLUTE deadline (T0 + watchdog window) plus the configured
130
- // search cap. The core bounds the Hub call by the time REMAINING to that
131
- // deadline (minus its own post-await safety margin) and runs local-gene
132
- // disk I/O BEFORE the Hub await — so neither the Hub search nor the
133
- // post-processing can overrun the watchdog (Bugbot #183: slow startup or a
134
- // budget-eating Hub call must not let the timer fire mid-work). If too
135
- // little time remains even before starting, skip and fail open.
136
- const elapsed = Date.now() - T0;
137
- const remaining = ABSOLUTE_DEADLINE_MS - elapsed - SEARCH_SAFETY_MS;
138
- if (remaining < MIN_SEARCH_MS) return finish({});
139
- const deadlineMs = T0 + ABSOLUTE_DEADLINE_MS;
140
-
141
- Promise.resolve()
142
- .then(() => core.recallForTask({ prompt, mode, sessionId, timeoutMs: getSearchBudgetMs(), deadlineMs }))
143
- .then((r) => {
144
- if (r && r.inject && r.text) {
145
- // Emit BOTH shapes:
146
- // - nested hookSpecificOutput.additionalContext is the DOCUMENTED
147
- // canonical UserPromptSubmit injection shape (system-reminder,
148
- // no transcript noise).
149
- // - flat additionalContext / agent_message match the in-repo
150
- // precedent (session-start.js) for hosts that read the flat key.
151
- // Extra keys are tolerated/ignored by hosts; whichever wins, the
152
- // other is harmless.
153
- return finish({
154
- agent_message: r.text,
155
- additionalContext: r.text,
156
- hookSpecificOutput: {
157
- hookEventName: 'UserPromptSubmit',
158
- additionalContext: r.text,
159
- },
160
- });
161
- }
162
- // shadow (logged inside the core) and no-match both inject nothing.
163
- return finish({});
164
- })
165
- .catch(() => finish({}));
166
- });
167
- }
168
-
169
- if (require.main === module) {
170
- main();
171
- } else {
172
- module.exports = { getMode, getSearchBudgetMs };
173
- }
@@ -1,283 +0,0 @@
1
- // ATP end-to-end task completer.
2
- //
3
- // Invoked from the `atp-complete` subcommand (index.js). A spawned Cursor
4
- // sub-session answers an ATP task, writes the answer to disk, then runs:
5
- //
6
- // node index.js atp-complete \
7
- // --task-id=<tid> --order-id=<oid> --answer-file=<path> [--summary="..."]
8
- //
9
- // This module takes that answer and drives the full settlement path:
10
- // 1. Synthesize a minimal Gene + Capsule bundle wrapping the answer.
11
- // 2. POST /a2a/publish to register the Capsule asset on the Hub (signed).
12
- // 3. POST /a2a/task/complete to bind the resultAssetId to the claimed task.
13
- // 4. POST /a2a/atp/deliver to submit the proof_payload (asset_id + result).
14
- //
15
- // Autoverify (verifyMode=auto) on the Hub treats `payload.asset_id` and
16
- // `payload.result` as has_result=true with pass_rate=1.0, so a valid answer
17
- // immediately progresses the DeliveryProof from pending -> verified -> settled.
18
- //
19
- // Failures are returned as { ok:false, stage:..., error:... } so the caller can
20
- // retry per-stage without duplicating upstream effects (Gene/Capsule asset_ids
21
- // are deterministic content-hashes, so republish of the same bundle is
22
- // idempotent server-side).
23
-
24
- const fs = require('fs');
25
- const crypto = require('crypto');
26
-
27
- const { computeAssetId } = require('../gep/contentHash');
28
- const { hubFetch } = require('../gep/hubFetch');
29
- const {
30
- getNodeId,
31
- getHubUrl,
32
- getHubNodeSecret,
33
- buildHubHeaders,
34
- buildNodeScopedHubHeaders,
35
- sendHelloToHub,
36
- } = require('../gep/a2aProtocol');
37
- const { submitDelivery } = require('./hubClient');
38
-
39
- const MAX_ANSWER_CHARS = 32000; // cap capsule.content to protect Hub payload limits
40
- const PUBLISH_TIMEOUT_MS = 15000;
41
-
42
- function _readAnswer(answerFile) {
43
- const raw = fs.readFileSync(answerFile, 'utf8');
44
- const trimmed = String(raw || '').trim();
45
- if (!trimmed) throw new Error('answer file is empty');
46
- if (trimmed.length > MAX_ANSWER_CHARS) {
47
- return trimmed.slice(0, MAX_ANSWER_CHARS - 40) + '\n...[TRUNCATED]...';
48
- }
49
- return trimmed;
50
- }
51
-
52
- function _buildGene(capabilities, signals) {
53
- const caps = Array.isArray(capabilities) && capabilities.length > 0
54
- ? capabilities.slice(0, 8)
55
- : ['general'];
56
- const sig = Array.isArray(signals) && signals.length > 0
57
- ? signals.slice(0, 8)
58
- : ['atp_task'];
59
- const gene = {
60
- type: 'Gene',
61
- schema_version: '1.0.0',
62
- id: 'gene_atp_answer_' + caps.sort().join('_').slice(0, 40),
63
- summary: 'Deliver an ATP task answer for capabilities: ' + caps.join(', '),
64
- signals_match: sig,
65
- category: 'innovate',
66
- strategy: [
67
- 'Read the buyer question carefully and identify the requested capability.',
68
- 'Produce a concrete, actionable answer addressing the question directly.',
69
- 'Return the answer as Capsule content for verifiable delivery.',
70
- ],
71
- // gep-sdk Gene schema requires `constraints`; an ATP answer edits no
72
- // files, so the blast radius is empty rather than left unbounded.
73
- constraints: { max_files: 0, forbidden_paths: [] },
74
- validation: [
75
- 'Answer is non-empty and directly addresses the buyer question.',
76
- 'Answer references the requested capabilities where relevant.',
77
- ],
78
- };
79
- gene.asset_id = computeAssetId(gene);
80
- return gene;
81
- }
82
-
83
- function _buildCapsule({ gene, answer, summary, orderId, taskId, capabilities, signals }) {
84
- const caps = Array.isArray(capabilities) ? capabilities.slice(0, 8) : [];
85
- const sig = Array.isArray(signals) && signals.length > 0 ? signals.slice(0, 8) : ['atp_task'];
86
- const confidence = 0.9; // merchant self-attested; buyer verify may override
87
- const capsuleSummary = String(summary || '').trim()
88
- || 'ATP merchant delivery for order ' + String(orderId || '').slice(0, 24);
89
- const capsule = {
90
- type: 'Capsule',
91
- schema_version: '1.0.0',
92
- id: 'capsule_atp_' + String(orderId || taskId || Date.now()).replace(/[^a-zA-Z0-9_\-]/g, '_').slice(0, 40),
93
- trigger: sig,
94
- gene: gene.id,
95
- summary: capsuleSummary.slice(0, 200),
96
- confidence,
97
- blast_radius: { files: 0, lines: Math.min(1000, answer.split('\n').length) },
98
- outcome: { status: 'success', score: confidence },
99
- env_fingerprint: { platform: process.platform, arch: process.arch, runtime: 'evolver-atp' },
100
- content: answer,
101
- // 'generated' is the gep-sdk source_type enum value for a freshly
102
- // produced asset; the ATP-specific provenance rides in `a2a.atp` below.
103
- source_type: 'generated',
104
- // The order/task association MUST live under `a2a`, not as a top-level
105
- // `atp` key: the Hub's payload sanitizer allow-lists `a2a` but not `atp`
106
- // (CAPSULE_ALLOWED_FIELDS), so a top-level `atp` was being silently
107
- // stripped on publish and the association never reached the Hub. `a2a`
108
- // is also an open object in gep-sdk's Capsule schema, so this keeps the
109
- // bundle GEP-valid.
110
- a2a: {
111
- atp: {
112
- order_id: orderId || null,
113
- task_id: taskId || null,
114
- capabilities: caps,
115
- },
116
- },
117
- };
118
- capsule.asset_id = computeAssetId(capsule);
119
- return capsule;
120
- }
121
-
122
- function _publishUrl() {
123
- const base = String(getHubUrl() || '').replace(/\/+$/, '');
124
- if (!base) throw new Error('hub url not configured');
125
- return base + '/a2a/publish';
126
- }
127
-
128
- async function _postJson(urlStr, body, timeoutMs) {
129
- const payload = JSON.stringify(body || {});
130
- const buildHeaders = buildNodeScopedHubHeaders || buildHubHeaders;
131
- const headers = Object.assign(
132
- { 'Content-Type': 'application/json' },
133
- buildHeaders() || {},
134
- );
135
-
136
- try {
137
- const res = await hubFetch(urlStr, {
138
- method: 'POST',
139
- headers: headers,
140
- body: payload,
141
- signal: AbortSignal.timeout(timeoutMs || PUBLISH_TIMEOUT_MS),
142
- });
143
- const text = await res.text();
144
- let data = null;
145
- try { data = text ? JSON.parse(text) : null; } catch (e) { data = { raw: text }; }
146
- if (res.status >= 200 && res.status < 300) {
147
- return { ok: true, status: res.status, data };
148
- }
149
- return { ok: false, status: res.status, data, error: 'http_' + res.status };
150
- } catch (err) {
151
- const msg = (err && err.message) || String(err);
152
- if (msg.indexOf('[hubFetch]') !== -1) {
153
- if (/not a valid URL/i.test(msg)) return { ok: false, error: 'invalid_url: ' + msg };
154
- return { ok: false, error: 'tls_refused: ' + msg };
155
- }
156
- return { ok: false, error: msg };
157
- }
158
- }
159
-
160
- async function _ensureNodeSecret() {
161
- if (getHubNodeSecret()) return true;
162
- try {
163
- const hello = await sendHelloToHub();
164
- return !!(hello && hello.ok);
165
- } catch (e) {
166
- return false;
167
- }
168
- }
169
-
170
- async function _publishBundle(gene, capsule) {
171
- const nodeSecret = getHubNodeSecret();
172
- if (!nodeSecret) return { ok: false, error: 'missing_node_secret_after_hello' };
173
- const signatureInput = [gene.asset_id, capsule.asset_id].sort().join('|');
174
- const signature = crypto.createHmac('sha256', nodeSecret).update(signatureInput).digest('hex');
175
- const msg = {
176
- protocol: 'gep-a2a',
177
- protocol_version: '1.0.0',
178
- message_type: 'publish',
179
- message_id: 'msg_atp_' + crypto.randomBytes(8).toString('hex'),
180
- timestamp: new Date().toISOString(),
181
- sender_id: getNodeId(),
182
- payload: { assets: [gene, capsule], signature: signature },
183
- };
184
- return _postJson(_publishUrl(), msg, PUBLISH_TIMEOUT_MS);
185
- }
186
-
187
- async function _completeTaskOnHub(taskId, assetId) {
188
- const base = String(getHubUrl() || '').replace(/\/+$/, '');
189
- if (!base) return { ok: false, error: 'hub_url_missing' };
190
- return _postJson(base + '/a2a/task/complete', {
191
- task_id: taskId,
192
- asset_id: assetId,
193
- node_id: getNodeId(),
194
- }, PUBLISH_TIMEOUT_MS);
195
- }
196
-
197
- /**
198
- * End-to-end ATP task completion driver.
199
- *
200
- * @param {object} opts
201
- * @param {string} opts.taskId - Hub task row id (required)
202
- * @param {string} opts.orderId - ATP DeliveryProof id (required)
203
- * @param {string} opts.answerFile - Path to file holding the merchant answer (required)
204
- * @param {string} [opts.summary] - Short summary for capsule.summary
205
- * @param {string[]} [opts.capabilities] - Listing capabilities (metadata only)
206
- * @param {string[]} [opts.signals] - Task signals (metadata only)
207
- * @returns {Promise<{ok:boolean, stage?:string, error?:string, assetId?:string}>}
208
- */
209
- async function completeAtpTask(opts) {
210
- const taskId = opts && opts.taskId;
211
- const orderId = opts && opts.orderId;
212
- const answerFile = opts && opts.answerFile;
213
- if (!taskId || !orderId || !answerFile) {
214
- return { ok: false, stage: 'input', error: 'taskId, orderId, answerFile are required' };
215
- }
216
-
217
- let answer;
218
- try {
219
- answer = _readAnswer(answerFile);
220
- } catch (e) {
221
- return { ok: false, stage: 'read_answer', error: e && e.message };
222
- }
223
-
224
- const handshakeOk = await _ensureNodeSecret();
225
- if (!handshakeOk) {
226
- return { ok: false, stage: 'hello', error: 'failed to register with hub; node_secret missing' };
227
- }
228
-
229
- const gene = _buildGene(opts.capabilities, opts.signals);
230
- const capsule = _buildCapsule({
231
- gene,
232
- answer,
233
- summary: opts.summary,
234
- orderId,
235
- taskId,
236
- capabilities: opts.capabilities,
237
- signals: opts.signals,
238
- });
239
-
240
- const pub = await _publishBundle(gene, capsule);
241
- if (!pub.ok) {
242
- return { ok: false, stage: 'publish', error: pub.error || 'publish_failed', details: pub };
243
- }
244
- const decision = pub.data && pub.data.payload && pub.data.payload.decision;
245
- if (decision && decision !== 'accept') {
246
- const reason = pub.data.payload.reason || 'unknown';
247
- return { ok: false, stage: 'publish', error: 'publish_rejected: ' + reason, details: pub.data };
248
- }
249
-
250
- const complete = await _completeTaskOnHub(taskId, capsule.asset_id);
251
- if (!complete.ok) {
252
- return { ok: false, stage: 'complete', error: complete.error || 'complete_failed', details: complete };
253
- }
254
-
255
- const proofPayload = {
256
- asset_id: capsule.asset_id,
257
- result: capsule.summary,
258
- content_hash: capsule.asset_id,
259
- pass_rate: 1.0,
260
- delivered_by: getNodeId(),
261
- task_id: taskId,
262
- };
263
-
264
- const delivery = await submitDelivery(orderId, proofPayload);
265
- if (!delivery || !delivery.ok) {
266
- return {
267
- ok: false,
268
- stage: 'deliver',
269
- error: (delivery && delivery.error) || 'deliver_failed',
270
- assetId: capsule.asset_id,
271
- details: delivery,
272
- };
273
- }
274
-
275
- return { ok: true, assetId: capsule.asset_id, deliveryId: delivery.data && delivery.data.proof_id };
276
- }
277
-
278
- module.exports = {
279
- completeAtpTask,
280
- // exported for tests
281
- _buildGene,
282
- _buildCapsule,
283
- };
@@ -1,233 +0,0 @@
1
- // ATP Task Pickup (merchant-side)
2
- //
3
- // When a buyer places an ATP order, the Hub creates a Task row in status
4
- // "claimed" already bound to a target merchant node (see orderRouterService).
5
- // That Task never appears in /a2a/fetch (which only returns status="open"
6
- // tasks), so without this module the merchant's Evolver runtime never knows
7
- // it has work to do, no resultAssetId is ever written, autoDeliver never
8
- // runs, and the DeliveryProof expires after 7 days.
9
- //
10
- // This module bridges the gap by:
11
- // 1. Polling /a2a/task/my for tasks with atp_order_id set and no
12
- // result_asset_id yet (the "merchant owes work" shape).
13
- // 2. Producing a renderable sessions_spawn(...) prompt that the main loop
14
- // can emit to stdout. The Evolver wrapper (Cursor/Claude Code hook)
15
- // picks that up and launches a sub-session that answers the question
16
- // and runs `node index.js atp-complete` to settle the order.
17
- // 3. Deduping via a local ledger so the same task is never spawned twice,
18
- // even across restarts.
19
- //
20
- // The module never *itself* prints sessions_spawn. It only PROVIDES the
21
- // spawn string to whoever orchestrates stdout (evolve.js main loop), so the
22
- // existing "one sessions_spawn per cycle" contract with the wrapper is
23
- // preserved and evolve's normal bridge is not interfered with.
24
-
25
- const fs = require('fs');
26
- const path = require('path');
27
-
28
- const { getMemoryDir } = require('../gep/paths');
29
- const { renderSessionsSpawnCall } = require('../gep/bridge');
30
- const hubClient = require('./hubClient');
31
-
32
- const LEDGER_FILENAME = 'atp-pickup-ledger.json';
33
- const LEDGER_MAX_ENTRIES = 500;
34
- const SPAWN_COOLDOWN_MS = 5 * 60 * 1000; // do not respawn the same task within 5 min
35
- const MAX_ANSWER_PROMPT_CHARS = 12000;
36
-
37
- function _isEnabled() {
38
- const raw = (process.env.EVOLVER_ATP_PICKUP || 'on').toLowerCase().trim();
39
- return raw !== 'off' && raw !== '0' && raw !== 'false';
40
- }
41
-
42
- function _ledgerPath() {
43
- return path.join(getMemoryDir(), LEDGER_FILENAME);
44
- }
45
-
46
- function _emptyLedger() {
47
- return { version: 1, spawned: {} };
48
- }
49
-
50
- function _readLedger() {
51
- try {
52
- const p = _ledgerPath();
53
- if (!fs.existsSync(p)) return _emptyLedger();
54
- const raw = fs.readFileSync(p, 'utf8');
55
- const parsed = JSON.parse(raw);
56
- if (!parsed || typeof parsed !== 'object' || !parsed.spawned) return _emptyLedger();
57
- return parsed;
58
- } catch (_) {
59
- return _emptyLedger();
60
- }
61
- }
62
-
63
- function _writeLedger(ledger) {
64
- try {
65
- const dir = getMemoryDir();
66
- if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
67
- const entries = Object.entries(ledger.spawned || {});
68
- if (entries.length > LEDGER_MAX_ENTRIES) {
69
- ledger.spawned = Object.fromEntries(entries.slice(-LEDGER_MAX_ENTRIES));
70
- }
71
- const tmp = _ledgerPath() + '.tmp';
72
- fs.writeFileSync(tmp, JSON.stringify(ledger, null, 2));
73
- fs.renameSync(tmp, _ledgerPath());
74
- } catch (_) {
75
- // Non-fatal: next tick will re-read Hub state. Stale ledger at worst
76
- // causes a duplicate spawn, which the Hub will 409 on already-completed
77
- // tasks without side effects.
78
- }
79
- }
80
-
81
- function _isEligible(task) {
82
- if (!task || typeof task !== 'object') return false;
83
- if (!task.atp_order_id) return false;
84
- if (task.result_asset_id) return false;
85
- if (task.status && task.status !== 'claimed' && task.status !== 'open') return false;
86
- if (!task.id) return false;
87
- return true;
88
- }
89
-
90
- function _recentlySpawned(ledger, taskId) {
91
- const entry = ledger.spawned && ledger.spawned[taskId];
92
- if (!entry || typeof entry !== 'object') return false;
93
- const ts = Number(entry.at) || 0;
94
- return Date.now() - ts < SPAWN_COOLDOWN_MS;
95
- }
96
-
97
- function _clipQuestion(q) {
98
- const s = String(q || '').trim();
99
- if (!s) return '(buyer did not provide a question body)';
100
- if (s.length <= MAX_ANSWER_PROMPT_CHARS) return s;
101
- return s.slice(0, MAX_ANSWER_PROMPT_CHARS - 40) + '\n...[TRUNCATED]...';
102
- }
103
-
104
- function _answerFilePath(taskId) {
105
- const safe = String(taskId || 'task').replace(/[^a-zA-Z0-9_\-]/g, '_').slice(0, 64);
106
- return path.join(getMemoryDir(), 'atp_answer_' + safe + '.md');
107
- }
108
-
109
- function _buildSpawnTask(task, opts) {
110
- const capabilities = Array.isArray(task.capabilities) ? task.capabilities.slice(0, 8) : [];
111
- const signalsCsv = task.signals ? String(task.signals) : '';
112
- const answerPath = _answerFilePath(task.id);
113
- const question = _clipQuestion(task.user_question_body || task.description || task.title);
114
-
115
- const evolverExec = opts && opts.evolverExec ? opts.evolverExec : 'node index.js';
116
-
117
- const lines = [
118
- 'You are an ATP merchant sub-agent. A buyer has paid credits for your node to answer their request.',
119
- '',
120
- '# Task',
121
- '- Task ID: ' + task.id,
122
- '- ATP Order ID: ' + task.atp_order_id,
123
- '- Title: ' + String(task.title || '(no title)').slice(0, 200),
124
- '- Capabilities requested: ' + (capabilities.length ? capabilities.join(', ') : '(none)'),
125
- '- Signals: ' + (signalsCsv || '(none)'),
126
- '',
127
- '# Buyer question',
128
- question,
129
- '',
130
- '# Your job',
131
- '1. Produce a concrete, useful answer to the buyer question above.',
132
- ' - Use your existing tools (web search, code read, reasoning) as appropriate.',
133
- ' - Keep the answer focused and actionable; do not invent facts.',
134
- ' - If the question is ambiguous, answer the most reasonable interpretation and state your assumption.',
135
- '2. Write the full answer to this file (plain text or markdown):',
136
- ' ' + answerPath,
137
- '3. Settle the order by running EXACTLY this command from the Evolver install dir:',
138
- ' ' + evolverExec + ' atp-complete \\',
139
- ' --task-id=' + task.id + ' \\',
140
- ' --order-id=' + task.atp_order_id + ' \\',
141
- ' --answer-file=' + answerPath +
142
- (capabilities.length ? ' \\\n --capabilities=' + capabilities.join(',') : '') +
143
- (signalsCsv ? ' \\\n --signals=' + signalsCsv : ''),
144
- '4. If atp-complete prints "[ATP-Complete] OK asset_id=...", you are done.',
145
- ' If it prints "FAILED", read the stage= field. Safe to retry the same command.',
146
- '',
147
- '# Hard rules',
148
- '- Do NOT commit or push any repo changes -- this is a per-order side task, not a code evolution.',
149
- '- Do NOT run `node index.js solidify` or `node index.js run`.',
150
- '- Do NOT fabricate the answer; if you cannot answer, still run atp-complete with a short',
151
- ' honest explanation so the buyer is not left waiting for 7 days.',
152
- '- Keep the answer under 12k characters.',
153
- ];
154
- return lines.join('\n');
155
- }
156
-
157
- /**
158
- * Fetch a pickup action if one is due. Idempotent -- safe to call from the
159
- * main loop every cycle.
160
- *
161
- * @param {object} [opts]
162
- * @param {number} [opts.limit=5] -- how many Hub tasks to consider per call
163
- * @param {string} [opts.evolverExec] -- how the wrapper should invoke Evolver
164
- * @returns {Promise<null | { spawnCall: string, task: object }>}
165
- * null when there is nothing to do; otherwise a sessions_spawn() string
166
- * the caller SHOULD print to stdout on its next cycle output and the task
167
- * we picked so the caller can log it.
168
- */
169
- async function pickOne(opts) {
170
- if (!_isEnabled()) return null;
171
- const limit = Math.max(1, Math.min(20, Number(opts && opts.limit) || 5));
172
-
173
- let listResult;
174
- try {
175
- listResult = await hubClient.listMyTasks(limit);
176
- } catch (_) {
177
- return null;
178
- }
179
- if (!listResult || !listResult.ok) return null;
180
-
181
- const tasks = (listResult.data && Array.isArray(listResult.data.tasks))
182
- ? listResult.data.tasks
183
- : (Array.isArray(listResult.data) ? listResult.data : []);
184
- if (!tasks.length) return null;
185
-
186
- const ledger = _readLedger();
187
- let picked = null;
188
- for (const t of tasks) {
189
- if (!_isEligible(t)) continue;
190
- if (_recentlySpawned(ledger, t.id)) continue;
191
- picked = t;
192
- break;
193
- }
194
- if (!picked) return null;
195
-
196
- const spawnTask = _buildSpawnTask(picked, opts);
197
- const spawnCall = renderSessionsSpawnCall({
198
- task: spawnTask,
199
- agentId: 'atp_pickup',
200
- cleanup: 'delete',
201
- label: 'atp_pickup_' + String(picked.id).slice(0, 32),
202
- });
203
-
204
- ledger.spawned = ledger.spawned || {};
205
- ledger.spawned[picked.id] = { at: Date.now(), order_id: picked.atp_order_id };
206
- _writeLedger(ledger);
207
-
208
- return { spawnCall, task: picked };
209
- }
210
-
211
- /**
212
- * Forget a previously-spawned task so the main loop will retry it next cycle.
213
- * Called by callers that detected the spawn channel was unavailable (e.g.
214
- * wrapper not attached) so we do not burn the cooldown on a no-op spawn.
215
- */
216
- function forget(taskId) {
217
- if (!taskId) return;
218
- const ledger = _readLedger();
219
- if (ledger.spawned && ledger.spawned[taskId]) {
220
- delete ledger.spawned[taskId];
221
- _writeLedger(ledger);
222
- }
223
- }
224
-
225
- module.exports = {
226
- pickOne,
227
- forget,
228
- _isEnabled,
229
- _isEligible,
230
- _buildSpawnTask,
231
- _recentlySpawned,
232
- _answerFilePath,
233
- };