@evomap/evolver-proxy 2.0.0-beta.1 → 2.0.0-beta.11
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/dist/bin/evolver-proxy.d.ts +69 -7
- package/dist/bin/evolver-proxy.js +437 -91
- package/dist/bin/proxySettings.d.ts +2 -0
- package/dist/bin/proxySettings.js +8 -1
- package/dist/daemon/collaborationFacade.d.ts +56 -0
- package/dist/daemon/collaborationFacade.js +877 -0
- package/dist/daemon/proxyDaemon.d.ts +4 -0
- package/dist/daemon/proxyDaemon.js +130 -2
- package/dist/daemon/selectHub.js +17 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/lifecycle/legacyNodeId.d.ts +11 -13
- package/dist/lifecycle/legacyNodeId.js +35 -20
- package/dist/llm/traceControl.js +1 -1
- package/dist/private/accountAssetCompatibility.d.ts +28 -0
- package/dist/private/accountAssetCompatibility.js +196 -0
- package/dist/private/adapterLoader.d.ts +19 -2
- package/dist/private/adapterLoader.js +78 -4
- package/dist/router/messagesRoute.d.ts +13 -0
- package/dist/router/messagesRoute.js +56 -0
- package/dist/selfUpdate/executor.d.ts +10 -5
- package/dist/selfUpdate/executor.js +81 -6
- package/dist/selfUpdate/failureCodes.d.ts +6 -0
- package/dist/selfUpdate/failureCodes.js +6 -0
- package/dist/selfUpdate/index.d.ts +4 -1
- package/dist/selfUpdate/index.js +4 -1
- package/dist/selfUpdate/lastUpdate.d.ts +3 -1
- package/dist/selfUpdate/lastUpdate.js +37 -6
- package/dist/selfUpdate/releaseBinary.d.ts +10 -0
- package/dist/selfUpdate/releaseBinary.js +43 -6
- package/dist/selfUpdate/transaction.d.ts +109 -0
- package/dist/selfUpdate/transaction.js +1174 -0
- package/dist/selfUpdate/unixController.d.ts +15 -0
- package/dist/selfUpdate/unixController.js +186 -0
- package/dist/selfUpdate/version.d.ts +6 -2
- package/dist/selfUpdate/version.js +5 -3
- package/dist/selfUpdate/windowsController.d.ts +23 -0
- package/dist/selfUpdate/windowsController.js +274 -0
- package/dist/selfUpdate/windowsUpdater.d.ts +79 -0
- package/dist/selfUpdate/windowsUpdater.js +715 -0
- package/dist/sync/engine.d.ts +6 -5
- package/dist/sync/engine.js +102 -58
- package/package.json +8 -3
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { randomBytes } from 'node:crypto';
|
|
3
3
|
import { readFileSync, realpathSync, rmSync } from 'node:fs';
|
|
4
4
|
import { homedir } from 'node:os';
|
|
5
|
-
import { join } from 'node:path';
|
|
5
|
+
import { join, resolve, win32 } from 'node:path';
|
|
6
6
|
import { fileURLToPath } from 'node:url';
|
|
7
7
|
import { daemon, hub as hubNs, mailbox } from '@evomap/evolver-core';
|
|
8
8
|
import { AtpHubClient, connectPublicHub, globalFetchLike, isNodeSecret, parseNodeSecretVersion } from '@evomap/evolver-adapter-public';
|
|
@@ -14,88 +14,241 @@ import { connectPrivateProxyHub } from '../private/adapterLoader.js';
|
|
|
14
14
|
import { createAtpOrderConsentGate } from '../daemon/atpConsent.js';
|
|
15
15
|
import { resolveProxyStorePath } from './proxyStorePath.js';
|
|
16
16
|
import { publishProxySettings } from './proxySettings.js';
|
|
17
|
-
import { loadEnvFileFromEnv } from './envFile.js';
|
|
17
|
+
import { expandHomePath, loadEnvFileFromEnv } from './envFile.js';
|
|
18
18
|
import { resolveProxyNodeId } from '../lifecycle/legacyNodeId.js';
|
|
19
19
|
import { getCurrentVersion } from '../selfUpdate/version.js';
|
|
20
20
|
import { resolveSelfUpdatePolicy } from '../selfUpdate/policy.js';
|
|
21
|
-
import { atomicReplaceExecutable, downloadGithubReleaseArtifact, resolveGithubReleaseManifest, } from '../selfUpdate/releaseBinary.js';
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
import { atomicReplaceExecutable, downloadGithubReleaseArtifact, resolveGithubReleaseManifest, resolveSelfUpdateTarget, } from '../selfUpdate/releaseBinary.js';
|
|
22
|
+
import { beginDurableSelfUpdate, confirmDurableSelfUpdate, recoverDurableSelfUpdate, rollbackDurableSelfUpdate, } from '../selfUpdate/transaction.js';
|
|
23
|
+
import { SELF_UPDATE_FAILURE_CODES } from '../selfUpdate/failureCodes.js';
|
|
24
|
+
import { maybeRunWindowsUpdaterWorkerFromArgv } from '../selfUpdate/windowsUpdater.js';
|
|
25
|
+
import { maybeRunUnixRecoveryController } from '../selfUpdate/unixController.js';
|
|
26
|
+
import { maybeRunWindowsRecoveryController } from '../selfUpdate/windowsController.js';
|
|
27
|
+
import { finalizeSelfUpdateRecoveryLastUpdate } from '../selfUpdate/lastUpdate.js';
|
|
28
|
+
export async function runProxyMain(options = {}) {
|
|
24
29
|
if (process.argv.includes('--help') || process.argv.includes('-h')) {
|
|
25
30
|
process.stdout.write(proxyUsage());
|
|
26
31
|
return;
|
|
27
32
|
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
//
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
// Read our own version for hello/heartbeat reporting and self-update decisions. EVOLVER_SELF_UPDATE defaults
|
|
51
|
-
// to off; prompt/auto opt into the daemon path, but the release download/replace seams still fail closed until
|
|
52
|
-
// a verified release implementation is wired.
|
|
53
|
-
const evolverVersion = getCurrentVersion();
|
|
54
|
-
const selfUpdatePolicy = resolveSelfUpdatePolicy(process.env);
|
|
55
|
-
const runtime = await connectHubRuntime({ mode, hubUrl, senderId, store });
|
|
56
|
-
const proxyStartedAt = new Date().toISOString();
|
|
33
|
+
if (!options.environmentPrepared) {
|
|
34
|
+
const envFile = loadProxyEnvFile(process.env);
|
|
35
|
+
if (envFile.error)
|
|
36
|
+
process.stderr.write(`[evolver-proxy] failed to load EVOLVER_ENV_FILE: ${safeLoopMessage(envFile.error)}\n`);
|
|
37
|
+
}
|
|
38
|
+
// Recovery must run before any hub/store/runtime initialization. In particular,
|
|
39
|
+
// a broken store must not prevent a pending-health update from restoring the old binary.
|
|
40
|
+
const recovery = await recoverBoundDurableSelfUpdate({ env: process.env, processExecPath: process.execPath });
|
|
41
|
+
if (recovery.outcome === 'blocked') {
|
|
42
|
+
throw new Error(`self_update_recovery_blocked:${recovery.failureCode ?? 'unknown'}`);
|
|
43
|
+
}
|
|
44
|
+
if (recovery.restartRequired)
|
|
45
|
+
process.exit(78);
|
|
46
|
+
let storePath;
|
|
47
|
+
let store;
|
|
48
|
+
let proxyDaemon;
|
|
49
|
+
let confirmation;
|
|
50
|
+
let mode;
|
|
51
|
+
let hubUrl;
|
|
52
|
+
let port;
|
|
53
|
+
let evolverVersion;
|
|
54
|
+
let selfUpdatePolicy;
|
|
57
55
|
const proxySettingsState = {};
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
56
|
+
let publishLocalProxySettings = () => { };
|
|
57
|
+
try {
|
|
58
|
+
mode = resolveHubMode(process.env);
|
|
59
|
+
hubUrl = resolveHubUrl(process.env);
|
|
60
|
+
storePath = resolveProxyStorePath(process.env);
|
|
61
|
+
store = new mailbox.MailboxStore({ path: storePath });
|
|
62
|
+
finalizeRecoveryTelemetry(store, recovery);
|
|
63
|
+
// `?.trim() ||` not `??`: an EMPTY/whitespace EVOLVER_IPC_TOKEN (e.g. a blank `.env` entry or `export
|
|
64
|
+
// EVOLVER_IPC_TOKEN=`) must be treated as unset and get a strong random token, never fall through as `''` —
|
|
65
|
+
// an empty token would authenticate any `Authorization: Bearer ` request and defeat the loopback IPC auth.
|
|
66
|
+
const ipcToken = process.env['EVOLVER_IPC_TOKEN']?.trim() || randomBytes(24).toString('hex');
|
|
67
|
+
const ipcPort = resolveIpcPort(process.env);
|
|
68
|
+
// Trim + treat blank as unset, preferring the first NON-EMPTY override so an
|
|
69
|
+
// empty `EVOMAP_NODE_ID=` (k8s configmap / `$(cat missing)`) neither shadows a
|
|
70
|
+
// valid A2A_NODE_ID nor suppresses the legacy recovery below. v1 parity:
|
|
71
|
+
// a2aProtocol trimmed the env id before use.
|
|
72
|
+
const configuredNodeId = (process.env['EVOMAP_NODE_ID']?.trim() || process.env['A2A_NODE_ID']?.trim()) || undefined;
|
|
73
|
+
// store node_id → env override → legacy ~/.evomap/node_id (PORT v1 #117): when
|
|
74
|
+
// the store is unprimed AND no env override is set, recover the id the legacy
|
|
75
|
+
// GEP path persisted before letting hello() mint a fresh A2ANode under the
|
|
76
|
+
// same owner. See lifecycle/legacyNodeId.ts for the duplicate-node rationale.
|
|
77
|
+
const senderId = () => resolveProxyNodeId({ storedNodeId: store.getState('node_id'), configuredNodeId });
|
|
78
|
+
evolverVersion = getCurrentVersion();
|
|
79
|
+
selfUpdatePolicy = resolveSelfUpdatePolicy(process.env);
|
|
80
|
+
const proxyStartedAt = new Date().toISOString();
|
|
81
|
+
publishLocalProxySettings = () => {
|
|
82
|
+
if (!proxySettingsState.url)
|
|
83
|
+
return;
|
|
84
|
+
publishProxySettings({
|
|
85
|
+
env: process.env,
|
|
86
|
+
record: {
|
|
87
|
+
url: proxySettingsState.url,
|
|
88
|
+
token: ipcToken,
|
|
89
|
+
pid: process.pid,
|
|
90
|
+
started_at: proxyStartedAt,
|
|
91
|
+
version: evolverVersion,
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
};
|
|
95
|
+
const runtime = await connectHubRuntime({ mode, hubUrl, senderId, store });
|
|
96
|
+
proxyDaemon = new ProxyDaemon({
|
|
97
|
+
...createProxyDaemonDeps({
|
|
98
|
+
runtime,
|
|
99
|
+
store,
|
|
100
|
+
ipcToken,
|
|
101
|
+
...(ipcPort !== undefined ? { ipcPort } : {}),
|
|
102
|
+
evolverVersion,
|
|
103
|
+
selfUpdatePolicy,
|
|
104
|
+
env: process.env,
|
|
105
|
+
}),
|
|
106
|
+
onIpcListen: (listeningPort) => {
|
|
107
|
+
proxySettingsState.url = `http://127.0.0.1:${listeningPort}`;
|
|
108
|
+
publishLocalProxySettings();
|
|
68
109
|
},
|
|
110
|
+
onIpcAuthFailure: publishLocalProxySettings,
|
|
69
111
|
});
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
112
|
+
port = await proxyDaemon.start();
|
|
113
|
+
if (recovery.outcome === 'pending_health') {
|
|
114
|
+
assertSelfUpdateProcessTargetBound({ env: process.env, processExecPath: process.execPath });
|
|
115
|
+
confirmation = await confirmDurableSelfUpdate({ env: process.env, processExecPath: process.execPath });
|
|
116
|
+
if (confirmation.outcome !== 'confirmed') {
|
|
117
|
+
throw new Error(`self_update_confirmation_failed:${confirmation.outcome}`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
catch (error) {
|
|
122
|
+
if (recovery.outcome === 'pending_health') {
|
|
123
|
+
const rollback = await rollbackPendingStartup({
|
|
124
|
+
...(storePath ? { storePath } : {}),
|
|
125
|
+
...(store ? { store } : {}),
|
|
126
|
+
...(proxyDaemon ? { daemon: proxyDaemon } : {}),
|
|
127
|
+
startupError: error,
|
|
128
|
+
env: process.env,
|
|
129
|
+
});
|
|
130
|
+
process.stderr.write(`[evolver-proxy] self-update startup health check failed; ${rollback.outcome}: ${safeLoopErrorMessage(error)}\n`);
|
|
131
|
+
process.exit(startupRollbackExitCode(rollback));
|
|
132
|
+
}
|
|
133
|
+
await closeStartupResources({ store, daemon: proxyDaemon });
|
|
134
|
+
throw error;
|
|
135
|
+
}
|
|
136
|
+
if (confirmation)
|
|
137
|
+
finalizeRecoveryTelemetry(store, confirmation);
|
|
88
138
|
proxySettingsState.url = `http://127.0.0.1:${port}`;
|
|
89
139
|
publishLocalProxySettings();
|
|
90
140
|
process.stdout.write(`[evolver-proxy] mode=${mode} hub=${hubUrl} ipc=127.0.0.1:${port} v=${evolverVersion} self-update=${selfUpdatePolicy}\n`);
|
|
91
|
-
await runProxyLoop(
|
|
141
|
+
await runProxyLoop(proxyDaemon, { logger: process.stderr });
|
|
142
|
+
}
|
|
143
|
+
export async function recoverBoundDurableSelfUpdate(options) {
|
|
144
|
+
return recoverDurableSelfUpdate({
|
|
145
|
+
...options,
|
|
146
|
+
beforeJournalMutation: () => {
|
|
147
|
+
assertSelfUpdateProcessTargetBound(options);
|
|
148
|
+
},
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
export function loadProxyEnvFile(env) {
|
|
152
|
+
const supervisor = env['EVOLVER_SELF_UPDATE_SUPERVISOR'];
|
|
153
|
+
const stateDir = env['EVOLVER_SELF_UPDATE_STATE_DIR'];
|
|
154
|
+
const targetPath = env['EVOLVER_SELF_UPDATE_TARGET_PATH'];
|
|
155
|
+
const result = loadEnvFileFromEnv(env);
|
|
156
|
+
if (supervisor === undefined) {
|
|
157
|
+
delete env['EVOLVER_SELF_UPDATE_SUPERVISOR'];
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
160
|
+
env['EVOLVER_SELF_UPDATE_SUPERVISOR'] = supervisor;
|
|
161
|
+
if (stateDir !== undefined)
|
|
162
|
+
env['EVOLVER_SELF_UPDATE_STATE_DIR'] = stateDir;
|
|
163
|
+
if (targetPath !== undefined)
|
|
164
|
+
env['EVOLVER_SELF_UPDATE_TARGET_PATH'] = targetPath;
|
|
165
|
+
}
|
|
166
|
+
return result;
|
|
167
|
+
}
|
|
168
|
+
function finalizeRecoveryTelemetry(store, recovery) {
|
|
169
|
+
try {
|
|
170
|
+
finalizeSelfUpdateRecoveryLastUpdate(store, recovery);
|
|
171
|
+
}
|
|
172
|
+
catch (error) {
|
|
173
|
+
process.stderr.write(`[evolver-proxy] failed to persist self-update recovery telemetry: ${safeLoopErrorMessage(error)}\n`);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
export async function rollbackPendingStartup(options) {
|
|
177
|
+
await closeStartupResources(options);
|
|
178
|
+
let rollback;
|
|
179
|
+
try {
|
|
180
|
+
const processExecPath = options.processExecPath ?? process.execPath;
|
|
181
|
+
assertSelfUpdateProcessTargetBound({ env: options.env ?? process.env, processExecPath }, true);
|
|
182
|
+
rollback = await (options.rollback ?? rollbackDurableSelfUpdate)({ env: options.env ?? process.env, processExecPath }, SELF_UPDATE_FAILURE_CODES.RESTART_FAILED);
|
|
183
|
+
}
|
|
184
|
+
catch (error) {
|
|
185
|
+
throw new Error(`self_update_recovery_blocked:${safeLoopErrorMessage(error)}`, {
|
|
186
|
+
cause: options.startupError,
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
if (options.storePath) {
|
|
190
|
+
let telemetryStore;
|
|
191
|
+
try {
|
|
192
|
+
telemetryStore = (options.openTelemetryStore ?? ((storePath) => (new mailbox.MailboxStore({ path: storePath }))))(options.storePath);
|
|
193
|
+
(options.persistTelemetry ?? ((openedStore, recovery) => {
|
|
194
|
+
finalizeRecoveryTelemetry(openedStore, recovery);
|
|
195
|
+
}))(telemetryStore, rollback);
|
|
196
|
+
}
|
|
197
|
+
catch {
|
|
198
|
+
(options.logger ?? process.stderr).write('[evolver-proxy] failed to reopen self-update telemetry store\n');
|
|
199
|
+
}
|
|
200
|
+
finally {
|
|
201
|
+
try {
|
|
202
|
+
telemetryStore?.close();
|
|
203
|
+
}
|
|
204
|
+
catch { /* rollback already completed; telemetry cleanup is best-effort */ }
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
return rollback;
|
|
92
208
|
}
|
|
93
|
-
|
|
209
|
+
async function closeStartupResources(options) {
|
|
210
|
+
let daemonStopped = false;
|
|
211
|
+
if (options.daemon) {
|
|
212
|
+
try {
|
|
213
|
+
await options.daemon.stop();
|
|
214
|
+
daemonStopped = true;
|
|
215
|
+
}
|
|
216
|
+
catch {
|
|
217
|
+
// Continue closing the directly-created store before rollback.
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
if (options.store && !daemonStopped) {
|
|
221
|
+
try {
|
|
222
|
+
options.store.close();
|
|
223
|
+
}
|
|
224
|
+
catch { /* rollback must not be blocked by resource cleanup */ }
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
export function startupRollbackExitCode(rollback) {
|
|
228
|
+
if (rollback.outcome === 'blocked') {
|
|
229
|
+
throw new Error(`self_update_recovery_blocked:${rollback.failureCode ?? 'unknown'}`);
|
|
230
|
+
}
|
|
231
|
+
if (rollback.outcome !== 'rollback_pending'
|
|
232
|
+
&& rollback.outcome !== 'rolled_back'
|
|
233
|
+
&& rollback.outcome !== 'confirmed') {
|
|
234
|
+
throw new Error(`self_update_recovery_blocked:unexpected_${rollback.outcome}`);
|
|
235
|
+
}
|
|
236
|
+
return 78;
|
|
237
|
+
}
|
|
238
|
+
export function proxyUsage(command = 'evolver-proxy') {
|
|
94
239
|
return [
|
|
95
|
-
|
|
240
|
+
`Usage: ${command} [options]`,
|
|
96
241
|
'',
|
|
97
242
|
'Starts the local Evolver proxy daemon.',
|
|
98
243
|
'',
|
|
244
|
+
'Options (CLI overrides environment variables):',
|
|
245
|
+
' --home <dir> Root for assets, store, settings, and traces',
|
|
246
|
+
' --evomap-home <dir> Identity home for node_id/node_secret (EVOMAP_HOME); defaults to --home',
|
|
247
|
+
' --store <path> Mailbox store path (EVOLVER_PROXY_STORE)',
|
|
248
|
+
' --settings <path> Proxy settings file (EVOLVER_PROXY_SETTINGS_FILE)',
|
|
249
|
+
' --env-file <path> Environment file (EVOLVER_ENV_FILE)',
|
|
250
|
+
' -h, --help Show this help',
|
|
251
|
+
'',
|
|
99
252
|
'Required for public mode:',
|
|
100
253
|
' EVOMAP_NODE_SECRET or A2A_NODE_SECRET',
|
|
101
254
|
'',
|
|
@@ -104,22 +257,124 @@ export function proxyUsage() {
|
|
|
104
257
|
' EVOMAP_HUB_URL=<private hub url>',
|
|
105
258
|
' EVOMAP_ENTERPRISE_TOKEN=<token>',
|
|
106
259
|
'',
|
|
260
|
+
'Hub URL precedence:',
|
|
261
|
+
' A2A_HUB_URL -> EVOMAP_HUB_URL -> EVOLVER_DEFAULT_HUB_URL -> https://evomap.ai',
|
|
262
|
+
'',
|
|
107
263
|
'Useful options are configured through env or EVOLVER_ENV_FILE:',
|
|
108
264
|
' EVOLVER_IPC_PORT, EVOLVER_IPC_TOKEN, EVOLVER_PROXY_SETTINGS_FILE',
|
|
109
265
|
' EVOLVER_SELF_UPDATE, EVOLVER_LLM_TRACE_CAPTURE_BODIES',
|
|
110
266
|
'',
|
|
111
267
|
].join('\n');
|
|
112
268
|
}
|
|
113
|
-
|
|
269
|
+
const PROXY_PATH_FLAGS = new Map([
|
|
270
|
+
['--home', 'home'],
|
|
271
|
+
['--evomap-home', 'evomapHome'],
|
|
272
|
+
['--store', 'store'],
|
|
273
|
+
['--settings', 'settings'],
|
|
274
|
+
['--env-file', 'envFile'],
|
|
275
|
+
]);
|
|
276
|
+
export function parseProxyCliPathOptions(argv) {
|
|
277
|
+
const options = { help: false };
|
|
278
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
279
|
+
const arg = argv[index];
|
|
280
|
+
if (arg === '--help' || arg === '-h') {
|
|
281
|
+
options.help = true;
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
284
|
+
const equalsIndex = arg.indexOf('=');
|
|
285
|
+
const flag = equalsIndex >= 0 ? arg.slice(0, equalsIndex) : arg;
|
|
286
|
+
const key = PROXY_PATH_FLAGS.get(flag);
|
|
287
|
+
if (key) {
|
|
288
|
+
const value = equalsIndex >= 0 ? arg.slice(equalsIndex + 1) : argv[++index];
|
|
289
|
+
if (!value?.trim() || (equalsIndex < 0 && value.startsWith('-'))) {
|
|
290
|
+
throw new Error(`${flag} requires a path`);
|
|
291
|
+
}
|
|
292
|
+
options[key] = resolve(expandHomePath(value.trim()));
|
|
293
|
+
continue;
|
|
294
|
+
}
|
|
295
|
+
if (arg.startsWith('-'))
|
|
296
|
+
throw new Error(`unknown option: ${arg}`);
|
|
297
|
+
}
|
|
298
|
+
return options;
|
|
299
|
+
}
|
|
300
|
+
export function prepareProxyCliEnvironment(argv, env) {
|
|
301
|
+
const options = parseProxyCliPathOptions(argv);
|
|
302
|
+
if (options.envFile)
|
|
303
|
+
env['EVOLVER_ENV_FILE'] = options.envFile;
|
|
304
|
+
const envFile = loadProxyEnvFile(env);
|
|
305
|
+
if (options.home) {
|
|
306
|
+
env['EVOMAP_DIR'] = options.home;
|
|
307
|
+
env['EVOLVER_HOME'] = options.home;
|
|
308
|
+
env['EVOMAP_HOME'] = options.home;
|
|
309
|
+
env['EVOLVER_SETTINGS_DIR'] = options.home;
|
|
310
|
+
env['EVOLVER_PROXY_STORE'] = join(options.home, 'proxy', 'mailbox.db');
|
|
311
|
+
env['EVOLVER_PROXY_SETTINGS_FILE'] = join(options.home, 'settings.json');
|
|
312
|
+
env['EVOLVER_LLM_TRACE_DIR'] = join(options.home, 'proxy', 'traces');
|
|
313
|
+
}
|
|
314
|
+
// Identity/state split for embedders whose node identity lives outside the state root (evox agentDir keeps
|
|
315
|
+
// node_id/node_secret under <agentDir>/evomap while evolver state lives under <agentDir>/evolver, #555 T2).
|
|
316
|
+
// Applied AFTER --home so it overrides the single-root EVOMAP_HOME derivation; state paths stay on --home.
|
|
317
|
+
if (options.evomapHome)
|
|
318
|
+
env['EVOMAP_HOME'] = options.evomapHome;
|
|
319
|
+
if (options.store)
|
|
320
|
+
env['EVOLVER_PROXY_STORE'] = options.store;
|
|
321
|
+
if (options.settings)
|
|
322
|
+
env['EVOLVER_PROXY_SETTINGS_FILE'] = options.settings;
|
|
323
|
+
return { options, envFile };
|
|
324
|
+
}
|
|
325
|
+
export async function runProxyCli(options = {}) {
|
|
326
|
+
const argv = options.argv ?? process.argv.slice(2);
|
|
327
|
+
const env = options.env ?? process.env;
|
|
328
|
+
const unixControllerExitCode = await (options.runUnixRecoveryController ?? maybeRunUnixRecoveryController)({
|
|
329
|
+
argv,
|
|
330
|
+
env,
|
|
331
|
+
platform: options.platform ?? process.platform,
|
|
332
|
+
processExecPath: options.processExecPath ?? process.execPath,
|
|
333
|
+
});
|
|
334
|
+
if (unixControllerExitCode !== undefined)
|
|
335
|
+
return unixControllerExitCode;
|
|
336
|
+
const windowsControllerExitCode = await (options.runWindowsRecoveryController ?? maybeRunWindowsRecoveryController)({
|
|
337
|
+
argv,
|
|
338
|
+
env,
|
|
339
|
+
platform: options.platform ?? process.platform,
|
|
340
|
+
processExecPath: options.processExecPath ?? process.execPath,
|
|
341
|
+
});
|
|
342
|
+
if (windowsControllerExitCode !== undefined)
|
|
343
|
+
return windowsControllerExitCode;
|
|
344
|
+
const workerExitCode = await (options.runWindowsUpdaterWorker ?? maybeRunWindowsUpdaterWorkerFromArgv)({
|
|
345
|
+
argv,
|
|
346
|
+
env,
|
|
347
|
+
platform: options.platform ?? process.platform,
|
|
348
|
+
processExecPath: options.processExecPath ?? process.execPath,
|
|
349
|
+
});
|
|
350
|
+
if (workerExitCode !== undefined)
|
|
351
|
+
return workerExitCode;
|
|
114
352
|
const uninstallUnhandledRejectionGuard = daemon.installUnhandledRejectionWindow();
|
|
115
|
-
|
|
353
|
+
try {
|
|
354
|
+
const cliOptions = parseProxyCliPathOptions(argv);
|
|
355
|
+
if (cliOptions.help) {
|
|
356
|
+
process.stdout.write(proxyUsage(argv[0] === 'proxy' ? 'evolver proxy' : 'evolver-proxy'));
|
|
357
|
+
return 0;
|
|
358
|
+
}
|
|
359
|
+
const prepared = prepareProxyCliEnvironment(argv, env);
|
|
360
|
+
if (prepared.envFile.error) {
|
|
361
|
+
process.stderr.write(`[evolver-proxy] failed to load EVOLVER_ENV_FILE: ${safeLoopMessage(prepared.envFile.error)}\n`);
|
|
362
|
+
}
|
|
363
|
+
await (options.runMain ?? (() => runProxyMain({ environmentPrepared: true })))();
|
|
364
|
+
return 0;
|
|
365
|
+
}
|
|
366
|
+
catch (error) {
|
|
367
|
+
process.stderr.write(`[evolver-proxy] fatal: ${safeLoopErrorMessage(error)}\n`);
|
|
368
|
+
return 1;
|
|
369
|
+
}
|
|
370
|
+
finally {
|
|
116
371
|
uninstallUnhandledRejectionGuard();
|
|
117
|
-
|
|
118
|
-
process.exit(1);
|
|
119
|
-
});
|
|
372
|
+
}
|
|
120
373
|
}
|
|
121
374
|
if (isDirectRun(import.meta.url, process.argv[1])) {
|
|
122
|
-
runProxyCli()
|
|
375
|
+
void runProxyCli().then((exitCode) => {
|
|
376
|
+
process.exitCode = exitCode;
|
|
377
|
+
});
|
|
123
378
|
}
|
|
124
379
|
export async function runProxyLoop(daemon, options = {}) {
|
|
125
380
|
const minDelayMs = options.minDelayMs ?? 1_000;
|
|
@@ -335,24 +590,90 @@ function errorMessage(err) {
|
|
|
335
590
|
export function createSelfUpdateDeps(policy, currentVersion, env = process.env, overrides = {}) {
|
|
336
591
|
if (policy !== 'auto')
|
|
337
592
|
return undefined;
|
|
593
|
+
const { supervisorAttested, restart, stagedBinaryProbe, processExecPath: _ignoredProcessExecPath, ...binaryOverrides } = overrides;
|
|
594
|
+
if (!supervisorAttested && !selfUpdateSupervisorAttested(env)) {
|
|
595
|
+
throw new Error('self_update_supervisor_required');
|
|
596
|
+
}
|
|
338
597
|
const publicKey = env['EVOLVER_SELF_UPDATE_PUBLIC_KEY']?.trim();
|
|
339
598
|
if (!publicKey)
|
|
340
599
|
throw new Error('self_update_public_key_required');
|
|
341
600
|
const releaseOpts = {
|
|
342
601
|
env,
|
|
343
|
-
...
|
|
602
|
+
...binaryOverrides,
|
|
603
|
+
processExecPath: supervisorAttested?.processExecPath ?? process.execPath,
|
|
344
604
|
requireSignedManifest: true,
|
|
345
605
|
};
|
|
606
|
+
const assertBound = () => assertSelfUpdateProcessTargetBound(releaseOpts);
|
|
607
|
+
assertBound();
|
|
346
608
|
return {
|
|
347
609
|
policy,
|
|
348
610
|
currentVersion,
|
|
349
|
-
resolveManifest: (directive) =>
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
611
|
+
resolveManifest: (directive) => {
|
|
612
|
+
assertBound();
|
|
613
|
+
return resolveGithubReleaseManifest(directive, releaseOpts);
|
|
614
|
+
},
|
|
615
|
+
download: (targetVersion, directive) => {
|
|
616
|
+
assertBound();
|
|
617
|
+
return downloadGithubReleaseArtifact(targetVersion, directive, releaseOpts);
|
|
618
|
+
},
|
|
619
|
+
atomicReplace: (stagedPath) => {
|
|
620
|
+
assertBound();
|
|
621
|
+
return atomicReplaceExecutable(stagedPath, releaseOpts);
|
|
622
|
+
},
|
|
623
|
+
beginTransaction: async (targetVersion) => {
|
|
624
|
+
assertBound();
|
|
625
|
+
const transaction = await beginDurableSelfUpdate(targetVersion, {
|
|
626
|
+
...releaseOpts,
|
|
627
|
+
currentVersion,
|
|
628
|
+
...(stagedBinaryProbe ? { stagedBinaryProbe } : {}),
|
|
629
|
+
});
|
|
630
|
+
return {
|
|
631
|
+
...transaction,
|
|
632
|
+
install: async () => {
|
|
633
|
+
assertBound();
|
|
634
|
+
await transaction.install();
|
|
635
|
+
},
|
|
636
|
+
};
|
|
637
|
+
},
|
|
638
|
+
restart: restart ?? (() => { process.exit(78); }),
|
|
353
639
|
publicKey,
|
|
354
640
|
};
|
|
355
641
|
}
|
|
642
|
+
export function assertSelfUpdateProcessTargetBound(options, allowUnresolvedTarget = false) {
|
|
643
|
+
let targetPath;
|
|
644
|
+
try {
|
|
645
|
+
targetPath = resolveSelfUpdateTarget(options).path;
|
|
646
|
+
}
|
|
647
|
+
catch {
|
|
648
|
+
if (allowUnresolvedTarget)
|
|
649
|
+
return;
|
|
650
|
+
throw new Error('self_update_process_target_mismatch');
|
|
651
|
+
}
|
|
652
|
+
const processExecPath = options.processExecPath ?? process.execPath;
|
|
653
|
+
try {
|
|
654
|
+
if (canonicalExecutablePath(processExecPath) !== canonicalExecutablePath(targetPath)) {
|
|
655
|
+
throw new Error('self_update_process_target_mismatch');
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
catch {
|
|
659
|
+
throw new Error('self_update_process_target_mismatch');
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
function canonicalExecutablePath(path) {
|
|
663
|
+
const canonical = realpathSync.native(path);
|
|
664
|
+
if (process.platform !== 'win32')
|
|
665
|
+
return resolve(canonical);
|
|
666
|
+
const withoutNamespace = canonical
|
|
667
|
+
.replace(/^\\\\\?\\UNC\\/i, '\\\\')
|
|
668
|
+
.replace(/^\\\\\?\\/i, '');
|
|
669
|
+
return win32.normalize(withoutNamespace).toLowerCase();
|
|
670
|
+
}
|
|
671
|
+
function selfUpdateSupervisorAttested(env) {
|
|
672
|
+
const supervisor = env['EVOLVER_SELF_UPDATE_SUPERVISOR']?.trim();
|
|
673
|
+
return supervisor === 'systemd'
|
|
674
|
+
|| supervisor === 'launchd'
|
|
675
|
+
|| supervisor === 'windows-scheduled-task';
|
|
676
|
+
}
|
|
356
677
|
export function resolvePublicNodeSecret(deps) {
|
|
357
678
|
const envNodeSecret = process.env['EVOMAP_NODE_SECRET'] ?? process.env['A2A_NODE_SECRET'];
|
|
358
679
|
// version env precedence MUST mirror the node_secret precedence above (EVOMAP-first) so an operator
|
|
@@ -392,13 +713,29 @@ function readTrimmedFile(path) {
|
|
|
392
713
|
return undefined;
|
|
393
714
|
}
|
|
394
715
|
}
|
|
716
|
+
// Identity-home probe order (#555 T2): EVOMAP_HOME is THE identity home and outranks the state root
|
|
717
|
+
// (EVOLVER_HOME) — under the evox agentDir split (`--home <agentDir>/evolver --evomap-home <agentDir>/evomap`)
|
|
718
|
+
// node files live only under the evomap dir, and the old single-home read (EVOLVER_HOME-first) would miss
|
|
719
|
+
// them and fall back to the machine-global ~/.evomap node. Probing is a fall-through union, so single-home
|
|
720
|
+
// setups (only EVOLVER_HOME, or neither) resolve exactly as before.
|
|
721
|
+
function identityHomeCandidates(env = process.env) {
|
|
722
|
+
const candidates = [
|
|
723
|
+
env['EVOMAP_HOME'],
|
|
724
|
+
env['EVOMAP_DIR'],
|
|
725
|
+
env['EVOLVER_HOME'],
|
|
726
|
+
join(env['HOME'] || homedir(), '.evomap'),
|
|
727
|
+
];
|
|
728
|
+
return [...new Set(candidates.map((value) => value?.trim()).filter((value) => Boolean(value)))];
|
|
729
|
+
}
|
|
395
730
|
function readLegacyNodeSecret(env = process.env) {
|
|
396
|
-
const home
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
731
|
+
for (const home of identityHomeCandidates(env)) {
|
|
732
|
+
const nodeSecret = readTrimmedFile(join(home, 'node_secret'));
|
|
733
|
+
if (!nodeSecret || !isNodeSecret(nodeSecret))
|
|
734
|
+
continue;
|
|
735
|
+
const nodeSecretVersion = parseNodeSecretVersion(readTrimmedFile(join(home, 'node_secret_version')));
|
|
736
|
+
return { nodeSecret, ...(nodeSecretVersion !== undefined ? { nodeSecretVersion } : {}) };
|
|
737
|
+
}
|
|
738
|
+
return undefined;
|
|
402
739
|
}
|
|
403
740
|
// Durable copies of the legacy node_secret, cleared on hub-signalled divergence.
|
|
404
741
|
// Store keys mirror cli LOCAL_SECRET_STATE_KEYS (index.ts:82); on-disk files mirror
|
|
@@ -414,13 +751,17 @@ const LEGACY_SECRET_FILES = ['node_secret', 'node_secret_version'];
|
|
|
414
751
|
export function clearDivergedPublicNodeSecret(store, env = process.env) {
|
|
415
752
|
for (const key of LOCAL_SECRET_STATE_KEYS)
|
|
416
753
|
store.setState(key, '');
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
754
|
+
// Wipe every identity-home candidate, not just the resolved state home: under the identity/state split
|
|
755
|
+
// (EVOMAP_HOME ≠ EVOLVER_HOME) the diverged files live in the evomap dir, and clearing only one home would
|
|
756
|
+
// leave them to resurrect the diverged secret on the next start (same union rationale as reset-local-secret).
|
|
757
|
+
for (const home of identityHomeCandidates(env)) {
|
|
758
|
+
for (const file of LEGACY_SECRET_FILES) {
|
|
759
|
+
try {
|
|
760
|
+
rmSync(join(home, file), { force: true });
|
|
761
|
+
}
|
|
762
|
+
catch (err) {
|
|
763
|
+
process.stderr.write(`[evolver-proxy] failed to unlink diverged ${file}: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
764
|
+
}
|
|
424
765
|
}
|
|
425
766
|
}
|
|
426
767
|
}
|
|
@@ -460,14 +801,19 @@ function isDirectRun(metaUrl, argv1) {
|
|
|
460
801
|
}
|
|
461
802
|
export async function connectHubRuntime(deps) {
|
|
462
803
|
if (deps.mode === 'private') {
|
|
463
|
-
const
|
|
804
|
+
const runtime = await connectPrivateProxyHub({
|
|
464
805
|
hubUrl: deps.hubUrl,
|
|
465
806
|
senderId: deps.senderId,
|
|
466
807
|
env: deps.env ?? process.env,
|
|
467
808
|
...(deps.now ? { now: deps.now } : {}),
|
|
468
809
|
...(deps.privateImporter ? { importer: deps.privateImporter } : {}),
|
|
469
810
|
});
|
|
470
|
-
return {
|
|
811
|
+
return {
|
|
812
|
+
hub: runtime.hub,
|
|
813
|
+
hello: runtime.hello,
|
|
814
|
+
heartbeat: (opts) => runtime.hub.heartbeat(opts),
|
|
815
|
+
helloMode: 'enterprise_token',
|
|
816
|
+
};
|
|
471
817
|
}
|
|
472
818
|
const selection = resolvePublicNodeSecret(deps);
|
|
473
819
|
const { nodeSecret, nodeSecretVersion } = selection;
|
|
@@ -8,8 +8,10 @@ export interface ProxySettingsRecord {
|
|
|
8
8
|
export interface PublishProxySettingsOptions {
|
|
9
9
|
settingsPath?: string;
|
|
10
10
|
homeDir?: string;
|
|
11
|
+
env?: Record<string, string | undefined>;
|
|
11
12
|
record: ProxySettingsRecord;
|
|
12
13
|
}
|
|
13
14
|
export declare function defaultProxySettingsPath(homeDir?: string): string;
|
|
15
|
+
export declare function resolveProxySettingsPath(env?: Record<string, string | undefined>, homeDir?: string): string;
|
|
14
16
|
export declare function publishProxySettings(options: PublishProxySettingsOptions): boolean;
|
|
15
17
|
export declare function proxySettingsMatch(settingsPath: string, record: ProxySettingsRecord): boolean;
|
|
@@ -5,8 +5,15 @@ import { dirname, join } from 'node:path';
|
|
|
5
5
|
export function defaultProxySettingsPath(homeDir = homedir()) {
|
|
6
6
|
return join(homeDir, '.evolver', 'settings.json');
|
|
7
7
|
}
|
|
8
|
+
export function resolveProxySettingsPath(env = {}, homeDir = homedir()) {
|
|
9
|
+
const explicit = env['EVOLVER_PROXY_SETTINGS_FILE']?.trim();
|
|
10
|
+
if (explicit)
|
|
11
|
+
return explicit;
|
|
12
|
+
const settingsDir = env['EVOLVER_SETTINGS_DIR']?.trim() || join(homeDir, '.evolver');
|
|
13
|
+
return join(settingsDir, 'settings.json');
|
|
14
|
+
}
|
|
8
15
|
export function publishProxySettings(options) {
|
|
9
|
-
const settingsPath = options.settingsPath ??
|
|
16
|
+
const settingsPath = options.settingsPath ?? resolveProxySettingsPath(options.env, options.homeDir);
|
|
10
17
|
const record = options.record;
|
|
11
18
|
if (!record.token.trim())
|
|
12
19
|
return false;
|