@evomap/evolver 1.90.0 → 2.0.0-beta.1

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,55 +0,0 @@
1
- // Standardized ValidationReport type for GEP.
2
- // Machine-readable, self-contained, and interoperable.
3
- // Can be consumed by external Hubs or Judges for automated assessment.
4
-
5
- const { computeAssetId, SCHEMA_VERSION } = require('./contentHash');
6
- const { captureEnvFingerprint, envFingerprintKey } = require('./envFingerprint');
7
-
8
- // Build a standardized ValidationReport from raw validation results.
9
- function buildValidationReport({ geneId, commands, results, envFp, startedAt, finishedAt }) {
10
- const env = envFp || captureEnvFingerprint();
11
- const resultsList = Array.isArray(results) ? results : [];
12
- const cmdsList = Array.isArray(commands) ? commands : resultsList.map(function (r) { return r && r.cmd ? String(r.cmd) : ''; });
13
- const overallOk = resultsList.length > 0 && resultsList.every(function (r) { return r && r.ok; });
14
- const durationMs =
15
- Number.isFinite(startedAt) && Number.isFinite(finishedAt) ? finishedAt - startedAt : null;
16
-
17
- const report = {
18
- type: 'ValidationReport',
19
- schema_version: SCHEMA_VERSION,
20
- id: 'vr_' + Date.now(),
21
- gene_id: geneId || null,
22
- env_fingerprint: env,
23
- env_fingerprint_key: envFingerprintKey(env),
24
- commands: cmdsList.map(function (cmd, i) {
25
- const r = resultsList[i] || {};
26
- return {
27
- command: String(cmd || ''),
28
- ok: !!r.ok,
29
- stdout: String(r.out || r.stdout || '').slice(0, 4000), // Updated to support both 'out' and 'stdout'
30
- stderr: String(r.err || r.stderr || '').slice(0, 4000), // Updated to support both 'err' and 'stderr'
31
- };
32
- }),
33
- overall_ok: overallOk,
34
- duration_ms: durationMs,
35
- created_at: new Date().toISOString(),
36
- };
37
-
38
- report.asset_id = computeAssetId(report);
39
- return report;
40
- }
41
-
42
- // Validate that an object is a well-formed ValidationReport.
43
- function isValidValidationReport(obj) {
44
- if (!obj || typeof obj !== 'object') return false;
45
- if (obj.type !== 'ValidationReport') return false;
46
- if (!obj.id || typeof obj.id !== 'string') return false;
47
- if (!Array.isArray(obj.commands)) return false;
48
- if (typeof obj.overall_ok !== 'boolean') return false;
49
- return true;
50
- }
51
-
52
- module.exports = {
53
- buildValidationReport,
54
- isValidValidationReport,
55
- };
@@ -1,411 +0,0 @@
1
- // src/gep/validator/index.js
2
- //
3
- // Validator mode entry-point. Feature-gated by EVOLVER_VALIDATOR_ENABLED.
4
- // Intended usage: called once per evolve cycle, it will fetch assigned
5
- // validation tasks from the Hub, execute the provided commands in a
6
- // sandbox, and submit a ValidationReport back to the Hub.
7
- //
8
- // Failure modes are all non-fatal -- a validator that cannot reach the Hub
9
- // or cannot sandbox-execute will simply skip and try again next cycle.
10
- 'use strict';
11
-
12
- const { getNodeId, buildHubHeaders, buildNodeScopedHubHeaders } = require('../a2aProtocol');
13
- const { hubFetch } = require('../hubFetch');
14
- const { runInSandbox, runPreflight } = require('./sandboxExecutor');
15
- const { buildReportPayload, submitReport } = require('./reporter');
16
- const { ensureValidatorStake } = require('./stakeBootstrap');
17
- const { readFeatureFlag } = require('../featureFlags');
18
- const { resolveHubUrl: resolveDefaultHubUrl } = require('../../config');
19
-
20
- const FETCH_TIMEOUT_MS = Number(process.env.EVOLVER_VALIDATOR_FETCH_TIMEOUT_MS) || 8_000;
21
- const MAX_TASKS_PER_CYCLE = Math.max(1, Number(process.env.EVOLVER_VALIDATOR_MAX_TASKS_PER_CYCLE) || 2);
22
-
23
- function buildValidatorHubHeaders() {
24
- const buildHeaders = buildNodeScopedHubHeaders || buildHubHeaders;
25
- return buildHeaders();
26
- }
27
-
28
- // Three-tier resolution:
29
- // 1. Local env (highest priority - user escape hatch). Both ON and OFF are honored.
30
- // 2. Persisted feature flag from disk (set by hub mailbox).
31
- // 3. Code default: ON (validator role is opt-out as of v1.69.0).
32
- function isValidatorEnabled() {
33
- const raw = String(process.env.EVOLVER_VALIDATOR_ENABLED || '').toLowerCase().trim();
34
- if (raw === '0' || raw === 'false' || raw === 'no' || raw === 'off') return false;
35
- if (raw === '1' || raw === 'true' || raw === 'yes' || raw === 'on') return true;
36
- try {
37
- const flag = readFeatureFlag('validator_enabled');
38
- if (typeof flag === 'boolean') return flag;
39
- } catch (_) {}
40
- return true;
41
- }
42
-
43
- function resolveHubUrl() {
44
- // Always go through config.resolveHubUrl() — it reads the same env vars
45
- // getHubUrl() reads plus EVOLVER_DEFAULT_HUB_URL, falls back to the
46
- // PUBLIC_DEFAULT_HUB_URL constant, and enforces the https schema check
47
- // (with EVOMAP_HUB_ALLOW_INSECURE=1 as the explicit escape hatch).
48
- // Preferring the raw getHubUrl() here would let an http:// URL slip past
49
- // this frame; hubFetch would still catch it, but the in-validator code
50
- // path should not look like it accepts an unvalidated URL.
51
- return resolveDefaultHubUrl();
52
- }
53
-
54
- /**
55
- * Fetch validation tasks assigned to this node.
56
- */
57
- async function fetchValidationTasks() {
58
- const nodeId = getNodeId();
59
- if (!nodeId) return [];
60
- const hubUrl = resolveHubUrl();
61
- const url = hubUrl.replace(/\/+$/, '') + '/a2a/fetch';
62
-
63
- const controller = new AbortController();
64
- const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
65
-
66
- const msg = {
67
- protocol: 'gep-a2a',
68
- protocol_version: '1.0.0',
69
- message_type: 'fetch',
70
- message_id: 'msg_' + Date.now().toString(36),
71
- sender_id: nodeId,
72
- timestamp: new Date().toISOString(),
73
- payload: {
74
- // tasks_only short-circuits the Hub's asset search + GDI credit charge.
75
- // Pre-v1.77.0 this sent `validation_only: true`, which Hub ignored,
76
- // causing ~96 credits per 15s poll to be deducted from the owner. Keep
77
- // validation_only as a belt-and-suspenders hint for newer Hubs that
78
- // understand it; tasks_only is the universal no-charge switch.
79
- tasks_only: true,
80
- validation_only: true,
81
- include_tasks: true,
82
- },
83
- };
84
-
85
- try {
86
- const res = await hubFetch(url, {
87
- method: 'POST',
88
- headers: buildValidatorHubHeaders(),
89
- body: JSON.stringify(msg),
90
- signal: controller.signal,
91
- });
92
- clearTimeout(timer);
93
- if (!res.ok) return [];
94
- const data = await res.json();
95
- const p = data.payload || data;
96
- const list = Array.isArray(p.validation_tasks) ? p.validation_tasks : [];
97
- return list;
98
- } catch (_) {
99
- clearTimeout(timer);
100
- return [];
101
- }
102
- }
103
-
104
- /**
105
- * Validate a single task.
106
- * @param {object} task - Hub-provided validation task
107
- * @returns {Promise<{ status: string, report?: object, response?: object, reason?: string }>}
108
- */
109
- async function validateOneTask(task) {
110
- if (!task || !task.task_id || !task.nonce) {
111
- return { status: 'skipped', reason: 'invalid_task_shape' };
112
- }
113
- const commands = Array.isArray(task.validation_commands) ? task.validation_commands : [];
114
- if (commands.length === 0) {
115
- // Nothing to run -- report overall_ok=false so the Hub records a fail and moves on.
116
- const payload = buildReportPayload(task, { results: [], overallOk: false, durationMs: 0 });
117
- const r = await submitReport(payload);
118
- return { status: 'reported_empty', report: payload, response: r };
119
- }
120
-
121
- let execution;
122
- try {
123
- execution = await runInSandbox(commands, {});
124
- } catch (err) {
125
- execution = {
126
- results: [{
127
- cmd: commands[0],
128
- ok: false,
129
- stdout: '',
130
- stderr: 'sandbox_error: ' + (err && err.message ? err.message : String(err)),
131
- exitCode: -1,
132
- durationMs: 0,
133
- timedOut: false,
134
- }],
135
- overallOk: false,
136
- durationMs: 0,
137
- stoppedEarly: true,
138
- };
139
- }
140
-
141
- const payload = buildReportPayload(task, execution);
142
- const response = await submitReport(payload);
143
- return {
144
- status: response && response.ok ? 'reported' : 'report_failed',
145
- report: payload,
146
- response,
147
- };
148
- }
149
-
150
- // Lazy single-shot preflight cache. Runs once per process, reused by both the
151
- // daemon and the inline runValidatorCycle() called from the main evolve loop.
152
- let _preflightPromise = null;
153
- let _preflightResult = null;
154
-
155
- async function _ensurePreflight() {
156
- if (_preflightResult) return _preflightResult;
157
- if (_preflightPromise) return _preflightPromise;
158
- _preflightPromise = (async () => {
159
- try {
160
- _preflightResult = await runPreflight();
161
- } catch (err) {
162
- _preflightResult = {
163
- ok: false,
164
- reason: 'preflight_threw',
165
- durationMs: 0,
166
- stderrTail: err && err.message ? String(err.message) : String(err),
167
- };
168
- }
169
- _daemonStats.preflight = {
170
- ok: !!_preflightResult.ok,
171
- reason: _preflightResult.reason || null,
172
- duration_ms: _preflightResult.durationMs || 0,
173
- at: Date.now(),
174
- };
175
- if (!_preflightResult.ok) {
176
- _preflightDisabled = true;
177
- try {
178
- console.warn(
179
- '[Validator] Preflight FAILED (' + (_preflightResult.reason || 'unknown') + '): ' +
180
- 'cannot spawn `node <script>` inside the sandbox on this host. ' +
181
- 'Validator role is being SKIPPED to avoid flooding the Hub with env_fail reports. ' +
182
- 'Likely causes: node binary not on PATH for headless invocations, missing exec perm, ' +
183
- 'or unwritable TMPDIR. Diagnostic stderr: ' + (_preflightResult.stderrTail || '<empty>')
184
- );
185
- } catch (_) { /* console unavailable -- non-fatal */ }
186
- }
187
- return _preflightResult;
188
- })();
189
- return _preflightPromise;
190
- }
191
-
192
- /**
193
- * Run one validator cycle. Intended to be called from the main evolve loop.
194
- * Returns a summary object (useful for logging/tests).
195
- *
196
- * @param {{ skipStake?: boolean }} [opts]
197
- */
198
- async function runValidatorCycle(opts) {
199
- const options = opts || {};
200
- if (!isValidatorEnabled()) {
201
- return { skipped: 'disabled' };
202
- }
203
- // Lazy preflight gate: refuse to talk to the Hub if the local toolchain
204
- // cannot even run a trivial `node <script>` in the sandbox. Without this,
205
- // every validation task posted to this node returns duration_ms=1 /
206
- // commands_passed=0 and the Hub auto-quarantines the node for chronic
207
- // env_fail.
208
- const pf = await _ensurePreflight();
209
- if (!pf.ok) {
210
- return { skipped: 'preflight_failed', reason: pf.reason || 'unknown' };
211
- }
212
- if (!options.skipStake) {
213
- try {
214
- await ensureValidatorStake({});
215
- } catch (err) {
216
- // non-fatal -- stake may already exist or will retry later
217
- }
218
- }
219
-
220
- const tasks = await fetchValidationTasks();
221
- if (!tasks || tasks.length === 0) {
222
- return { tasks: 0, processed: 0 };
223
- }
224
-
225
- const slice = tasks.slice(0, MAX_TASKS_PER_CYCLE);
226
- const outcomes = [];
227
- for (const t of slice) {
228
- try {
229
- const outcome = await validateOneTask(t);
230
- outcomes.push({ task_id: t.task_id, ...outcome });
231
- } catch (err) {
232
- outcomes.push({
233
- task_id: t.task_id,
234
- status: 'error',
235
- reason: err && err.message ? err.message : String(err),
236
- });
237
- }
238
- }
239
- return { tasks: tasks.length, processed: outcomes.length, outcomes };
240
- }
241
-
242
- // --- Background daemon ---
243
- //
244
- // In long-running modes (--loop / --mad-dog) the validator role used to share
245
- // the main evolve loop and was suppressed by idle gating (skipHubCalls) when
246
- // the host was saturated. The daemon runs independently on its own timer so
247
- // that validator participation does not depend on the agent's foreground load.
248
-
249
- function _envIntDefault(key, fallback) {
250
- const raw = process.env[key];
251
- if (raw === undefined || raw === '') return fallback;
252
- const n = Number(raw);
253
- return Number.isFinite(n) ? n : fallback;
254
- }
255
- const DAEMON_INTERVAL_MS = Math.max(15000, _envIntDefault('EVOLVER_VALIDATOR_DAEMON_INTERVAL_MS', 60000));
256
- const DAEMON_FIRST_DELAY_MS = Math.max(0, _envIntDefault('EVOLVER_VALIDATOR_DAEMON_FIRST_DELAY_MS', 30000));
257
-
258
- let _daemonTimer = null;
259
- let _daemonRunning = false;
260
- let _daemonInflight = false;
261
- let _daemonStats = { ticks: 0, processed: 0, lastError: null, lastRunAt: 0, preflight: null };
262
- let _preflightDisabled = false;
263
- // Generation counter: every poke / start bumps this. Tick captures
264
- // its gen at entry; if it doesn't match on resume, the tick refuses
265
- // to schedule its own next timer (a fresher path already owns it).
266
- // Mirrors the `_heartbeatGen` pattern in proxy/lifecycle/manager.js.
267
- let _daemonGen = 0;
268
-
269
- async function _daemonTick() {
270
- if (_daemonInflight) return;
271
- _daemonInflight = true;
272
- const myGen = _daemonGen;
273
- try {
274
- if (!isValidatorEnabled()) {
275
- _daemonStats.ticks += 1;
276
- return;
277
- }
278
- if (_preflightDisabled) {
279
- // Preflight failed during startup -- stay silent. We still tick so the
280
- // operator can re-enable later by restarting the agent after fixing PATH.
281
- _daemonStats.ticks += 1;
282
- return;
283
- }
284
- const out = await runValidatorCycle({});
285
- _daemonStats.ticks += 1;
286
- _daemonStats.lastRunAt = Date.now();
287
- if (out && typeof out.processed === 'number') {
288
- _daemonStats.processed += out.processed;
289
- if (out.processed > 0) {
290
- console.log('[ValidatorDaemon] processed ' + out.processed + '/' + (out.tasks || 0) + ' task(s).');
291
- }
292
- }
293
- } catch (err) {
294
- _daemonStats.lastError = err && err.message || String(err);
295
- console.warn('[ValidatorDaemon] tick failed (non-fatal): ' + _daemonStats.lastError);
296
- } finally {
297
- _daemonInflight = false;
298
- // Generation guard: a poke or start fired while we were awaiting
299
- // runValidatorCycle(). The fresher path already armed its own
300
- // timer (T2); scheduling here would overwrite the T2 reference
301
- // without clearing it, leaving two self-rearming chains running
302
- // at doubled cadence.
303
- if (_daemonRunning && myGen === _daemonGen) {
304
- _daemonTimer = setTimeout(_daemonTick, DAEMON_INTERVAL_MS);
305
- if (_daemonTimer && typeof _daemonTimer.unref === 'function') _daemonTimer.unref();
306
- }
307
- }
308
- }
309
-
310
- /**
311
- * Start an independent validator daemon. Safe to call once at process boot
312
- * from --loop / --mad-dog modes. No-op if already running.
313
- */
314
- function startValidatorDaemon() {
315
- if (_daemonRunning) return false;
316
- _daemonRunning = true;
317
- _daemonGen += 1;
318
- if (isValidatorEnabled()) {
319
- // Surface an explicit notice every time validator mode starts so that users
320
- // who do not read docs cannot later claim they were unaware the validator
321
- // consumes network / stake / CPU. See GH issue #451.
322
- try {
323
- console.log(
324
- '[Validator] Validator mode is ENABLED. Your node will participate in ' +
325
- 'Hub validation tasks: CPU, network bandwidth, and staked credits WILL ' +
326
- 'be used. To opt out, set EVOLVER_VALIDATOR_ENABLED=false (or unset it).'
327
- );
328
- } catch (_) { /* console unavailable -- non-fatal */ }
329
- // Fire preflight immediately so the user-visible warning lands before the
330
- // first DAEMON_FIRST_DELAY_MS tick. _ensurePreflight is idempotent and the
331
- // first call from runValidatorCycle will reuse this promise.
332
- _ensurePreflight();
333
- }
334
- _daemonTimer = setTimeout(_daemonTick, DAEMON_FIRST_DELAY_MS);
335
- if (_daemonTimer && typeof _daemonTimer.unref === 'function') _daemonTimer.unref();
336
- return true;
337
- }
338
-
339
- function stopValidatorDaemon() {
340
- _daemonRunning = false;
341
- if (_daemonTimer) {
342
- clearTimeout(_daemonTimer);
343
- _daemonTimer = null;
344
- }
345
- }
346
-
347
- /**
348
- * Force the daemon to fire its next tick immediately. The internal
349
- * setTimeout runs on libuv's monotonic clock, which freezes during macOS
350
- * sleep -- without a poke, the post-wake tick may be up to
351
- * DAEMON_INTERVAL_MS (default 60s) away on the resumed clock, leaving
352
- * the node "alive on heartbeat but functionally inert" for that window.
353
- *
354
- * Safe to call when the daemon is stopped (no-op) or when a tick is
355
- * already in flight (the in-flight guard at _daemonTick / the running
356
- * gate at the top of the function both protect against double-firing).
357
- *
358
- * Wired from the index.js SIGCONT handler so wake events fan out to the
359
- * validator the same way they already do to heartbeat and SSE.
360
- */
361
- function pokeValidatorDaemon() {
362
- if (!_daemonRunning) return false;
363
- if (_daemonTimer) {
364
- clearTimeout(_daemonTimer);
365
- _daemonTimer = null;
366
- }
367
- // Bump generation: any in-flight `_daemonTick` from before the
368
- // poke will see its captured gen mismatch on resume and skip its
369
- // tail-`setTimeout`, so we don't end up with two concurrent timers
370
- // self-rearming at doubled cadence.
371
- _daemonGen += 1;
372
- _daemonTimer = setTimeout(_daemonTick, 0);
373
- if (_daemonTimer && typeof _daemonTimer.unref === 'function') _daemonTimer.unref();
374
- return true;
375
- }
376
-
377
- function getValidatorDaemonStats() {
378
- return Object.assign({ running: _daemonRunning, intervalMs: DAEMON_INTERVAL_MS }, _daemonStats);
379
- }
380
-
381
- function _resetPreflightForTests() {
382
- _preflightPromise = null;
383
- _preflightResult = null;
384
- _preflightDisabled = false;
385
- _daemonStats.preflight = null;
386
- }
387
-
388
- function _setPreflightForTests(result) {
389
- _preflightResult = result || { ok: true, durationMs: 0 };
390
- _preflightPromise = Promise.resolve(_preflightResult);
391
- _preflightDisabled = !_preflightResult.ok;
392
- _daemonStats.preflight = {
393
- ok: !!_preflightResult.ok,
394
- reason: _preflightResult.reason || null,
395
- duration_ms: _preflightResult.durationMs || 0,
396
- at: Date.now(),
397
- };
398
- }
399
-
400
- module.exports = {
401
- runValidatorCycle,
402
- fetchValidationTasks,
403
- validateOneTask,
404
- isValidatorEnabled,
405
- startValidatorDaemon,
406
- stopValidatorDaemon,
407
- pokeValidatorDaemon,
408
- getValidatorDaemonStats,
409
- _resetPreflightForTests,
410
- _setPreflightForTests,
411
- };
@@ -1,210 +0,0 @@
1
- // src/gep/validator/reporter.js
2
- //
3
- // Builds and submits validation reports for validation tasks.
4
- // Sends via POST /a2a/report with `task_id` + `nonce` so the Hub routes the
5
- // body into processValidationReport instead of the normal reporting path.
6
- 'use strict';
7
-
8
- const crypto = require('crypto');
9
- const { buildHubHeaders, buildNodeScopedHubHeaders, getHubUrl, getNodeId } = require('../a2aProtocol');
10
- const { hubFetch } = require('../hubFetch');
11
- const { captureEnvFingerprint } = require('../envFingerprint');
12
- const { resolveHubUrl: resolveDefaultHubUrl } = require('../../config');
13
-
14
- const REPORT_TIMEOUT_MS = Number(process.env.EVOLVER_VALIDATOR_REPORT_TIMEOUT_MS) || 10_000;
15
-
16
- function buildValidatorReportHeaders() {
17
- const buildHeaders = buildNodeScopedHubHeaders || buildHubHeaders;
18
- return buildHeaders();
19
- }
20
-
21
- // Per-command stderr/stdout tail bundled into the report. Bounded so a noisy
22
- // validator cannot blow up the Hub's a2a/report payload size.
23
- const REPORT_CMD_TAIL_CHARS = 240;
24
- const REPORT_MAX_COMMANDS = 8;
25
-
26
- // Failure classes attached to each command result so the Hub can distinguish
27
- // "validator host is broken" (env_fail) from "Gene's validation cmd cannot
28
- // run in our hardened sandbox" (sandbox_block) from a real assertion failure
29
- // (exit_nonzero).
30
- const FAILURE_CLASS = {
31
- OK: 'ok',
32
- PARSE_FAILED: 'parse_failed',
33
- EXEC_NOT_ALLOWED: 'executable_not_allowed',
34
- SANDBOX_BLOCK_NODE_FLAG: 'sandbox_block_node_flag',
35
- SPAWN_FAILED: 'spawn_failed',
36
- TIMEOUT: 'timeout',
37
- EXIT_NONZERO: 'exit_nonzero',
38
- UNKNOWN: 'unknown',
39
- };
40
-
41
- function classifyCommandFailure(result) {
42
- if (!result) return FAILURE_CLASS.UNKNOWN;
43
- if (result.ok) return FAILURE_CLASS.OK;
44
- const stderr = String(result.stderr || '');
45
- if (result.timedOut) return FAILURE_CLASS.TIMEOUT;
46
- // Check sandbox-block-on-node-flag BEFORE the generic parse_failed check
47
- // because assertNodeCommandSafe wraps its rejection inside the
48
- // `command_parse_failed:` stderr prefix; a plain prefix check would
49
- // misclassify the Gene/Hub-incompatibility case as a parse error.
50
- if (stderr.includes('node flag not allowed in sandbox')
51
- || stderr.includes('node requires a script file argument')) {
52
- return FAILURE_CLASS.SANDBOX_BLOCK_NODE_FLAG;
53
- }
54
- if (stderr.startsWith('executable_not_allowed:')) return FAILURE_CLASS.EXEC_NOT_ALLOWED;
55
- if (stderr.startsWith('command_parse_failed:')) return FAILURE_CLASS.PARSE_FAILED;
56
- if (stderr.startsWith('spawn_failed:')) return FAILURE_CLASS.SPAWN_FAILED;
57
- if (typeof result.exitCode === 'number' && result.exitCode !== 0) {
58
- return FAILURE_CLASS.EXIT_NONZERO;
59
- }
60
- return FAILURE_CLASS.UNKNOWN;
61
- }
62
-
63
- function tailString(s, n) {
64
- const str = typeof s === 'string' ? s : '';
65
- if (str.length <= n) return str;
66
- return str.slice(-n);
67
- }
68
-
69
- function summarizeResult(result) {
70
- return {
71
- cmd: typeof result.cmd === 'string' ? result.cmd.slice(0, 200) : '',
72
- ok: !!result.ok,
73
- exit_code: typeof result.exitCode === 'number' ? result.exitCode : null,
74
- duration_ms: typeof result.durationMs === 'number' ? result.durationMs : 0,
75
- timed_out: !!result.timedOut,
76
- failure_class: classifyCommandFailure(result),
77
- stderr_tail: tailString(result.stderr, REPORT_CMD_TAIL_CHARS),
78
- };
79
- }
80
-
81
- // Aggregate failure_class for the whole report -- the first non-OK class wins.
82
- // Surfaces a single label in `failure_class` at the top of the report so that
83
- // the Hub can route on it without iterating per-command.
84
- function aggregateFailureClass(commands) {
85
- if (!Array.isArray(commands) || commands.length === 0) return FAILURE_CLASS.UNKNOWN;
86
- if (commands.every((c) => c.failure_class === FAILURE_CLASS.OK)) return FAILURE_CLASS.OK;
87
- for (const c of commands) {
88
- if (c.failure_class && c.failure_class !== FAILURE_CLASS.OK) return c.failure_class;
89
- }
90
- return FAILURE_CLASS.UNKNOWN;
91
- }
92
-
93
- function resolveHubUrl() {
94
- try {
95
- const u = getHubUrl && getHubUrl();
96
- if (u && typeof u === 'string') return u;
97
- } catch (_) {}
98
- return resolveDefaultHubUrl();
99
- }
100
-
101
- function hashExecutionLog(results) {
102
- const list = Array.isArray(results) ? results : [];
103
- const hash = crypto.createHash('sha256');
104
- for (const r of list) {
105
- hash.update(String(r.cmd || ''));
106
- hash.update('\0');
107
- hash.update(String(r.ok ? 1 : 0));
108
- hash.update('\0');
109
- hash.update(String(r.exitCode || 0));
110
- hash.update('\0');
111
- hash.update((r.stdout || '').slice(0, 4000));
112
- hash.update('\0');
113
- hash.update((r.stderr || '').slice(0, 4000));
114
- hash.update('\0');
115
- }
116
- return hash.digest('hex');
117
- }
118
-
119
- /**
120
- * Build the validation report payload the Hub expects.
121
- *
122
- * @param {{ task_id: string, nonce: string }} task
123
- * @param {{ results: Array, overallOk: boolean, durationMs: number }} execution
124
- * @param {{ reproductionScore?: number }} [opts]
125
- */
126
- function buildReportPayload(task, execution, opts) {
127
- const options = opts || {};
128
- const results = Array.isArray(execution && execution.results) ? execution.results : [];
129
- const commandsTotal = results.length;
130
- const commandsPassed = results.filter((r) => r && r.ok).length;
131
- const env = captureEnvFingerprint();
132
-
133
- const reproductionScore = Number.isFinite(options.reproductionScore)
134
- ? options.reproductionScore
135
- : (commandsTotal > 0 ? commandsPassed / commandsTotal : 0);
136
-
137
- // Per-command summaries. Bounded to REPORT_MAX_COMMANDS so a runaway batch
138
- // cannot blow up the Hub payload size.
139
- const commands = results.slice(0, REPORT_MAX_COMMANDS).map(summarizeResult);
140
- const failureClass = !execution.overallOk
141
- ? aggregateFailureClass(commands)
142
- : FAILURE_CLASS.OK;
143
-
144
- return {
145
- task_id: task.task_id,
146
- nonce: task.nonce,
147
- overall_ok: !!execution.overallOk,
148
- commands_passed: commandsPassed,
149
- commands_total: commandsTotal,
150
- duration_ms: execution.durationMs || 0,
151
- execution_log_hash: hashExecutionLog(results),
152
- env_fingerprint: env,
153
- reproduction_score: Math.max(0, Math.min(1, reproductionScore)),
154
- // New diagnostic surface (Hub-side classifier should prefer these when
155
- // present; older Hubs that ignore unknown fields keep working unchanged).
156
- failure_class: failureClass,
157
- commands,
158
- };
159
- }
160
-
161
- async function submitReport(payload) {
162
- const nodeId = getNodeId();
163
- if (!nodeId) return { ok: false, error: 'no_node_id' };
164
- const hubUrl = resolveHubUrl();
165
- const url = hubUrl.replace(/\/+$/, '') + '/a2a/report';
166
-
167
- const controller = new AbortController();
168
- const timer = setTimeout(() => controller.abort(), REPORT_TIMEOUT_MS);
169
-
170
- const msg = {
171
- protocol: 'gep-a2a',
172
- protocol_version: '1.0.0',
173
- message_type: 'report',
174
- message_id: 'msg_' + Date.now().toString(36) + '_' + crypto.randomBytes(3).toString('hex'),
175
- sender_id: nodeId,
176
- timestamp: new Date().toISOString(),
177
- payload,
178
- };
179
-
180
- try {
181
- const res = await hubFetch(url, {
182
- method: 'POST',
183
- headers: buildValidatorReportHeaders(),
184
- body: JSON.stringify(msg),
185
- signal: controller.signal,
186
- });
187
- clearTimeout(timer);
188
- const text = await res.text();
189
- if (!res.ok) {
190
- return { ok: false, status: res.status, error: text.slice(0, 400) };
191
- }
192
- try {
193
- return { ok: true, data: JSON.parse(text) };
194
- } catch {
195
- return { ok: true, data: text };
196
- }
197
- } catch (err) {
198
- clearTimeout(timer);
199
- return { ok: false, error: err && err.message ? err.message : String(err) };
200
- }
201
- }
202
-
203
- module.exports = {
204
- buildReportPayload,
205
- submitReport,
206
- hashExecutionLog,
207
- classifyCommandFailure,
208
- aggregateFailureClass,
209
- FAILURE_CLASS,
210
- };