@evomap/evolver 1.67.6 → 1.68.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/gep/candidates.jsonl +9 -6
- package/index.js +9 -0
- package/package.json +1 -1
- package/src/config.js +42 -0
- package/src/evolve.js +1 -1
- package/src/gep/.integrity +0 -0
- package/src/gep/a2aProtocol.js +1 -1
- package/src/gep/candidateEval.js +1 -1
- package/src/gep/candidates.js +1 -1
- package/src/gep/contentHash.js +1 -1
- package/src/gep/crypto.js +1 -1
- package/src/gep/curriculum.js +1 -1
- package/src/gep/deviceId.js +1 -1
- package/src/gep/envFingerprint.js +1 -1
- package/src/gep/explore.js +1 -1
- package/src/gep/hubReview.js +1 -1
- package/src/gep/hubSearch.js +1 -1
- package/src/gep/hubVerify.js +1 -1
- package/src/gep/integrityCheck.js +1 -1
- package/src/gep/learningSignals.js +1 -1
- package/src/gep/memoryGraph.js +1 -1
- package/src/gep/memoryGraphAdapter.js +1 -1
- package/src/gep/mutation.js +1 -1
- package/src/gep/narrativeMemory.js +1 -1
- package/src/gep/personality.js +1 -1
- package/src/gep/policyCheck.js +1 -1
- package/src/gep/prompt.js +1 -1
- package/src/gep/reflection.js +1 -1
- package/src/gep/selector.js +1 -1
- package/src/gep/shield.js +1 -1
- package/src/gep/skillDistiller.js +1 -1
- package/src/gep/solidify.js +1 -1
- package/src/gep/strategy.js +1 -1
- package/src/gep/validator/index.js +170 -0
- package/src/gep/validator/reporter.js +118 -0
- package/src/gep/validator/sandboxExecutor.js +262 -0
- package/src/gep/validator/stakeBootstrap.js +81 -0
- package/src/ops/skills_monitor.js +1 -1
- package/src/proxy/mailbox/store.js +3 -1
|
@@ -0,0 +1,262 @@
|
|
|
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
|
+
function safeNumber(v, fallback) {
|
|
32
|
+
const n = Number(v);
|
|
33
|
+
if (!Number.isFinite(n) || n <= 0) return fallback;
|
|
34
|
+
return n;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function truncate(str, limit) {
|
|
38
|
+
if (typeof str !== 'string') return '';
|
|
39
|
+
if (str.length <= limit) return str;
|
|
40
|
+
return str.slice(0, limit) + '\n...[truncated]';
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function createSandboxDir() {
|
|
44
|
+
const base = path.join(os.tmpdir(), 'evolver-validator');
|
|
45
|
+
if (!fs.existsSync(base)) {
|
|
46
|
+
fs.mkdirSync(base, { recursive: true, mode: 0o700 });
|
|
47
|
+
}
|
|
48
|
+
const name = 'task_' + Date.now().toString(36) + '_' + crypto.randomBytes(4).toString('hex');
|
|
49
|
+
const dir = path.join(base, name);
|
|
50
|
+
fs.mkdirSync(dir, { mode: 0o700 });
|
|
51
|
+
return dir;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function cleanupDir(dir) {
|
|
55
|
+
if (!dir) return;
|
|
56
|
+
try {
|
|
57
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
58
|
+
} catch (_) {
|
|
59
|
+
// non-fatal
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function buildSandboxEnv() {
|
|
64
|
+
// Minimal env: no secrets, no proxy, TMPDIR isolated, PATH kept.
|
|
65
|
+
const base = {
|
|
66
|
+
PATH: process.env.PATH || '/usr/local/bin:/usr/bin:/bin',
|
|
67
|
+
HOME: process.env.HOME || '/tmp',
|
|
68
|
+
LANG: process.env.LANG || 'C.UTF-8',
|
|
69
|
+
LC_ALL: process.env.LC_ALL || 'C.UTF-8',
|
|
70
|
+
NODE_ENV: 'sandbox',
|
|
71
|
+
EVOLVER_SANDBOX: '1',
|
|
72
|
+
};
|
|
73
|
+
// Explicitly block common outbound-config envs.
|
|
74
|
+
return base;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function runSingleCommand(cmd, opts) {
|
|
78
|
+
const options = opts || {};
|
|
79
|
+
const timeoutMs = Math.min(
|
|
80
|
+
safeNumber(options.timeoutMs, DEFAULT_CMD_TIMEOUT_MS),
|
|
81
|
+
MAX_CMD_TIMEOUT_MS,
|
|
82
|
+
);
|
|
83
|
+
const cwd = options.cwd;
|
|
84
|
+
const env = buildSandboxEnv();
|
|
85
|
+
|
|
86
|
+
return new Promise((resolve) => {
|
|
87
|
+
let child;
|
|
88
|
+
try {
|
|
89
|
+
child = spawn(String(cmd), {
|
|
90
|
+
shell: true,
|
|
91
|
+
cwd,
|
|
92
|
+
env,
|
|
93
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
94
|
+
});
|
|
95
|
+
} catch (err) {
|
|
96
|
+
resolve({
|
|
97
|
+
cmd: String(cmd),
|
|
98
|
+
ok: false,
|
|
99
|
+
stdout: '',
|
|
100
|
+
stderr: 'spawn_failed: ' + (err && err.message ? err.message : String(err)),
|
|
101
|
+
exitCode: -1,
|
|
102
|
+
durationMs: 0,
|
|
103
|
+
timedOut: false,
|
|
104
|
+
});
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
let stdout = '';
|
|
109
|
+
let stderr = '';
|
|
110
|
+
let settled = false;
|
|
111
|
+
const startedAt = Date.now();
|
|
112
|
+
|
|
113
|
+
const timer = setTimeout(() => {
|
|
114
|
+
if (!child.killed) {
|
|
115
|
+
try { child.kill('SIGKILL'); } catch (_) {}
|
|
116
|
+
}
|
|
117
|
+
if (!settled) {
|
|
118
|
+
settled = true;
|
|
119
|
+
resolve({
|
|
120
|
+
cmd: String(cmd),
|
|
121
|
+
ok: false,
|
|
122
|
+
stdout: truncate(stdout, MAX_OUTPUT_CHARS),
|
|
123
|
+
stderr: truncate(stderr + '\n[killed by sandbox timeout]', MAX_OUTPUT_CHARS),
|
|
124
|
+
exitCode: -1,
|
|
125
|
+
durationMs: Date.now() - startedAt,
|
|
126
|
+
timedOut: true,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
}, timeoutMs);
|
|
130
|
+
|
|
131
|
+
child.stdout.on('data', (d) => {
|
|
132
|
+
stdout += d.toString('utf8');
|
|
133
|
+
if (stdout.length > MAX_OUTPUT_CHARS * 2) {
|
|
134
|
+
stdout = stdout.slice(-MAX_OUTPUT_CHARS * 2);
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
child.stderr.on('data', (d) => {
|
|
138
|
+
stderr += d.toString('utf8');
|
|
139
|
+
if (stderr.length > MAX_OUTPUT_CHARS * 2) {
|
|
140
|
+
stderr = stderr.slice(-MAX_OUTPUT_CHARS * 2);
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
child.on('error', (err) => {
|
|
145
|
+
if (settled) return;
|
|
146
|
+
settled = true;
|
|
147
|
+
clearTimeout(timer);
|
|
148
|
+
resolve({
|
|
149
|
+
cmd: String(cmd),
|
|
150
|
+
ok: false,
|
|
151
|
+
stdout: truncate(stdout, MAX_OUTPUT_CHARS),
|
|
152
|
+
stderr: truncate(stderr + '\n' + (err && err.message ? err.message : String(err)), MAX_OUTPUT_CHARS),
|
|
153
|
+
exitCode: -1,
|
|
154
|
+
durationMs: Date.now() - startedAt,
|
|
155
|
+
timedOut: false,
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
child.on('exit', (code, signal) => {
|
|
160
|
+
if (settled) return;
|
|
161
|
+
settled = true;
|
|
162
|
+
clearTimeout(timer);
|
|
163
|
+
const exitCode = typeof code === 'number' ? code : (signal ? -1 : -1);
|
|
164
|
+
resolve({
|
|
165
|
+
cmd: String(cmd),
|
|
166
|
+
ok: exitCode === 0,
|
|
167
|
+
stdout: truncate(stdout, MAX_OUTPUT_CHARS),
|
|
168
|
+
stderr: truncate(stderr, MAX_OUTPUT_CHARS),
|
|
169
|
+
exitCode,
|
|
170
|
+
durationMs: Date.now() - startedAt,
|
|
171
|
+
timedOut: false,
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Run a list of validation commands in a fresh sandbox directory.
|
|
179
|
+
* Stops at the first failure and returns partial results.
|
|
180
|
+
*
|
|
181
|
+
* @param {string[]} commands
|
|
182
|
+
* @param {{ cmdTimeoutMs?: number, batchTimeoutMs?: number, keepSandbox?: boolean }} [opts]
|
|
183
|
+
* @returns {Promise<{
|
|
184
|
+
* results: Array<{ cmd: string, ok: boolean, stdout: string, stderr: string, exitCode: number, durationMs: number, timedOut: boolean }>,
|
|
185
|
+
* overallOk: boolean,
|
|
186
|
+
* sandboxDir: string,
|
|
187
|
+
* durationMs: number,
|
|
188
|
+
* stoppedEarly: boolean,
|
|
189
|
+
* }>}
|
|
190
|
+
*/
|
|
191
|
+
async function runInSandbox(commands, opts) {
|
|
192
|
+
const options = opts || {};
|
|
193
|
+
const cmds = Array.isArray(commands) ? commands.filter((c) => typeof c === 'string' && c.trim()) : [];
|
|
194
|
+
const startedAt = Date.now();
|
|
195
|
+
|
|
196
|
+
if (cmds.length === 0) {
|
|
197
|
+
return {
|
|
198
|
+
results: [],
|
|
199
|
+
overallOk: false,
|
|
200
|
+
sandboxDir: null,
|
|
201
|
+
durationMs: 0,
|
|
202
|
+
stoppedEarly: false,
|
|
203
|
+
reason: 'no_commands',
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const batchTimeoutMs = Math.min(
|
|
208
|
+
safeNumber(options.batchTimeoutMs, DEFAULT_BATCH_TIMEOUT_MS),
|
|
209
|
+
DEFAULT_BATCH_TIMEOUT_MS * 3,
|
|
210
|
+
);
|
|
211
|
+
|
|
212
|
+
const sandboxDir = createSandboxDir();
|
|
213
|
+
const results = [];
|
|
214
|
+
let stoppedEarly = false;
|
|
215
|
+
|
|
216
|
+
try {
|
|
217
|
+
for (const cmd of cmds) {
|
|
218
|
+
const elapsed = Date.now() - startedAt;
|
|
219
|
+
if (elapsed >= batchTimeoutMs) {
|
|
220
|
+
stoppedEarly = true;
|
|
221
|
+
break;
|
|
222
|
+
}
|
|
223
|
+
const remaining = batchTimeoutMs - elapsed;
|
|
224
|
+
const r = await runSingleCommand(cmd, {
|
|
225
|
+
cwd: sandboxDir,
|
|
226
|
+
timeoutMs: Math.min(
|
|
227
|
+
safeNumber(options.cmdTimeoutMs, DEFAULT_CMD_TIMEOUT_MS),
|
|
228
|
+
remaining,
|
|
229
|
+
),
|
|
230
|
+
});
|
|
231
|
+
results.push(r);
|
|
232
|
+
if (!r.ok) {
|
|
233
|
+
stoppedEarly = true;
|
|
234
|
+
break;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
} finally {
|
|
238
|
+
if (!options.keepSandbox) cleanupDir(sandboxDir);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const overallOk = results.length > 0 && results.every((r) => r.ok) && !stoppedEarly;
|
|
242
|
+
|
|
243
|
+
return {
|
|
244
|
+
results,
|
|
245
|
+
overallOk,
|
|
246
|
+
sandboxDir: options.keepSandbox ? sandboxDir : null,
|
|
247
|
+
durationMs: Date.now() - startedAt,
|
|
248
|
+
stoppedEarly,
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
module.exports = {
|
|
253
|
+
runInSandbox,
|
|
254
|
+
runSingleCommand,
|
|
255
|
+
createSandboxDir,
|
|
256
|
+
cleanupDir,
|
|
257
|
+
buildSandboxEnv,
|
|
258
|
+
DEFAULT_CMD_TIMEOUT_MS,
|
|
259
|
+
MAX_CMD_TIMEOUT_MS,
|
|
260
|
+
DEFAULT_BATCH_TIMEOUT_MS,
|
|
261
|
+
MAX_OUTPUT_CHARS,
|
|
262
|
+
};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
// src/gep/validator/stakeBootstrap.js
|
|
2
|
+
//
|
|
3
|
+
// Ensures this node has an active validator stake on the Hub before it
|
|
4
|
+
// starts consuming validation tasks. Idempotent: repeated calls will not
|
|
5
|
+
// create duplicate stakes; the Hub returns the existing active stake.
|
|
6
|
+
'use strict';
|
|
7
|
+
|
|
8
|
+
const crypto = require('crypto');
|
|
9
|
+
const { buildHubHeaders, getHubUrl, getNodeId } = require('../a2aProtocol');
|
|
10
|
+
|
|
11
|
+
const DEFAULT_STAKE_AMOUNT = Number(process.env.EVOLVER_VALIDATOR_STAKE_AMOUNT) || 100;
|
|
12
|
+
const STAKE_TIMEOUT_MS = Number(process.env.EVOLVER_VALIDATOR_STAKE_TIMEOUT_MS) || 10_000;
|
|
13
|
+
const HUB_URL_FALLBACK = process.env.A2A_HUB_URL || process.env.EVOMAP_HUB_URL || 'https://evomap.ai';
|
|
14
|
+
|
|
15
|
+
function resolveHubUrl() {
|
|
16
|
+
try {
|
|
17
|
+
const u = getHubUrl && getHubUrl();
|
|
18
|
+
if (u && typeof u === 'string') return u;
|
|
19
|
+
} catch (_) {}
|
|
20
|
+
return HUB_URL_FALLBACK;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
let _lastAttemptAt = 0;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Attempt to stake credits so this node becomes eligible for validation tasks.
|
|
27
|
+
* Safe to call repeatedly; debounced to at most once per 5 minutes.
|
|
28
|
+
*
|
|
29
|
+
* @param {{ amount?: number, force?: boolean }} [opts]
|
|
30
|
+
*/
|
|
31
|
+
async function ensureValidatorStake(opts) {
|
|
32
|
+
const options = opts || {};
|
|
33
|
+
const now = Date.now();
|
|
34
|
+
if (!options.force && now - _lastAttemptAt < 5 * 60 * 1000) {
|
|
35
|
+
return { ok: true, skipped: 'debounced' };
|
|
36
|
+
}
|
|
37
|
+
_lastAttemptAt = now;
|
|
38
|
+
|
|
39
|
+
const nodeId = getNodeId();
|
|
40
|
+
if (!nodeId) return { ok: false, error: 'no_node_id' };
|
|
41
|
+
|
|
42
|
+
const hubUrl = resolveHubUrl();
|
|
43
|
+
const url = hubUrl.replace(/\/+$/, '') + '/a2a/validator/stake';
|
|
44
|
+
const amount = Math.max(100, Math.round(Number(options.amount) || DEFAULT_STAKE_AMOUNT));
|
|
45
|
+
|
|
46
|
+
const controller = new AbortController();
|
|
47
|
+
const timer = setTimeout(() => controller.abort(), STAKE_TIMEOUT_MS);
|
|
48
|
+
|
|
49
|
+
const body = {
|
|
50
|
+
sender_id: nodeId,
|
|
51
|
+
node_id: nodeId,
|
|
52
|
+
payload: { stake_amount: amount },
|
|
53
|
+
message_id: 'msg_' + Date.now().toString(36) + '_' + crypto.randomBytes(3).toString('hex'),
|
|
54
|
+
timestamp: new Date().toISOString(),
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
try {
|
|
58
|
+
const res = await fetch(url, {
|
|
59
|
+
method: 'POST',
|
|
60
|
+
headers: buildHubHeaders(),
|
|
61
|
+
body: JSON.stringify(body),
|
|
62
|
+
signal: controller.signal,
|
|
63
|
+
});
|
|
64
|
+
clearTimeout(timer);
|
|
65
|
+
const text = await res.text();
|
|
66
|
+
if (!res.ok) {
|
|
67
|
+
return { ok: false, status: res.status, error: text.slice(0, 400) };
|
|
68
|
+
}
|
|
69
|
+
let parsed;
|
|
70
|
+
try { parsed = JSON.parse(text); } catch { parsed = { raw: text }; }
|
|
71
|
+
return { ok: true, stake: parsed && parsed.stake ? parsed.stake : parsed };
|
|
72
|
+
} catch (err) {
|
|
73
|
+
clearTimeout(timer);
|
|
74
|
+
return { ok: false, error: err && err.message ? err.message : String(err) };
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
module.exports = {
|
|
79
|
+
ensureValidatorStake,
|
|
80
|
+
DEFAULT_STAKE_AMOUNT,
|
|
81
|
+
};
|
|
@@ -92,7 +92,7 @@ function autoHeal(skillName, issues) {
|
|
|
92
92
|
// Remove package-lock.json if it exists to prevent conflict errors
|
|
93
93
|
try { fs.unlinkSync(path.join(skillPath, 'package-lock.json')); } catch (e) {}
|
|
94
94
|
|
|
95
|
-
execSync('npm install --production --no-audit --no-fund', {
|
|
95
|
+
execSync('npm install --production --no-audit --no-fund --ignore-scripts', {
|
|
96
96
|
cwd: skillPath, stdio: 'ignore', timeout: 60000 // Increased timeout
|
|
97
97
|
});
|
|
98
98
|
healed.push(issues[i]);
|
|
@@ -107,7 +107,9 @@ class MailboxStore {
|
|
|
107
107
|
_persistState() {
|
|
108
108
|
const dir = path.dirname(this._stateFile);
|
|
109
109
|
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
110
|
-
|
|
110
|
+
const tmp = `${this._stateFile}.tmp`;
|
|
111
|
+
fs.writeFileSync(tmp, JSON.stringify(this._state, null, 2) + '\n', 'utf8');
|
|
112
|
+
fs.renameSync(tmp, this._stateFile);
|
|
111
113
|
}
|
|
112
114
|
|
|
113
115
|
_rebuildIndex() {
|