@evomap/evolver-proxy 2.0.0-beta.4 → 2.0.0-beta.6
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.
|
@@ -39,7 +39,7 @@ interface RollbackPendingStartupOptions {
|
|
|
39
39
|
}
|
|
40
40
|
export declare function rollbackPendingStartup(options: RollbackPendingStartupOptions): Promise<SelfUpdateRecoveryResult>;
|
|
41
41
|
export declare function startupRollbackExitCode(rollback: SelfUpdateRecoveryResult): 78;
|
|
42
|
-
export declare function proxyUsage(): string;
|
|
42
|
+
export declare function proxyUsage(command?: string): string;
|
|
43
43
|
export interface RunProxyCliOptions {
|
|
44
44
|
argv?: readonly string[];
|
|
45
45
|
env?: NodeJS.ProcessEnv;
|
|
@@ -52,6 +52,7 @@ export interface RunProxyCliOptions {
|
|
|
52
52
|
}
|
|
53
53
|
export interface ProxyCliPathOptions {
|
|
54
54
|
home?: string;
|
|
55
|
+
evomapHome?: string;
|
|
55
56
|
store?: string;
|
|
56
57
|
settings?: string;
|
|
57
58
|
envFile?: string;
|
|
@@ -235,18 +235,19 @@ export function startupRollbackExitCode(rollback) {
|
|
|
235
235
|
}
|
|
236
236
|
return 78;
|
|
237
237
|
}
|
|
238
|
-
export function proxyUsage() {
|
|
238
|
+
export function proxyUsage(command = 'evolver-proxy') {
|
|
239
239
|
return [
|
|
240
|
-
|
|
240
|
+
`Usage: ${command} [options]`,
|
|
241
241
|
'',
|
|
242
242
|
'Starts the local Evolver proxy daemon.',
|
|
243
243
|
'',
|
|
244
244
|
'Options (CLI overrides environment variables):',
|
|
245
|
-
' --home <dir>
|
|
246
|
-
' --
|
|
247
|
-
' --
|
|
248
|
-
' --
|
|
249
|
-
' -
|
|
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',
|
|
250
251
|
'',
|
|
251
252
|
'Required for public mode:',
|
|
252
253
|
' EVOMAP_NODE_SECRET or A2A_NODE_SECRET',
|
|
@@ -267,6 +268,7 @@ export function proxyUsage() {
|
|
|
267
268
|
}
|
|
268
269
|
const PROXY_PATH_FLAGS = new Map([
|
|
269
270
|
['--home', 'home'],
|
|
271
|
+
['--evomap-home', 'evomapHome'],
|
|
270
272
|
['--store', 'store'],
|
|
271
273
|
['--settings', 'settings'],
|
|
272
274
|
['--env-file', 'envFile'],
|
|
@@ -309,6 +311,11 @@ export function prepareProxyCliEnvironment(argv, env) {
|
|
|
309
311
|
env['EVOLVER_PROXY_SETTINGS_FILE'] = join(options.home, 'settings.json');
|
|
310
312
|
env['EVOLVER_LLM_TRACE_DIR'] = join(options.home, 'proxy', 'traces');
|
|
311
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;
|
|
312
319
|
if (options.store)
|
|
313
320
|
env['EVOLVER_PROXY_STORE'] = options.store;
|
|
314
321
|
if (options.settings)
|
|
@@ -346,7 +353,7 @@ export async function runProxyCli(options = {}) {
|
|
|
346
353
|
try {
|
|
347
354
|
const cliOptions = parseProxyCliPathOptions(argv);
|
|
348
355
|
if (cliOptions.help) {
|
|
349
|
-
process.stdout.write(proxyUsage());
|
|
356
|
+
process.stdout.write(proxyUsage(argv[0] === 'proxy' ? 'evolver proxy' : 'evolver-proxy'));
|
|
350
357
|
return 0;
|
|
351
358
|
}
|
|
352
359
|
const prepared = prepareProxyCliEnvironment(argv, env);
|
|
@@ -706,13 +713,29 @@ function readTrimmedFile(path) {
|
|
|
706
713
|
return undefined;
|
|
707
714
|
}
|
|
708
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
|
+
}
|
|
709
730
|
function readLegacyNodeSecret(env = process.env) {
|
|
710
|
-
const home
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
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;
|
|
716
739
|
}
|
|
717
740
|
// Durable copies of the legacy node_secret, cleared on hub-signalled divergence.
|
|
718
741
|
// Store keys mirror cli LOCAL_SECRET_STATE_KEYS (index.ts:82); on-disk files mirror
|
|
@@ -728,13 +751,17 @@ const LEGACY_SECRET_FILES = ['node_secret', 'node_secret_version'];
|
|
|
728
751
|
export function clearDivergedPublicNodeSecret(store, env = process.env) {
|
|
729
752
|
for (const key of LOCAL_SECRET_STATE_KEYS)
|
|
730
753
|
store.setState(key, '');
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
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
|
+
}
|
|
738
765
|
}
|
|
739
766
|
}
|
|
740
767
|
}
|
|
@@ -151,6 +151,7 @@ export declare class ProxyDaemon {
|
|
|
151
151
|
private recordTickError;
|
|
152
152
|
/** 启动: 锁 + IPC 监听 + 初次 hello. 返回 IPC 端口. */
|
|
153
153
|
start(): Promise<number>;
|
|
154
|
+
private listenIpc;
|
|
154
155
|
/** 单轮: core pump/TTL/wake + proxy 出站 + hub 入站 + 到点心跳. */
|
|
155
156
|
tick(): Promise<ProxyTickReport>;
|
|
156
157
|
/** 下一轮建议延时: inbound 背压/idle 与 outbound pending cadence 取更快者. */
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { dirname, join } from 'node:path';
|
|
2
|
-
import { mailbox, hub as hubNs, shadow as shadow_, assetstore } from '@evomap/evolver-core';
|
|
2
|
+
import { mailbox, hub as hubNs, shadow as shadow_, assetstore, util } from '@evomap/evolver-core';
|
|
3
3
|
import { SyncEngine, SYNC_INTERVALS } from '../sync/engine.js';
|
|
4
4
|
import { LifecycleManager } from '../lifecycle/manager.js';
|
|
5
5
|
import { executeForceUpdate } from '../selfUpdate/executor.js';
|
|
@@ -11,6 +11,7 @@ export const DEFAULT_IPC_PORT = 19820;
|
|
|
11
11
|
const MAX_TIMER_DELAY_MS = 2_147_483_647;
|
|
12
12
|
const MAX_PROXY_TICK_ERROR_LENGTH = 2_000;
|
|
13
13
|
const MAX_HEARTBEAT_TICK_ERROR_LENGTH = 1_000;
|
|
14
|
+
const MAX_EPHEMERAL_IPC_LISTEN_ATTEMPTS = 5;
|
|
14
15
|
/**
|
|
15
16
|
* ProxyDaemon(M6-4) 装配层: 把 core(MailboxStore/Dispatcher/MailboxDaemon/IpcServer) +
|
|
16
17
|
* HubBindings(M6-1) + SyncEngine(M6-2) + LifecycleManager(M6-3) 拼成系统级 proxy.
|
|
@@ -164,7 +165,7 @@ export class ProxyDaemon {
|
|
|
164
165
|
...(this.deps.onIpcAuthFailure ? { onAuthFailure: this.deps.onIpcAuthFailure } : {}),
|
|
165
166
|
extraRoutes: [(ctx) => this.handleProxyRoute(ctx)],
|
|
166
167
|
});
|
|
167
|
-
const port = await this.
|
|
168
|
+
const port = await this.listenIpc(this.ipc);
|
|
168
169
|
try {
|
|
169
170
|
this.deps.onIpcListen?.(port);
|
|
170
171
|
}
|
|
@@ -188,6 +189,22 @@ export class ProxyDaemon {
|
|
|
188
189
|
throw err;
|
|
189
190
|
}
|
|
190
191
|
}
|
|
192
|
+
async listenIpc(ipc) {
|
|
193
|
+
const requestedPort = this.deps.ipcPort ?? DEFAULT_IPC_PORT;
|
|
194
|
+
if (requestedPort !== 0)
|
|
195
|
+
return ipc.listen(requestedPort);
|
|
196
|
+
for (let attempt = 0; attempt < MAX_EPHEMERAL_IPC_LISTEN_ATTEMPTS; attempt += 1) {
|
|
197
|
+
const assignedPort = await ipc.listen(0);
|
|
198
|
+
if (!util.isFetchForbiddenPort(assignedPort))
|
|
199
|
+
return assignedPort;
|
|
200
|
+
// The outer start() cleanup owns the final listener when retries are exhausted.
|
|
201
|
+
if (attempt === MAX_EPHEMERAL_IPC_LISTEN_ATTEMPTS - 1) {
|
|
202
|
+
throw new Error('proxy_ipc_safe_port_unavailable');
|
|
203
|
+
}
|
|
204
|
+
await ipc.close();
|
|
205
|
+
}
|
|
206
|
+
throw new Error('proxy_ipc_safe_port_unavailable');
|
|
207
|
+
}
|
|
191
208
|
/** 单轮: core pump/TTL/wake + proxy 出站 + hub 入站 + 到点心跳. */
|
|
192
209
|
async tick() {
|
|
193
210
|
const errors = [];
|
|
@@ -19,12 +19,13 @@ export interface LegacyNodeIdCandidateOptions {
|
|
|
19
19
|
* and let the hub mint a duplicate. Probed FIRST and ADDITIVELY (the
|
|
20
20
|
* EVOLVER_HOME/EVOMAP_HOME candidates below are kept), so setting EVOMAP_DIR
|
|
21
21
|
* never hides an id that lives under one of the home overrides. Deduped.
|
|
22
|
-
* 1. `<
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
* the
|
|
22
|
+
* 1. `<identity home>/node_id` — the override-aware home files. `identityHomeCandidates()`
|
|
23
|
+
* probes EVOMAP_HOME first (THE identity home, #555 T2 — the evox agentDir split keeps
|
|
24
|
+
* node files under `<agentDir>/evomap` while EVOLVER_HOME points at the state root) and
|
|
25
|
+
* then EVOLVER_HOME (matching v1 `getEvomapDir`), after dropping blank/relative
|
|
26
|
+
* overrides. Probing both keeps the v1 #120 lesson — the reader walks every dir a
|
|
27
|
+
* writer may have used — while the order makes a split identity dir win over the
|
|
28
|
+
* state root when both hold files.
|
|
28
29
|
* 2. `~/.evomap/node_id` — the UNCONDITIONAL v1 location. v1's writer pivots on
|
|
29
30
|
* `getEvomapDir` = `EVOLVER_HOME || ~/.evomap` and ignores EVOMAP_HOME
|
|
30
31
|
* entirely, so unless EVOLVER_HOME was set a v1 file always physically lands
|
|
@@ -17,11 +17,22 @@ function cleanAbsolutePath(value) {
|
|
|
17
17
|
return undefined;
|
|
18
18
|
return isAbsolute(trimmed) ? trimmed : undefined;
|
|
19
19
|
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
20
|
+
// Identity homes in probe order (#555 T2): the explicit test/caller override wins outright; otherwise
|
|
21
|
+
// EVOMAP_HOME (THE identity home) outranks EVOLVER_HOME (the state root) so the evox agentDir split
|
|
22
|
+
// (`--evomap-home <agentDir>/evomap` + `--home <agentDir>/evolver`) reads node files from the evomap dir
|
|
23
|
+
// instead of missing them and falling through to the machine-global ~/.evomap node. Both homes stay in the
|
|
24
|
+
// candidate union, so single-home setups resolve exactly as before.
|
|
25
|
+
function identityHomeCandidates(opts, fallbackHomeDir) {
|
|
26
|
+
const fromOpts = cleanAbsolutePath(opts.evomapHomeDir);
|
|
27
|
+
if (fromOpts !== undefined)
|
|
28
|
+
return [fromOpts];
|
|
29
|
+
const homes = [
|
|
30
|
+
cleanAbsolutePath(process.env['EVOMAP_HOME']),
|
|
31
|
+
cleanAbsolutePath(process.env['EVOLVER_HOME']),
|
|
32
|
+
].filter((value) => value !== undefined);
|
|
33
|
+
if (homes.length > 0)
|
|
34
|
+
return homes;
|
|
35
|
+
return fallbackHomeDir === undefined ? [] : [join(fallbackHomeDir, '.evomap')];
|
|
25
36
|
}
|
|
26
37
|
function packageNameAt(dir) {
|
|
27
38
|
const pkgPath = join(dir, 'package.json');
|
|
@@ -66,12 +77,13 @@ function installRootNodeIdCandidates(moduleDir) {
|
|
|
66
77
|
* and let the hub mint a duplicate. Probed FIRST and ADDITIVELY (the
|
|
67
78
|
* EVOLVER_HOME/EVOMAP_HOME candidates below are kept), so setting EVOMAP_DIR
|
|
68
79
|
* never hides an id that lives under one of the home overrides. Deduped.
|
|
69
|
-
* 1. `<
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
* the
|
|
80
|
+
* 1. `<identity home>/node_id` — the override-aware home files. `identityHomeCandidates()`
|
|
81
|
+
* probes EVOMAP_HOME first (THE identity home, #555 T2 — the evox agentDir split keeps
|
|
82
|
+
* node files under `<agentDir>/evomap` while EVOLVER_HOME points at the state root) and
|
|
83
|
+
* then EVOLVER_HOME (matching v1 `getEvomapDir`), after dropping blank/relative
|
|
84
|
+
* overrides. Probing both keeps the v1 #120 lesson — the reader walks every dir a
|
|
85
|
+
* writer may have used — while the order makes a split identity dir win over the
|
|
86
|
+
* state root when both hold files.
|
|
75
87
|
* 2. `~/.evomap/node_id` — the UNCONDITIONAL v1 location. v1's writer pivots on
|
|
76
88
|
* `getEvomapDir` = `EVOLVER_HOME || ~/.evomap` and ignores EVOMAP_HOME
|
|
77
89
|
* entirely, so unless EVOLVER_HOME was set a v1 file always physically lands
|
|
@@ -97,12 +109,12 @@ function installRootNodeIdCandidates(moduleDir) {
|
|
|
97
109
|
export function legacyNodeIdCandidates(opts = {}) {
|
|
98
110
|
const home = cleanAbsolutePath(opts.homeDir) ?? cleanAbsolutePath(homedir());
|
|
99
111
|
const moduleDir = opts.moduleDir ?? _moduleDir;
|
|
100
|
-
const
|
|
112
|
+
const evomapHomes = identityHomeCandidates(opts, home);
|
|
101
113
|
const evomapDir = cleanAbsolutePath(opts.evomapDir) ?? cleanAbsolutePath(process.env['EVOMAP_DIR']);
|
|
102
114
|
return [
|
|
103
115
|
...new Set([
|
|
104
116
|
...(evomapDir === undefined ? [] : [join(evomapDir, 'node_id')]),
|
|
105
|
-
...(
|
|
117
|
+
...evomapHomes.map((dir) => join(dir, 'node_id')),
|
|
106
118
|
...(home === undefined ? [] : [join(home, '.evomap', 'node_id')]),
|
|
107
119
|
...installRootNodeIdCandidates(moduleDir),
|
|
108
120
|
]),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@evomap/evolver-proxy",
|
|
3
|
-
"version": "2.0.0-beta.
|
|
3
|
+
"version": "2.0.0-beta.6",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "系统级 mailbox/hub 同步 daemon (Node)",
|
|
@@ -26,8 +26,8 @@
|
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
28
|
"@aws-sdk/client-bedrock-runtime": "^3.1053.0",
|
|
29
|
-
"@evomap/evolver-adapter-public": "2.0.0-beta.
|
|
30
|
-
"@evomap/evolver-core": "2.0.0-beta.
|
|
29
|
+
"@evomap/evolver-adapter-public": "2.0.0-beta.6",
|
|
30
|
+
"@evomap/evolver-core": "2.0.0-beta.6"
|
|
31
31
|
},
|
|
32
32
|
"repository": {
|
|
33
33
|
"type": "git",
|