|
1 | 1 | #!/usr/bin/env node |
| 2 | +function _printProxyTokenUsage(out = process.stderr) { |
| 3 | + out.write('Usage: node index.js proxy-token [--settings FILE]\n'); |
| 4 | +} |
| 5 | + |
| 6 | +function _readProxyTokenFromSettingsFile(fs, settingsFile) { |
| 7 | + try { |
| 8 | + const parsed = JSON.parse(fs.readFileSync(settingsFile, 'utf8')); |
| 9 | + return parsed && parsed.proxy && typeof parsed.proxy.token === 'string' |
| 10 | + ? parsed.proxy.token |
| 11 | + : ''; |
| 12 | + } catch { |
| 13 | + return ''; |
| 14 | + } |
| 15 | +} |
| 16 | + |
| 17 | +// `proxy-token` is a credential helper for Codex. Handle it before loading any |
| 18 | +// project .env so a workspace cannot change EVOLVER_SETTINGS_DIR or other local |
| 19 | +// state used to find the proxy token. |
| 20 | +if (process.argv[2] === 'proxy-token') { |
| 21 | + try { |
| 22 | + const _fs = require('fs'); |
| 23 | + const _os = require('os'); |
| 24 | + const _path = require('path'); |
| 25 | + let settingsFile = ''; |
| 26 | + for (let i = 3; i < process.argv.length; i++) { |
| 27 | + const arg = process.argv[i]; |
| 28 | + if (arg === '-h' || arg === '--help') { |
| 29 | + _printProxyTokenUsage(process.stdout); |
| 30 | + process.exit(0); |
| 31 | + } |
| 32 | + if (arg === '--settings') { |
| 33 | + if (!process.argv[i + 1]) { |
| 34 | + _printProxyTokenUsage(); |
| 35 | + console.error('[proxy-token] missing value for --settings'); |
| 36 | + process.exit(2); |
| 37 | + } |
| 38 | + settingsFile = process.argv[i + 1]; |
| 39 | + i++; |
| 40 | + continue; |
| 41 | + } |
| 42 | + _printProxyTokenUsage(); |
| 43 | + console.error('[proxy-token] unknown argument'); |
| 44 | + process.exit(2); |
| 45 | + } |
| 46 | + const defaultSettingsFile = _path.join( |
| 47 | + process.env.EVOLVER_SETTINGS_DIR || _path.join(_os.homedir(), '.evolver'), |
| 48 | + 'settings.json', |
| 49 | + ); |
| 50 | + const token = _readProxyTokenFromSettingsFile(_fs, settingsFile || defaultSettingsFile); |
| 51 | + if (!token) { |
| 52 | + console.error('[proxy-token] no active proxy token found; start evolver with EVOMAP_PROXY=1 first'); |
| 53 | + process.exit(1); |
| 54 | + } |
| 55 | + process.stdout.write(token + '\n'); |
| 56 | + process.exit(0); |
| 57 | + } catch (e) { |
| 58 | + console.error('[proxy-token] Failed:', e && e.message || e); |
| 59 | + process.exit(1); |
| 60 | + } |
| 61 | +} |
| 62 | + |
2 | 63 | // Load .env BEFORE any internal require so that a2aProtocol and ATP |
3 | 64 | // modules see A2A_NODE_SECRET / A2A_NODE_ID / A2A_HUB_URL at first |
4 | 65 | // access and never fall back to a stale persisted/cached secret. |
@@ -625,7 +686,7 @@ async function main() { |
625 | 686 | // failure mode obvious. |
626 | 687 | try { |
627 | 688 | const { execSync } = require('child_process'); |
628 | | - execSync('git --version', { stdio: 'ignore', timeout: 5000 }); |
| 689 | + execSync('git --version', { stdio: 'ignore', timeout: 5000, windowsHide: true }); |
629 | 690 | } catch (_gitErr) { |
630 | 691 | console.error(''); |
631 | 692 | console.error('[Preflight] Could not run "git --version". Evolver requires git to be installed and available on PATH.'); |
@@ -1166,6 +1227,24 @@ async function main() { |
1166 | 1227 | const { registerMailboxTransport } = require('./src/gep/mailboxTransport'); |
1167 | 1228 | registerMailboxTransport(); |
1168 | 1229 | process.env.A2A_TRANSPORT = 'mailbox'; |
| 1230 | + try { |
| 1231 | + const a2a = require('./src/gep/a2aProtocol'); |
| 1232 | + a2a.startSystemdNotifyWatchdog(function () { |
| 1233 | + try { |
| 1234 | + const proxy = proxyInfo && proxyInfo.proxy; |
| 1235 | + const lifecycle = proxy && proxy.lifecycle; |
| 1236 | + if (lifecycle && typeof lifecycle.getHeartbeatStats === 'function') { |
| 1237 | + const stats = lifecycle.getHeartbeatStats(); |
| 1238 | + // Hub-backed lifecycle stats are authoritative even when stopped; |
| 1239 | + // systemd should starve and restart instead of seeing a false ping. |
| 1240 | + if (stats && (stats.running || proxy.hubUrl)) return stats; |
| 1241 | + } |
| 1242 | + } catch (_) {} |
| 1243 | + return { running: true, consecutiveFailures: 0, lastTickAt: Date.now() }; |
| 1244 | + }); |
| 1245 | + } catch (sdErr) { |
| 1246 | + console.warn('[Heartbeat] systemd notify/watchdog setup failed: ' + (sdErr && sdErr.message || sdErr)); |
| 1247 | + } |
1169 | 1248 | } else { |
1170 | 1249 | const a2a = require('./src/gep/a2aProtocol'); |
1171 | 1250 | try { a2a.startHeartbeat(); } |
@@ -1825,9 +1904,9 @@ async function main() { |
1825 | 1904 | const repoRoot = getRepoRoot(); |
1826 | 1905 | let diff = ''; |
1827 | 1906 | try { |
1828 | | - const unstaged = execSync('git diff', { cwd: repoRoot, encoding: 'utf8', timeout: 30000, maxBuffer: MAX_EXEC_BUFFER }).trim(); |
1829 | | - const staged = execSync('git diff --cached', { cwd: repoRoot, encoding: 'utf8', timeout: 30000, maxBuffer: MAX_EXEC_BUFFER }).trim(); |
1830 | | - const untracked = execSync('git ls-files --others --exclude-standard', { cwd: repoRoot, encoding: 'utf8', timeout: 10000, maxBuffer: MAX_EXEC_BUFFER }).trim(); |
| 1907 | + const unstaged = execSync('git diff', { cwd: repoRoot, encoding: 'utf8', timeout: 30000, maxBuffer: MAX_EXEC_BUFFER, windowsHide: true }).trim(); |
| 1908 | + const staged = execSync('git diff --cached', { cwd: repoRoot, encoding: 'utf8', timeout: 30000, maxBuffer: MAX_EXEC_BUFFER, windowsHide: true }).trim(); |
| 1909 | + const untracked = execSync('git ls-files --others --exclude-standard', { cwd: repoRoot, encoding: 'utf8', timeout: 10000, maxBuffer: MAX_EXEC_BUFFER, windowsHide: true }).trim(); |
1831 | 1910 | if (staged) diff += '=== Staged Changes ===\n' + staged + '\n\n'; |
1832 | 1911 | if (unstaged) diff += '=== Unstaged Changes ===\n' + unstaged + '\n\n'; |
1833 | 1912 | if (untracked) diff += '=== Untracked Files ===\n' + untracked + '\n'; |
@@ -1909,13 +1988,13 @@ async function main() { |
1909 | 1988 | } else if (args.includes('--reject')) { |
1910 | 1989 | console.log('\n[Review] Rejected. Rolling back changes...'); |
1911 | 1990 | try { |
1912 | | - execSync('git checkout -- .', { cwd: repoRoot, encoding: 'utf8', timeout: 30000, maxBuffer: MAX_EXEC_BUFFER }); |
| 1991 | + execSync('git checkout -- .', { cwd: repoRoot, encoding: 'utf8', timeout: 30000, maxBuffer: MAX_EXEC_BUFFER, windowsHide: true }); |
1913 | 1992 | // Preserve user state on reject: .env files, node_modules, runtime |
1914 | 1993 | // PID files, and a dedicated workspace/ dir (if one exists) MUST NOT |
1915 | 1994 | // be wiped by an automated rollback. Users have reported losing |
1916 | 1995 | // secrets and runtime caches to an aggressive git clean. |
1917 | 1996 | execSync('git clean -fd -e node_modules -e workspace -e .env -e ".env.*" -e "*.pid"', { |
1918 | | - cwd: repoRoot, encoding: 'utf8', timeout: 30000, maxBuffer: MAX_EXEC_BUFFER, |
| 1997 | + cwd: repoRoot, encoding: 'utf8', timeout: 30000, maxBuffer: MAX_EXEC_BUFFER, windowsHide: true, |
1919 | 1998 | }); |
1920 | 1999 | const evolDir = getEvolutionDir(); |
1921 | 2000 | const sp = path.join(evolDir, 'evolution_solidify_state.json'); |
@@ -2922,10 +3001,40 @@ async function main() { |
2922 | 3001 | process.exit(1); |
2923 | 3002 | } |
2924 | 3003 |
|
| 3004 | + } else if (command === 'experiment') { |
| 3005 | + // Comparative experiment runner: run the SAME task twice -- a baseline arm |
| 3006 | + // and a variant arm that reuses a gene's strategy -- via a headless agent |
| 3007 | + // CLI, collect duration/rounds/tokens/pass-rate, and print a comparison |
| 3008 | + // JSON to stdout. Consumed by EvoMap Desktop's ExperimentsAPI.Run, which |
| 3009 | + // spawns `node index.js experiment --request-file=<json>` and parses stdout. |
| 3010 | + try { |
| 3011 | + const expCli = require('./src/experiment/cli'); |
| 3012 | + const parsed = expCli.parseExperimentArgs(args.slice(1)); |
| 3013 | + if (!parsed.ok) { |
| 3014 | + console.error('[Experiment] ' + parsed.error); |
| 3015 | + console.error(expCli.printExperimentUsage()); |
| 3016 | + process.exit(2); |
| 3017 | + } |
| 3018 | + const res = await expCli.runExperiment(parsed.opts, { err: (...a) => console.error(...a) }); |
| 3019 | + // stdout carries ONLY the structured JSON so the Go caller can JSON.parse |
| 3020 | + // it without log contamination; all logging above went to stderr. res.data |
| 3021 | + // is already secret-redacted by runExperiment (sanitizePayload). |
| 3022 | + if (res && res.data) process.stdout.write(JSON.stringify(res.data) + '\n'); |
| 3023 | + process.exit(res && typeof res.exitCode === 'number' ? res.exitCode : (res && res.ok ? 0 : 1)); |
| 3024 | + } catch (expErr) { |
| 3025 | + console.error('[Experiment] CLI error:', expErr && expErr.message || expErr); |
| 3026 | + process.exit(1); |
| 3027 | + } |
| 3028 | + |
2925 | 3029 | } else { |
2926 | | - console.log(`Usage: node index.js [run|/evolve|login|logout|solidify|review|distill|fetch|sync|asset-log|webui|setup-hooks|recipe|buy|orders|verify|atp|atp-complete] [--loop] |
| 3030 | + console.log(`Usage: node index.js [run|/evolve|login|logout|proxy-token|solidify|review|distill|fetch|sync|asset-log|webui|setup-hooks|recipe|buy|orders|verify|atp|atp-complete|experiment] [--loop] |
2927 | 3031 | - login (authorize this device via the hub, gh-auth-login style; stores an OAuth token used instead of node_secret) |
2928 | 3032 | - logout (remove the stored OAuth token) |
| 3033 | + - proxy-token (print the local proxy bearer token for command-backed client auth) |
| 3034 | + - experiment flags: |
| 3035 | + - --task="..." --metric="..." (required; same task, baseline vs variant) |
| 3036 | + - --gene=<geneId> (variant arm reuses this gene's strategy) |
| 3037 | + - --baseline="..." --variant="..." --validation="c1;;c2" --request-file=<json> |
2929 | 3038 | - recipe flags: |
2930 | 3039 | - build --title="..." --genes=<asset_id,...> [--description] [--price=N] [--publish] |
2931 | 3040 | (builds a DRAFT DNA blueprint; --publish is opt-in) |
|
0 commit comments