@evomap/evolver 1.89.14 → 1.89.17
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/index.js +219 -29
- package/package.json +1 -1
- package/src/adapters/claudeCode.js +2 -2
- package/src/adapters/codex.js +2 -2
- package/src/adapters/hookAdapter.js +14 -2
- package/src/adapters/scripts/evolver-session-start.js +182 -2
- package/src/config.js +11 -3
- package/src/evolve/guards.js +1 -1
- package/src/evolve/pipeline/collect.js +1 -1
- package/src/evolve/pipeline/dispatch.js +1 -1
- package/src/evolve/pipeline/enrich.js +1 -1
- package/src/evolve/pipeline/hub.js +1 -1
- package/src/evolve/pipeline/select.js +1 -1
- package/src/evolve/pipeline/signals.js +1 -1
- package/src/evolve/utils.js +1 -1
- package/src/evolve.js +1 -1
- package/src/forceUpdate.js +499 -119
- package/src/gep/a2aProtocol.js +1 -1
- package/src/gep/antiAbuseTelemetry.js +1 -1
- package/src/gep/autoDistillConv.js +1 -1
- package/src/gep/autoDistillLlm.js +1 -1
- package/src/gep/candidateEval.js +1 -1
- package/src/gep/candidates.js +1 -1
- package/src/gep/cliContracts.js +1154 -0
- package/src/gep/contentHash.js +1 -1
- package/src/gep/conversationDistiller.js +1 -1
- package/src/gep/conversationSniffer.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/epigenetics.js +1 -1
- package/src/gep/execBridge.js +1 -1
- package/src/gep/explore.js +1 -1
- package/src/gep/hash.js +1 -1
- package/src/gep/hostErrorClassifier.js +34 -0
- package/src/gep/hubFetch.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/issueReporter.js +87 -0
- 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/openPRRegistry.js +1 -1
- package/src/gep/paths.js +20 -0
- package/src/gep/personality.js +1 -1
- package/src/gep/policyCheck.js +1 -1
- package/src/gep/prompt.js +1 -1
- package/src/gep/recallInject.js +1 -1
- package/src/gep/recallVerifier.js +1 -1
- package/src/gep/reflection.js +1 -1
- package/src/gep/sanitize.js +20 -4
- package/src/gep/savingsCore.js +1 -1
- package/src/gep/selector.js +1 -1
- package/src/gep/signals.js +70 -24
- package/src/gep/skillDistiller.js +1 -1
- package/src/gep/solidify.js +1 -1
- package/src/gep/strategy.js +1 -1
- package/src/gep/tokenSavings.js +1 -1
- package/src/gep/workspaceKeychain.js +1 -1
- package/src/ops/lifecycle.js +501 -31
- package/src/proxy/extensions/traceControl.js +1 -1
- package/src/proxy/index.js +4 -4
- package/src/proxy/inject.js +1 -1
- package/src/proxy/lifecycle/manager.js +233 -33
- package/src/proxy/sync/inbound.js +5 -4
- package/src/proxy/sync/outbound.js +3 -2
- package/src/proxy/trace/extractor.js +1 -1
- package/src/proxy/trace/usage.js +1 -1
package/index.js
CHANGED
|
@@ -1,4 +1,150 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
function _parseBootstrapSemver(version) {
|
|
3
|
+
const match = /^v?(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/.exec(String(version || ''));
|
|
4
|
+
if (!match) return null;
|
|
5
|
+
return {
|
|
6
|
+
major: match[1],
|
|
7
|
+
minor: match[2],
|
|
8
|
+
patch: match[3],
|
|
9
|
+
prerelease: match[4] ? match[4].split('.') : [],
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function _compareBootstrapNumeric(left, right) {
|
|
14
|
+
if (left.length !== right.length) return left.length - right.length;
|
|
15
|
+
if (left < right) return -1;
|
|
16
|
+
if (left > right) return 1;
|
|
17
|
+
return 0;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function _compareBootstrapPrerelease(left, right) {
|
|
21
|
+
const leftNumeric = /^\d+$/.test(left);
|
|
22
|
+
const rightNumeric = /^\d+$/.test(right);
|
|
23
|
+
if (leftNumeric && rightNumeric) return _compareBootstrapNumeric(left, right);
|
|
24
|
+
if (leftNumeric) return -1;
|
|
25
|
+
if (rightNumeric) return 1;
|
|
26
|
+
if (left < right) return -1;
|
|
27
|
+
if (left > right) return 1;
|
|
28
|
+
return 0;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function _compareBootstrapSemver(left, right) {
|
|
32
|
+
const a = _parseBootstrapSemver(left);
|
|
33
|
+
const b = _parseBootstrapSemver(right);
|
|
34
|
+
if (!a || !b) return null;
|
|
35
|
+
for (const key of ['major', 'minor', 'patch']) {
|
|
36
|
+
const cmp = _compareBootstrapNumeric(a[key], b[key]);
|
|
37
|
+
if (cmp !== 0) return cmp;
|
|
38
|
+
}
|
|
39
|
+
if (!a.prerelease.length && !b.prerelease.length) return 0;
|
|
40
|
+
if (!a.prerelease.length) return 1;
|
|
41
|
+
if (!b.prerelease.length) return -1;
|
|
42
|
+
const max = Math.max(a.prerelease.length, b.prerelease.length);
|
|
43
|
+
for (let i = 0; i < max; i++) {
|
|
44
|
+
if (a.prerelease[i] === undefined) return -1;
|
|
45
|
+
if (b.prerelease[i] === undefined) return 1;
|
|
46
|
+
const cmp = _compareBootstrapPrerelease(a.prerelease[i], b.prerelease[i]);
|
|
47
|
+
if (cmp !== 0) return cmp;
|
|
48
|
+
}
|
|
49
|
+
return 0;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function _bootstrapVersionSatisfies(currentVersion, requiredVersion) {
|
|
53
|
+
if (String(currentVersion || '') === String(requiredVersion || '')) return true;
|
|
54
|
+
const cmp = _compareBootstrapSemver(currentVersion, requiredVersion);
|
|
55
|
+
return cmp !== null && cmp >= 0;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function _failClosedForceUpdateBootstrap(backupName, entryName, error) {
|
|
59
|
+
const detail = error && error.message ? error.message : String(error || 'unknown error');
|
|
60
|
+
console.error('[ForceUpdate] Bootstrap recovery failed for ' + backupName +
|
|
61
|
+
(entryName ? ' while restoring ' + entryName : '') + ': ' + detail);
|
|
62
|
+
console.error('[ForceUpdate] Refusing to continue startup; recovery backup and journal were left in place.');
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function _recoverInterruptedForceUpdateBootstrap() {
|
|
67
|
+
const fs = require('fs');
|
|
68
|
+
const path = require('path');
|
|
69
|
+
const installRoot = __dirname;
|
|
70
|
+
const backupPrefix = '.evolver-force-update-backup-';
|
|
71
|
+
const journalName = '.evolver-force-update-journal.json';
|
|
72
|
+
let backups = [];
|
|
73
|
+
try {
|
|
74
|
+
backups = fs.readdirSync(installRoot)
|
|
75
|
+
.filter((name) => name.startsWith(backupPrefix))
|
|
76
|
+
.sort()
|
|
77
|
+
.reverse();
|
|
78
|
+
} catch (_) {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
for (const backupName of backups) {
|
|
82
|
+
const backupRoot = path.join(installRoot, backupName);
|
|
83
|
+
const journalPath = path.join(backupRoot, journalName);
|
|
84
|
+
let journal = null;
|
|
85
|
+
try {
|
|
86
|
+
journal = JSON.parse(fs.readFileSync(journalPath, 'utf8'));
|
|
87
|
+
} catch (_) {
|
|
88
|
+
// Not a force-update recovery journal; leave unrelated directories alone.
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
if (!journal || journal.state !== 'precommit' || !journal.requiredVersion) continue;
|
|
92
|
+
let currentVersion = '';
|
|
93
|
+
try {
|
|
94
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(installRoot, 'package.json'), 'utf8'));
|
|
95
|
+
currentVersion = pkg && pkg.version ? String(pkg.version) : '';
|
|
96
|
+
} catch (_) {
|
|
97
|
+
// If the package marker is unreadable, prefer restoring the old payload.
|
|
98
|
+
}
|
|
99
|
+
if (_bootstrapVersionSatisfies(currentVersion, String(journal.requiredVersion))) {
|
|
100
|
+
try { fs.rmSync(backupRoot, { recursive: true, force: true }); } catch (_) {
|
|
101
|
+
// Cleanup failure is non-fatal; normal startup can continue.
|
|
102
|
+
}
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
let entries = [];
|
|
106
|
+
try {
|
|
107
|
+
entries = fs.readdirSync(backupRoot, { withFileTypes: true });
|
|
108
|
+
} catch (readErr) {
|
|
109
|
+
_failClosedForceUpdateBootstrap(backupName, '', readErr);
|
|
110
|
+
}
|
|
111
|
+
for (const entry of entries) {
|
|
112
|
+
if (entry.name === journalName) continue;
|
|
113
|
+
const livePath = path.join(installRoot, entry.name);
|
|
114
|
+
const backupPath = path.join(backupRoot, entry.name);
|
|
115
|
+
try {
|
|
116
|
+
if (entry.name === 'index.js') {
|
|
117
|
+
const tmpPath = livePath + '.' + process.pid + '.recover-tmp';
|
|
118
|
+
try { fs.rmSync(tmpPath, { force: true }); } catch (_) {}
|
|
119
|
+
fs.copyFileSync(backupPath, tmpPath);
|
|
120
|
+
try {
|
|
121
|
+
const backupStat = fs.statSync(backupPath);
|
|
122
|
+
fs.chmodSync(tmpPath, backupStat.mode & 0o777);
|
|
123
|
+
} catch (_) {
|
|
124
|
+
// Recovery can proceed without mode restoration; npm/service launches
|
|
125
|
+
// normally use `node index.js` after an interrupted update.
|
|
126
|
+
}
|
|
127
|
+
fs.renameSync(tmpPath, livePath);
|
|
128
|
+
fs.rmSync(backupPath, { force: true });
|
|
129
|
+
} else {
|
|
130
|
+
fs.rmSync(livePath, { recursive: true, force: true });
|
|
131
|
+
fs.renameSync(backupPath, livePath);
|
|
132
|
+
}
|
|
133
|
+
} catch (restoreErr) {
|
|
134
|
+
_failClosedForceUpdateBootstrap(backupName, entry.name, restoreErr);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
try { fs.rmSync(backupRoot, { recursive: true, force: true }); } catch (_) {
|
|
138
|
+
// Best-effort cleanup; recovery already restored the payload.
|
|
139
|
+
}
|
|
140
|
+
console.warn('[ForceUpdate] Recovered interrupted install from ' + backupName);
|
|
141
|
+
return true;
|
|
142
|
+
}
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
_recoverInterruptedForceUpdateBootstrap();
|
|
147
|
+
|
|
2
148
|
function _printProxyTokenUsage(out = process.stderr) {
|
|
3
149
|
out.write('Usage: node index.js proxy-token [--settings FILE]\n');
|
|
4
150
|
}
|
|
@@ -2716,8 +2862,9 @@ async function main() {
|
|
|
2716
2862
|
// Wipe every local store of node_secret in one shot, so a daemon stuck
|
|
2717
2863
|
// after a manual web reset (https://evomap.ai/account -> Reset Secret)
|
|
2718
2864
|
// can boot clean. Local stores involved:
|
|
2719
|
-
// - MailboxStore: ~/.evomap/mailbox/state.json
|
|
2720
|
-
// - Legacy files: ~/.evomap/node_secret
|
|
2865
|
+
// - MailboxStore: ~/.evomap/mailbox/state.json node_secret state keys
|
|
2866
|
+
// - Legacy files: ~/.evomap/node_secret, node_secret_version,
|
|
2867
|
+
// node_secret_source, and node_secret_env_suppressed
|
|
2721
2868
|
// - Shell env: A2A_NODE_SECRET / EVOMAP_NODE_SECRET and matching
|
|
2722
2869
|
// version vars (we cannot mutate the parent shell; we
|
|
2723
2870
|
// just print the unset hint)
|
|
@@ -2730,38 +2877,47 @@ async function main() {
|
|
|
2730
2877
|
// this fallback, test/resetLocalSecret.test.js cannot inject a fake home
|
|
2731
2878
|
// and the reset operates on the real user dir.
|
|
2732
2879
|
const home = process.env.HOME || os.homedir();
|
|
2733
|
-
const
|
|
2734
|
-
const
|
|
2735
|
-
const
|
|
2880
|
+
const evomapDirs = [];
|
|
2881
|
+
const seenEvomapDirs = new Set();
|
|
2882
|
+
const addEvomapDir = (dir) => {
|
|
2883
|
+
if (!dir) return;
|
|
2884
|
+
const resolved = path.resolve(dir);
|
|
2885
|
+
if (seenEvomapDirs.has(resolved)) return;
|
|
2886
|
+
seenEvomapDirs.add(resolved);
|
|
2887
|
+
evomapDirs.push(dir);
|
|
2888
|
+
};
|
|
2889
|
+
addEvomapDir(path.join(home, '.evomap'));
|
|
2890
|
+
addEvomapDir(process.env.EVOLVER_HOME);
|
|
2736
2891
|
let cleared = 0;
|
|
2737
2892
|
try {
|
|
2738
|
-
|
|
2739
|
-
const
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2893
|
+
for (const evomapDir of evomapDirs) {
|
|
2894
|
+
const stateFile = path.join(evomapDir, 'mailbox', 'state.json');
|
|
2895
|
+
if (fs.existsSync(stateFile)) {
|
|
2896
|
+
const raw = JSON.parse(fs.readFileSync(stateFile, 'utf8'));
|
|
2897
|
+
let mutated = false;
|
|
2898
|
+
for (const k of ['node_secret', 'node_secret_source', 'node_secret_version', 'node_secret_env_suppressed']) {
|
|
2899
|
+
if (raw[k] !== undefined && raw[k] !== '') {
|
|
2900
|
+
raw[k] = '';
|
|
2901
|
+
mutated = true;
|
|
2902
|
+
}
|
|
2903
|
+
}
|
|
2904
|
+
if (mutated) {
|
|
2905
|
+
fs.writeFileSync(stateFile, JSON.stringify(raw, null, 2) + '\n', 'utf8');
|
|
2906
|
+
cleared += 1;
|
|
2907
|
+
console.log('[reset-local-secret] cleared MailboxStore at ' + stateFile);
|
|
2908
|
+
} else {
|
|
2909
|
+
console.log('[reset-local-secret] MailboxStore had no node_secret to clear at ' + stateFile);
|
|
2745
2910
|
}
|
|
2746
2911
|
}
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2912
|
+
for (const legacyName of ['node_secret', 'node_secret_version', 'node_secret_source', 'node_secret_env_suppressed']) {
|
|
2913
|
+
const legacyFile = path.join(evomapDir, legacyName);
|
|
2914
|
+
if (fs.existsSync(legacyFile)) {
|
|
2915
|
+
fs.unlinkSync(legacyFile);
|
|
2916
|
+
cleared += 1;
|
|
2917
|
+
console.log('[reset-local-secret] removed legacy file ' + legacyFile);
|
|
2918
|
+
}
|
|
2753
2919
|
}
|
|
2754
2920
|
}
|
|
2755
|
-
if (fs.existsSync(legacyFile)) {
|
|
2756
|
-
fs.unlinkSync(legacyFile);
|
|
2757
|
-
cleared += 1;
|
|
2758
|
-
console.log('[reset-local-secret] removed legacy file ' + legacyFile);
|
|
2759
|
-
}
|
|
2760
|
-
if (fs.existsSync(legacyVersionFile)) {
|
|
2761
|
-
fs.unlinkSync(legacyVersionFile);
|
|
2762
|
-
cleared += 1;
|
|
2763
|
-
console.log('[reset-local-secret] removed legacy file ' + legacyVersionFile);
|
|
2764
|
-
}
|
|
2765
2921
|
} catch (err) {
|
|
2766
2922
|
console.error('[reset-local-secret] error:', err && err.message || err);
|
|
2767
2923
|
process.exit(1);
|
|
@@ -2875,6 +3031,35 @@ async function main() {
|
|
|
2875
3031
|
process.exit(1);
|
|
2876
3032
|
}
|
|
2877
3033
|
|
|
3034
|
+
} else if (command === 'reuse') {
|
|
3035
|
+
try {
|
|
3036
|
+
const { runReuseCommand } = require('./src/gep/cliContracts');
|
|
3037
|
+
process.exit(await runReuseCommand(args.slice(1)));
|
|
3038
|
+
} catch (e) {
|
|
3039
|
+
process.stdout.write(JSON.stringify({
|
|
3040
|
+
ok: false,
|
|
3041
|
+
contract: 'reuse.v1',
|
|
3042
|
+
reason: 'internal_error',
|
|
3043
|
+
message: 'evolver reuse failed',
|
|
3044
|
+
}) + '\n');
|
|
3045
|
+
process.exit(1);
|
|
3046
|
+
}
|
|
3047
|
+
|
|
3048
|
+
} else if (command === 'publish') {
|
|
3049
|
+
try {
|
|
3050
|
+
const { runPublishCommand } = require('./src/gep/cliContracts');
|
|
3051
|
+
process.exit(await runPublishCommand(args.slice(1)));
|
|
3052
|
+
} catch (e) {
|
|
3053
|
+
process.stdout.write(JSON.stringify({
|
|
3054
|
+
ok: false,
|
|
3055
|
+
contract: 'publish.v1',
|
|
3056
|
+
reason: 'internal_error',
|
|
3057
|
+
retryable: false,
|
|
3058
|
+
message: 'evolver publish failed',
|
|
3059
|
+
}) + '\n');
|
|
3060
|
+
process.exit(1);
|
|
3061
|
+
}
|
|
3062
|
+
|
|
2878
3063
|
} else if (command === 'recipe') {
|
|
2879
3064
|
// recipe build — assemble a DNA blueprint from owned Gene/Capsule assets
|
|
2880
3065
|
// recipe reuse — fetch + express an existing recipe into an organism
|
|
@@ -3042,7 +3227,7 @@ async function main() {
|
|
|
3042
3227
|
}
|
|
3043
3228
|
|
|
3044
3229
|
} else {
|
|
3045
|
-
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]
|
|
3230
|
+
console.log(`Usage: node index.js [run|/evolve|login|logout|proxy-token|solidify|review|distill|fetch|sync|asset-log|webui|setup-hooks|reuse|publish|recipe|buy|orders|verify|atp|atp-complete|experiment] [--loop]
|
|
3046
3231
|
- login (authorize this device via the hub, gh-auth-login style; stores an OAuth token used instead of node_secret)
|
|
3047
3232
|
- logout (remove the stored OAuth token)
|
|
3048
3233
|
- proxy-token (print the local proxy bearer token for command-backed client auth)
|
|
@@ -3054,6 +3239,11 @@ async function main() {
|
|
|
3054
3239
|
- build --title="..." --genes=<asset_id,...> [--description] [--price=N] [--publish]
|
|
3055
3240
|
(builds a DRAFT DNA blueprint; --publish is opt-in)
|
|
3056
3241
|
- reuse --id=<recipe_id> [--input=<json>] (express a recipe into an organism)
|
|
3242
|
+
- reuse flags:
|
|
3243
|
+
- --id=<asset_id> --json (reuse a Hub asset into the local library; stdout JSON contract reuse.v1)
|
|
3244
|
+
- publish flags:
|
|
3245
|
+
- --asset=<id|path> [--asset ...] [--dry-run] --json
|
|
3246
|
+
(publish.v1 stdout JSON contract for Desktop)
|
|
3057
3247
|
- fetch flags:
|
|
3058
3248
|
- --skill=<id> | -s <id> (skill ID to download)
|
|
3059
3249
|
- --out=<dir> (output directory, default: ./skills/<skill_id>)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@evomap/evolver",
|
|
3
|
-
"version": "1.89.
|
|
3
|
+
"version": "1.89.17",
|
|
4
4
|
"description": "A GEP-powered self-evolution engine for AI agents. Features automated log analysis and Genome Evolution Protocol (GEP) for auditable, reusable evolution assets.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
|
-
const { mergeJsonFile, copyHookScripts, appendSectionToFile, removeHookScripts, removeMarkedSection, assertSafeConfigDir } = require('./hookAdapter');
|
|
3
|
+
const { mergeJsonFile, copyHookScripts, appendSectionToFile, removeHookScripts, removeMarkedSection, assertSafeConfigDir, isEvolverHookCommand } = require('./hookAdapter');
|
|
4
4
|
|
|
5
5
|
const HOOK_SCRIPTS_DIR_NAME = 'hooks';
|
|
6
6
|
const EVOLVER_MARKER = '<!-- evolver-evolution-memory -->';
|
|
@@ -146,7 +146,7 @@ function uninstall({ configRoot }) {
|
|
|
146
146
|
const innerBefore = matcher.hooks.length;
|
|
147
147
|
const filtered = matcher.hooks.filter(h => {
|
|
148
148
|
const cmd = (h && h.command) || '';
|
|
149
|
-
return !
|
|
149
|
+
return !isEvolverHookCommand(cmd);
|
|
150
150
|
});
|
|
151
151
|
// A matcher containing both evolver and user hooks shrinks
|
|
152
152
|
// its inner array without changing the outer matcher count.
|
package/src/adapters/codex.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
|
-
const { mergeJsonFile, copyHookScripts, appendSectionToFile, removeHookScripts, removeMarkedSection, assertSafeConfigDir } = require('./hookAdapter');
|
|
3
|
+
const { mergeJsonFile, copyHookScripts, appendSectionToFile, removeHookScripts, removeMarkedSection, assertSafeConfigDir, isEvolverHookCommand } = require('./hookAdapter');
|
|
4
4
|
|
|
5
5
|
const HOOK_SCRIPTS_DIR_NAME = 'hooks';
|
|
6
6
|
const EVOLVER_MARKER = '<!-- evolver-evolution-memory -->';
|
|
@@ -166,7 +166,7 @@ function uninstall({ configRoot }) {
|
|
|
166
166
|
const before = data.hooks[event].length;
|
|
167
167
|
data.hooks[event] = data.hooks[event].filter(h => {
|
|
168
168
|
const cmd = (h && h.command) || '';
|
|
169
|
-
return !
|
|
169
|
+
return !isEvolverHookCommand(cmd);
|
|
170
170
|
});
|
|
171
171
|
if (data.hooks[event].length !== before) touched = true;
|
|
172
172
|
if (data.hooks[event].length === 0) delete data.hooks[event];
|
|
@@ -76,7 +76,7 @@ function mergeWithHooksUnion(target, source) {
|
|
|
76
76
|
if (tArr && sArr) {
|
|
77
77
|
const isEvolverOwned = (entry) => {
|
|
78
78
|
const cmds = collectCommands(entry);
|
|
79
|
-
return cmds.some(
|
|
79
|
+
return cmds.some(isEvolverHookCommand);
|
|
80
80
|
};
|
|
81
81
|
const userEntries = tArr.filter(e => !isEvolverOwned(e));
|
|
82
82
|
result.hooks[event] = [...userEntries, ...sArr];
|
|
@@ -101,6 +101,17 @@ function collectCommands(entry) {
|
|
|
101
101
|
return out;
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
+
function isEvolverHookCommand(command) {
|
|
105
|
+
if (typeof command !== 'string') return false;
|
|
106
|
+
return command.includes('evolver-session') ||
|
|
107
|
+
command.includes('evolver-signal') ||
|
|
108
|
+
command.includes('evolver-task-recall') ||
|
|
109
|
+
// Legacy installs briefly shipped this companion daemon hook. Treat it
|
|
110
|
+
// as evolver-owned so reinstall/merge can remove it instead of preserving
|
|
111
|
+
// a stale supervisor that may point clients at a dead proxy.
|
|
112
|
+
command.includes('evolver-daemon-start');
|
|
113
|
+
}
|
|
114
|
+
|
|
104
115
|
function deepMerge(target, source) {
|
|
105
116
|
const result = { ...target };
|
|
106
117
|
for (const key of Object.keys(source)) {
|
|
@@ -226,7 +237,7 @@ function removeEvolverHooks(filePath, { markerKey = '_evolver_managed' } = {}) {
|
|
|
226
237
|
const before = data.hooks[event].length;
|
|
227
238
|
data.hooks[event] = data.hooks[event].filter(h => {
|
|
228
239
|
const cmd = h.command || '';
|
|
229
|
-
return !
|
|
240
|
+
return !isEvolverHookCommand(cmd);
|
|
230
241
|
});
|
|
231
242
|
if (data.hooks[event].length !== before) changed = true;
|
|
232
243
|
if (data.hooks[event].length === 0) delete data.hooks[event];
|
|
@@ -358,6 +369,7 @@ module.exports = {
|
|
|
358
369
|
deepMerge,
|
|
359
370
|
mergeWithHooksUnion,
|
|
360
371
|
collectCommands,
|
|
372
|
+
isEvolverHookCommand,
|
|
361
373
|
copyHookScripts,
|
|
362
374
|
appendSectionToFile,
|
|
363
375
|
assertSafeConfigDir,
|
|
@@ -14,6 +14,180 @@ const { filterRelevantOutcomes } = require('./_memoryFiltering');
|
|
|
14
14
|
// _maybeRestartDaemon's catch-all and silently disable daemon auto-restart.
|
|
15
15
|
const lockPaths = require('./_lockPaths');
|
|
16
16
|
|
|
17
|
+
function _readJson(file) {
|
|
18
|
+
try {
|
|
19
|
+
if (!file || !fs.existsSync(file)) return null;
|
|
20
|
+
return JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
21
|
+
} catch (_) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function _isLoopbackProxyUrl(value) {
|
|
27
|
+
const raw = String(value || '').trim();
|
|
28
|
+
if (!raw) return false;
|
|
29
|
+
try {
|
|
30
|
+
const u = new URL(raw);
|
|
31
|
+
const host = u.hostname.toLowerCase();
|
|
32
|
+
return u.protocol === 'http:' && (host === '127.0.0.1' || host === 'localhost' || host === '::1' || host === '[::1]');
|
|
33
|
+
} catch (_) {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function _stripTomlComment(line) {
|
|
39
|
+
let out = '';
|
|
40
|
+
let quote = null;
|
|
41
|
+
let escaped = false;
|
|
42
|
+
for (const ch of String(line || '')) {
|
|
43
|
+
if (escaped) {
|
|
44
|
+
out += ch;
|
|
45
|
+
escaped = false;
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
if (ch === '\\' && quote === '"') {
|
|
49
|
+
out += ch;
|
|
50
|
+
escaped = true;
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
if ((ch === '"' || ch === "'") && !quote) {
|
|
54
|
+
quote = ch;
|
|
55
|
+
out += ch;
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
if (ch === quote) {
|
|
59
|
+
quote = null;
|
|
60
|
+
out += ch;
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
if (ch === '#' && !quote) break;
|
|
64
|
+
out += ch;
|
|
65
|
+
}
|
|
66
|
+
return out.trim();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function _tomlStringValue(value) {
|
|
70
|
+
const raw = _stripTomlComment(value);
|
|
71
|
+
const match = raw.match(/^(['"])([\s\S]*)\1$/);
|
|
72
|
+
return match ? match[2] : raw.trim();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function _codexConfigPath() {
|
|
76
|
+
if (process.env.CODEX_CONFIG_FILE || process.env.EVOMAP_CODEX_CONFIG_FILE) {
|
|
77
|
+
return process.env.CODEX_CONFIG_FILE || process.env.EVOMAP_CODEX_CONFIG_FILE;
|
|
78
|
+
}
|
|
79
|
+
const home = process.env.HOME || os.homedir();
|
|
80
|
+
return home ? path.join(home, '.codex', 'config.toml') : null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function _codexConfigExpectsProxy() {
|
|
84
|
+
const file = _codexConfigPath();
|
|
85
|
+
if (!file || !fs.existsSync(file)) return false;
|
|
86
|
+
let selectedProvider = null;
|
|
87
|
+
let section = '';
|
|
88
|
+
const providerUrls = {};
|
|
89
|
+
try {
|
|
90
|
+
const content = fs.readFileSync(file, 'utf8');
|
|
91
|
+
for (const line of content.split(/\r?\n/)) {
|
|
92
|
+
const clean = _stripTomlComment(line);
|
|
93
|
+
if (!clean) continue;
|
|
94
|
+
const sectionMatch = clean.match(/^\[([^\]]+)\]$/);
|
|
95
|
+
if (sectionMatch) {
|
|
96
|
+
section = sectionMatch[1].trim();
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
const kv = clean.match(/^([A-Za-z0-9_.-]+)\s*=\s*([\s\S]+)$/);
|
|
100
|
+
if (!kv) continue;
|
|
101
|
+
const key = kv[1].trim();
|
|
102
|
+
const value = _tomlStringValue(kv[2]);
|
|
103
|
+
if (!section && key === 'model_provider') {
|
|
104
|
+
selectedProvider = value;
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
const providerMatch = section.match(/^model_providers\.([A-Za-z0-9_.-]+)$/);
|
|
108
|
+
if (providerMatch && key === 'base_url') {
|
|
109
|
+
providerUrls[providerMatch[1]] = value;
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
if (!section && key === 'base_url' && _isLoopbackProxyUrl(value)) return true;
|
|
113
|
+
}
|
|
114
|
+
} catch {
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
if (selectedProvider && _isLoopbackProxyUrl(providerUrls[selectedProvider])) return true;
|
|
118
|
+
return Object.keys(providerUrls).some(name =>
|
|
119
|
+
/(?:evomap|proxy)/i.test(name) && _isLoopbackProxyUrl(providerUrls[name])
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function _proxyExpected() {
|
|
124
|
+
if (String(process.env.EVOMAP_PROXY || '').trim() === '1') return true;
|
|
125
|
+
if (String(process.env.A2A_TRANSPORT || '').trim().toLowerCase() === 'mailbox') return true;
|
|
126
|
+
if (_isLoopbackProxyUrl(process.env.EVOMAP_PROXY_URL) || _isLoopbackProxyUrl(process.env.ANTHROPIC_BASE_URL)) return true;
|
|
127
|
+
if (_codexConfigExpectsProxy()) return true;
|
|
128
|
+
|
|
129
|
+
const home = process.env.HOME || os.homedir();
|
|
130
|
+
const settingsFile = process.env.CLAUDE_SETTINGS_FILE || process.env.EVOMAP_CLAUDE_SETTINGS_FILE ||
|
|
131
|
+
(home ? path.join(home, '.claude', 'settings.json') : null);
|
|
132
|
+
const settings = _readJson(settingsFile);
|
|
133
|
+
const cfg = settings && settings.env;
|
|
134
|
+
return !!(cfg && (
|
|
135
|
+
_isLoopbackProxyUrl(cfg.EVOMAP_PROXY_URL) ||
|
|
136
|
+
(String(cfg.EVOMAP_PROXY_AUTO_INJECTED || '') === '1' && _isLoopbackProxyUrl(cfg.ANTHROPIC_BASE_URL))
|
|
137
|
+
));
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function _proxyReachable(url, token) {
|
|
141
|
+
if (!_isLoopbackProxyUrl(url) || !token) return false;
|
|
142
|
+
try {
|
|
143
|
+
const { execFileSync } = require('child_process');
|
|
144
|
+
execFileSync(process.execPath, ['-e', `
|
|
145
|
+
const fs = require('fs');
|
|
146
|
+
const http = require('http');
|
|
147
|
+
const url = process.argv[1].replace(/\\/+$/, '') + '/proxy/status';
|
|
148
|
+
const token = fs.readFileSync(0, 'utf8').trim();
|
|
149
|
+
if (!token) process.exit(1);
|
|
150
|
+
const req = http.get(url, { headers: { Authorization: 'Bearer ' + token } }, (res) => {
|
|
151
|
+
let body = '';
|
|
152
|
+
res.setEncoding('utf8');
|
|
153
|
+
res.on('data', (chunk) => {
|
|
154
|
+
body += chunk;
|
|
155
|
+
if (body.length > 1024 * 1024) req.destroy(new Error('response too large'));
|
|
156
|
+
});
|
|
157
|
+
res.on('end', () => {
|
|
158
|
+
if (res.statusCode < 200 || res.statusCode >= 300) process.exit(1);
|
|
159
|
+
let parsed;
|
|
160
|
+
try { parsed = JSON.parse(body); } catch (_) { process.exit(1); }
|
|
161
|
+
if (parsed && parsed.status === 'running' && (parsed.proxy_protocol_version || parsed.schema_version || parsed.node_id != null)) {
|
|
162
|
+
process.exit(0);
|
|
163
|
+
}
|
|
164
|
+
process.exit(1);
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
req.setTimeout(700, () => req.destroy(new Error('timeout')));
|
|
168
|
+
req.on('error', () => process.exit(1));
|
|
169
|
+
`, url], { input: String(token), stdio: ['pipe', 'ignore', 'ignore'], timeout: 1200, windowsHide: true });
|
|
170
|
+
return true;
|
|
171
|
+
} catch (_) {
|
|
172
|
+
return false;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function _proxyHealthyIfExpected() {
|
|
177
|
+
if (!_proxyExpected()) return true;
|
|
178
|
+
const dir = process.env.EVOLVER_SETTINGS_DIR || path.join(os.homedir(), '.evolver');
|
|
179
|
+
const settings = _readJson(path.join(dir, 'settings.json'));
|
|
180
|
+
const proxy = settings && settings.proxy;
|
|
181
|
+
if (!proxy || !proxy.url) return false;
|
|
182
|
+
if (proxy.pid) {
|
|
183
|
+
try { process.kill(proxy.pid, 0); } catch (e) {
|
|
184
|
+
if (!(e && e.code === 'EPERM')) return false;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
if (!proxy.token) return false;
|
|
188
|
+
return _proxyReachable(proxy.url, proxy.token);
|
|
189
|
+
}
|
|
190
|
+
|
|
17
191
|
// Auto-restart guard: if the evolver daemon is not running when a new agent
|
|
18
192
|
// session starts, attempt a background restart. This covers the "idle-death"
|
|
19
193
|
// scenario: the user closed the machine (macOS sleep), the process died due to
|
|
@@ -69,7 +243,7 @@ function _maybeRestartDaemon(evolverRoot) {
|
|
|
69
243
|
}
|
|
70
244
|
} catch (_) { /* lock file unreadable or absent: assume not running */ }
|
|
71
245
|
|
|
72
|
-
if (daemonRunning) return; // already alive, nothing to do
|
|
246
|
+
if (daemonRunning && _proxyHealthyIfExpected()) return; // already alive, nothing to do
|
|
73
247
|
|
|
74
248
|
// Daemon appears dead. Spawn lifecycle.js start in the background so
|
|
75
249
|
// this session-start script exits immediately (< 50 ms) and does not
|
|
@@ -305,5 +479,11 @@ function main() {
|
|
|
305
479
|
if (require.main === module) {
|
|
306
480
|
main();
|
|
307
481
|
} else {
|
|
308
|
-
module.exports = {
|
|
482
|
+
module.exports = {
|
|
483
|
+
belongsToWorkspace,
|
|
484
|
+
_isLoopbackProxyUrl,
|
|
485
|
+
_proxyExpected,
|
|
486
|
+
_proxyReachable,
|
|
487
|
+
_proxyHealthyIfExpected,
|
|
488
|
+
};
|
|
309
489
|
}
|
package/src/config.js
CHANGED
|
@@ -85,9 +85,17 @@ const HUB_SEARCH_TIMEOUT_MS = envPositiveInt('EVOLVER_HUB_SEARCH_TIMEOUT_MS', 80
|
|
|
85
85
|
const PUBLIC_DEFAULT_HUB_URL = 'https://evomap.ai';
|
|
86
86
|
|
|
87
87
|
function resolveHubUrl() {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
88
|
+
// Trim each candidate and fall through on empty/whitespace-only values.
|
|
89
|
+
// A trailing space in a `.env` value (common with quoted entries like
|
|
90
|
+
// A2A_HUB_URL="https://evomap.ai ") otherwise survives into endpoint
|
|
91
|
+
// construction: `hubUrl.replace(/\/+$/, '')` strips trailing slashes but
|
|
92
|
+
// NOT whitespace, producing "https://evomap.ai /a2a/events/poll" which
|
|
93
|
+
// fails URL validation (#580 Bug 1). `||` alone would also pick a
|
|
94
|
+
// whitespace-only override as "truthy", so trim-then-fall-through here.
|
|
95
|
+
const pick = (v) => { const t = (v == null ? '' : String(v)).trim(); return t || null; };
|
|
96
|
+
const raw = pick(process.env.A2A_HUB_URL)
|
|
97
|
+
|| pick(process.env.EVOMAP_HUB_URL)
|
|
98
|
+
|| pick(process.env.EVOLVER_DEFAULT_HUB_URL)
|
|
91
99
|
|| PUBLIC_DEFAULT_HUB_URL;
|
|
92
100
|
|
|
93
101
|
if (process.env.EVOMAP_HUB_ALLOW_INSECURE !== '1') {
|