@evomap/evolver-proxy 2.0.0-beta.2 → 2.0.0-beta.5
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-llm-proxy.js +0 -0
- package/dist/bin/evolver-proxy.d.ts +68 -7
- package/dist/bin/evolver-proxy.js +387 -77
- package/dist/daemon/collaborationFacade.js +3 -3
- package/dist/llm/traceControl.js +1 -1
- 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/package.json +7 -3
|
File without changes
|
|
@@ -1,12 +1,68 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { mailbox } from '@evomap/evolver-core';
|
|
3
|
+
import { loadEnvFileFromEnv } from './envFile.js';
|
|
3
4
|
import { type SelfUpdatePolicy } from '../selfUpdate/policy.js';
|
|
4
5
|
import { type ReleaseBinaryOptions } from '../selfUpdate/releaseBinary.js';
|
|
6
|
+
import { rollbackDurableSelfUpdate, type SelfUpdateRecoveryOptions, type SelfUpdateRecoveryResult, type StagedBinaryProbe } from '../selfUpdate/transaction.js';
|
|
7
|
+
import { maybeRunWindowsUpdaterWorkerFromArgv } from '../selfUpdate/windowsUpdater.js';
|
|
8
|
+
import { maybeRunUnixRecoveryController } from '../selfUpdate/unixController.js';
|
|
9
|
+
import { maybeRunWindowsRecoveryController } from '../selfUpdate/windowsController.js';
|
|
5
10
|
import type { AtpProxyClient, ProxyDaemonDeps, ProxyTickReport } from '../daemon/proxyDaemon.js';
|
|
6
11
|
import type { HelloLifecycleMode, HelloResult, HeartbeatOptions, HeartbeatResult } from '../lifecycle/manager.js';
|
|
7
12
|
import type { InboundResult } from '../sync/engine.js';
|
|
8
|
-
|
|
9
|
-
|
|
13
|
+
/** evolver-proxy 系统级 daemon 入口(M6-7). EVOMAP_HUB_MODE/URL/NODE_SECRET 选址. */
|
|
14
|
+
interface RunProxyMainOptions {
|
|
15
|
+
environmentPrepared?: boolean;
|
|
16
|
+
}
|
|
17
|
+
export declare function runProxyMain(options?: RunProxyMainOptions): Promise<void>;
|
|
18
|
+
export declare function recoverBoundDurableSelfUpdate(options: Omit<SelfUpdateRecoveryOptions, 'beforeJournalMutation'>): Promise<SelfUpdateRecoveryResult>;
|
|
19
|
+
export declare function loadProxyEnvFile(env: NodeJS.ProcessEnv): ReturnType<typeof loadEnvFileFromEnv>;
|
|
20
|
+
interface StartupClosableStore {
|
|
21
|
+
close(): void;
|
|
22
|
+
}
|
|
23
|
+
interface StartupStoppableDaemon {
|
|
24
|
+
stop(): Promise<void>;
|
|
25
|
+
}
|
|
26
|
+
interface RollbackPendingStartupOptions {
|
|
27
|
+
storePath?: string;
|
|
28
|
+
store?: StartupClosableStore;
|
|
29
|
+
daemon?: StartupStoppableDaemon;
|
|
30
|
+
startupError: unknown;
|
|
31
|
+
env?: NodeJS.ProcessEnv;
|
|
32
|
+
rollback?: typeof rollbackDurableSelfUpdate;
|
|
33
|
+
openTelemetryStore?: (storePath: string) => StartupClosableStore;
|
|
34
|
+
persistTelemetry?: (store: StartupClosableStore, recovery: SelfUpdateRecoveryResult) => void;
|
|
35
|
+
logger?: {
|
|
36
|
+
write(chunk: string): unknown;
|
|
37
|
+
};
|
|
38
|
+
processExecPath?: string;
|
|
39
|
+
}
|
|
40
|
+
export declare function rollbackPendingStartup(options: RollbackPendingStartupOptions): Promise<SelfUpdateRecoveryResult>;
|
|
41
|
+
export declare function startupRollbackExitCode(rollback: SelfUpdateRecoveryResult): 78;
|
|
42
|
+
export declare function proxyUsage(command?: string): string;
|
|
43
|
+
export interface RunProxyCliOptions {
|
|
44
|
+
argv?: readonly string[];
|
|
45
|
+
env?: NodeJS.ProcessEnv;
|
|
46
|
+
platform?: NodeJS.Platform;
|
|
47
|
+
processExecPath?: string;
|
|
48
|
+
runMain?: () => Promise<void>;
|
|
49
|
+
runUnixRecoveryController?: typeof maybeRunUnixRecoveryController;
|
|
50
|
+
runWindowsRecoveryController?: typeof maybeRunWindowsRecoveryController;
|
|
51
|
+
runWindowsUpdaterWorker?: typeof maybeRunWindowsUpdaterWorkerFromArgv;
|
|
52
|
+
}
|
|
53
|
+
export interface ProxyCliPathOptions {
|
|
54
|
+
home?: string;
|
|
55
|
+
store?: string;
|
|
56
|
+
settings?: string;
|
|
57
|
+
envFile?: string;
|
|
58
|
+
help: boolean;
|
|
59
|
+
}
|
|
60
|
+
export declare function parseProxyCliPathOptions(argv: readonly string[]): ProxyCliPathOptions;
|
|
61
|
+
export declare function prepareProxyCliEnvironment(argv: readonly string[], env: NodeJS.ProcessEnv): {
|
|
62
|
+
options: ProxyCliPathOptions;
|
|
63
|
+
envFile: ReturnType<typeof loadEnvFileFromEnv>;
|
|
64
|
+
};
|
|
65
|
+
export declare function runProxyCli(options?: RunProxyCliOptions): Promise<number>;
|
|
10
66
|
type PrivateAdapterImporter = (specifier: string) => Promise<unknown>;
|
|
11
67
|
export interface RuntimeDeps {
|
|
12
68
|
mode: 'public' | 'private';
|
|
@@ -61,14 +117,19 @@ interface CreateProxyDaemonDepsOptions {
|
|
|
61
117
|
evolverVersion: string;
|
|
62
118
|
selfUpdatePolicy: SelfUpdatePolicy;
|
|
63
119
|
env?: NodeJS.ProcessEnv;
|
|
64
|
-
selfUpdateOverrides?:
|
|
65
|
-
|
|
120
|
+
selfUpdateOverrides?: SelfUpdateRuntimeOverrides;
|
|
121
|
+
}
|
|
122
|
+
interface SelfUpdateRuntimeOverrides extends ReleaseBinaryOptions {
|
|
123
|
+
restart?: () => void;
|
|
124
|
+
stagedBinaryProbe?: StagedBinaryProbe;
|
|
125
|
+
/** Test-only attestation seam. Production callers must use the real process executable. */
|
|
126
|
+
supervisorAttested?: {
|
|
127
|
+
processExecPath: string;
|
|
66
128
|
};
|
|
67
129
|
}
|
|
68
130
|
export declare function createProxyDaemonDeps(options: CreateProxyDaemonDepsOptions): ProxyDaemonDeps;
|
|
69
|
-
export declare function createSelfUpdateDeps(policy: SelfUpdatePolicy, currentVersion: string, env?: NodeJS.ProcessEnv, overrides?:
|
|
70
|
-
|
|
71
|
-
}): ProxyDaemonDeps['selfUpdate'];
|
|
131
|
+
export declare function createSelfUpdateDeps(policy: SelfUpdatePolicy, currentVersion: string, env?: NodeJS.ProcessEnv, overrides?: SelfUpdateRuntimeOverrides): ProxyDaemonDeps['selfUpdate'];
|
|
132
|
+
export declare function assertSelfUpdateProcessTargetBound(options: ReleaseBinaryOptions, allowUnresolvedTarget?: boolean): void;
|
|
72
133
|
export declare function resolvePublicNodeSecret(deps: RuntimeDeps): PublicNodeSecretSelection;
|
|
73
134
|
/**
|
|
74
135
|
* Hub rejected the cached node_secret as diverged (v1 a2aProtocol.js L1983-2017). Clear every
|
|
@@ -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,89 +14,240 @@ 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
|
-
|
|
68
|
-
|
|
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();
|
|
69
109
|
},
|
|
110
|
+
onIpcAuthFailure: publishLocalProxySettings,
|
|
70
111
|
});
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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);
|
|
89
138
|
proxySettingsState.url = `http://127.0.0.1:${port}`;
|
|
90
139
|
publishLocalProxySettings();
|
|
91
140
|
process.stdout.write(`[evolver-proxy] mode=${mode} hub=${hubUrl} ipc=127.0.0.1:${port} v=${evolverVersion} self-update=${selfUpdatePolicy}\n`);
|
|
92
|
-
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
|
+
});
|
|
93
150
|
}
|
|
94
|
-
export function
|
|
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;
|
|
208
|
+
}
|
|
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') {
|
|
95
239
|
return [
|
|
96
|
-
|
|
240
|
+
`Usage: ${command} [options]`,
|
|
97
241
|
'',
|
|
98
242
|
'Starts the local Evolver proxy daemon.',
|
|
99
243
|
'',
|
|
244
|
+
'Options (CLI overrides environment variables):',
|
|
245
|
+
' --home <dir> Root for assets, store, settings, and traces',
|
|
246
|
+
' --store <path> Mailbox store path (EVOLVER_PROXY_STORE)',
|
|
247
|
+
' --settings <path> Proxy settings file (EVOLVER_PROXY_SETTINGS_FILE)',
|
|
248
|
+
' --env-file <path> Environment file (EVOLVER_ENV_FILE)',
|
|
249
|
+
' -h, --help Show this help',
|
|
250
|
+
'',
|
|
100
251
|
'Required for public mode:',
|
|
101
252
|
' EVOMAP_NODE_SECRET or A2A_NODE_SECRET',
|
|
102
253
|
'',
|
|
@@ -114,16 +265,109 @@ export function proxyUsage() {
|
|
|
114
265
|
'',
|
|
115
266
|
].join('\n');
|
|
116
267
|
}
|
|
117
|
-
|
|
268
|
+
const PROXY_PATH_FLAGS = new Map([
|
|
269
|
+
['--home', 'home'],
|
|
270
|
+
['--store', 'store'],
|
|
271
|
+
['--settings', 'settings'],
|
|
272
|
+
['--env-file', 'envFile'],
|
|
273
|
+
]);
|
|
274
|
+
export function parseProxyCliPathOptions(argv) {
|
|
275
|
+
const options = { help: false };
|
|
276
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
277
|
+
const arg = argv[index];
|
|
278
|
+
if (arg === '--help' || arg === '-h') {
|
|
279
|
+
options.help = true;
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
282
|
+
const equalsIndex = arg.indexOf('=');
|
|
283
|
+
const flag = equalsIndex >= 0 ? arg.slice(0, equalsIndex) : arg;
|
|
284
|
+
const key = PROXY_PATH_FLAGS.get(flag);
|
|
285
|
+
if (key) {
|
|
286
|
+
const value = equalsIndex >= 0 ? arg.slice(equalsIndex + 1) : argv[++index];
|
|
287
|
+
if (!value?.trim() || (equalsIndex < 0 && value.startsWith('-'))) {
|
|
288
|
+
throw new Error(`${flag} requires a path`);
|
|
289
|
+
}
|
|
290
|
+
options[key] = resolve(expandHomePath(value.trim()));
|
|
291
|
+
continue;
|
|
292
|
+
}
|
|
293
|
+
if (arg.startsWith('-'))
|
|
294
|
+
throw new Error(`unknown option: ${arg}`);
|
|
295
|
+
}
|
|
296
|
+
return options;
|
|
297
|
+
}
|
|
298
|
+
export function prepareProxyCliEnvironment(argv, env) {
|
|
299
|
+
const options = parseProxyCliPathOptions(argv);
|
|
300
|
+
if (options.envFile)
|
|
301
|
+
env['EVOLVER_ENV_FILE'] = options.envFile;
|
|
302
|
+
const envFile = loadProxyEnvFile(env);
|
|
303
|
+
if (options.home) {
|
|
304
|
+
env['EVOMAP_DIR'] = options.home;
|
|
305
|
+
env['EVOLVER_HOME'] = options.home;
|
|
306
|
+
env['EVOMAP_HOME'] = options.home;
|
|
307
|
+
env['EVOLVER_SETTINGS_DIR'] = options.home;
|
|
308
|
+
env['EVOLVER_PROXY_STORE'] = join(options.home, 'proxy', 'mailbox.db');
|
|
309
|
+
env['EVOLVER_PROXY_SETTINGS_FILE'] = join(options.home, 'settings.json');
|
|
310
|
+
env['EVOLVER_LLM_TRACE_DIR'] = join(options.home, 'proxy', 'traces');
|
|
311
|
+
}
|
|
312
|
+
if (options.store)
|
|
313
|
+
env['EVOLVER_PROXY_STORE'] = options.store;
|
|
314
|
+
if (options.settings)
|
|
315
|
+
env['EVOLVER_PROXY_SETTINGS_FILE'] = options.settings;
|
|
316
|
+
return { options, envFile };
|
|
317
|
+
}
|
|
318
|
+
export async function runProxyCli(options = {}) {
|
|
319
|
+
const argv = options.argv ?? process.argv.slice(2);
|
|
320
|
+
const env = options.env ?? process.env;
|
|
321
|
+
const unixControllerExitCode = await (options.runUnixRecoveryController ?? maybeRunUnixRecoveryController)({
|
|
322
|
+
argv,
|
|
323
|
+
env,
|
|
324
|
+
platform: options.platform ?? process.platform,
|
|
325
|
+
processExecPath: options.processExecPath ?? process.execPath,
|
|
326
|
+
});
|
|
327
|
+
if (unixControllerExitCode !== undefined)
|
|
328
|
+
return unixControllerExitCode;
|
|
329
|
+
const windowsControllerExitCode = await (options.runWindowsRecoveryController ?? maybeRunWindowsRecoveryController)({
|
|
330
|
+
argv,
|
|
331
|
+
env,
|
|
332
|
+
platform: options.platform ?? process.platform,
|
|
333
|
+
processExecPath: options.processExecPath ?? process.execPath,
|
|
334
|
+
});
|
|
335
|
+
if (windowsControllerExitCode !== undefined)
|
|
336
|
+
return windowsControllerExitCode;
|
|
337
|
+
const workerExitCode = await (options.runWindowsUpdaterWorker ?? maybeRunWindowsUpdaterWorkerFromArgv)({
|
|
338
|
+
argv,
|
|
339
|
+
env,
|
|
340
|
+
platform: options.platform ?? process.platform,
|
|
341
|
+
processExecPath: options.processExecPath ?? process.execPath,
|
|
342
|
+
});
|
|
343
|
+
if (workerExitCode !== undefined)
|
|
344
|
+
return workerExitCode;
|
|
118
345
|
const uninstallUnhandledRejectionGuard = daemon.installUnhandledRejectionWindow();
|
|
119
|
-
|
|
346
|
+
try {
|
|
347
|
+
const cliOptions = parseProxyCliPathOptions(argv);
|
|
348
|
+
if (cliOptions.help) {
|
|
349
|
+
process.stdout.write(proxyUsage(argv[0] === 'proxy' ? 'evolver proxy' : 'evolver-proxy'));
|
|
350
|
+
return 0;
|
|
351
|
+
}
|
|
352
|
+
const prepared = prepareProxyCliEnvironment(argv, env);
|
|
353
|
+
if (prepared.envFile.error) {
|
|
354
|
+
process.stderr.write(`[evolver-proxy] failed to load EVOLVER_ENV_FILE: ${safeLoopMessage(prepared.envFile.error)}\n`);
|
|
355
|
+
}
|
|
356
|
+
await (options.runMain ?? (() => runProxyMain({ environmentPrepared: true })))();
|
|
357
|
+
return 0;
|
|
358
|
+
}
|
|
359
|
+
catch (error) {
|
|
360
|
+
process.stderr.write(`[evolver-proxy] fatal: ${safeLoopErrorMessage(error)}\n`);
|
|
361
|
+
return 1;
|
|
362
|
+
}
|
|
363
|
+
finally {
|
|
120
364
|
uninstallUnhandledRejectionGuard();
|
|
121
|
-
|
|
122
|
-
process.exit(1);
|
|
123
|
-
});
|
|
365
|
+
}
|
|
124
366
|
}
|
|
125
367
|
if (isDirectRun(import.meta.url, process.argv[1])) {
|
|
126
|
-
runProxyCli()
|
|
368
|
+
void runProxyCli().then((exitCode) => {
|
|
369
|
+
process.exitCode = exitCode;
|
|
370
|
+
});
|
|
127
371
|
}
|
|
128
372
|
export async function runProxyLoop(daemon, options = {}) {
|
|
129
373
|
const minDelayMs = options.minDelayMs ?? 1_000;
|
|
@@ -339,24 +583,90 @@ function errorMessage(err) {
|
|
|
339
583
|
export function createSelfUpdateDeps(policy, currentVersion, env = process.env, overrides = {}) {
|
|
340
584
|
if (policy !== 'auto')
|
|
341
585
|
return undefined;
|
|
586
|
+
const { supervisorAttested, restart, stagedBinaryProbe, processExecPath: _ignoredProcessExecPath, ...binaryOverrides } = overrides;
|
|
587
|
+
if (!supervisorAttested && !selfUpdateSupervisorAttested(env)) {
|
|
588
|
+
throw new Error('self_update_supervisor_required');
|
|
589
|
+
}
|
|
342
590
|
const publicKey = env['EVOLVER_SELF_UPDATE_PUBLIC_KEY']?.trim();
|
|
343
591
|
if (!publicKey)
|
|
344
592
|
throw new Error('self_update_public_key_required');
|
|
345
593
|
const releaseOpts = {
|
|
346
594
|
env,
|
|
347
|
-
...
|
|
595
|
+
...binaryOverrides,
|
|
596
|
+
processExecPath: supervisorAttested?.processExecPath ?? process.execPath,
|
|
348
597
|
requireSignedManifest: true,
|
|
349
598
|
};
|
|
599
|
+
const assertBound = () => assertSelfUpdateProcessTargetBound(releaseOpts);
|
|
600
|
+
assertBound();
|
|
350
601
|
return {
|
|
351
602
|
policy,
|
|
352
603
|
currentVersion,
|
|
353
|
-
resolveManifest: (directive) =>
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
604
|
+
resolveManifest: (directive) => {
|
|
605
|
+
assertBound();
|
|
606
|
+
return resolveGithubReleaseManifest(directive, releaseOpts);
|
|
607
|
+
},
|
|
608
|
+
download: (targetVersion, directive) => {
|
|
609
|
+
assertBound();
|
|
610
|
+
return downloadGithubReleaseArtifact(targetVersion, directive, releaseOpts);
|
|
611
|
+
},
|
|
612
|
+
atomicReplace: (stagedPath) => {
|
|
613
|
+
assertBound();
|
|
614
|
+
return atomicReplaceExecutable(stagedPath, releaseOpts);
|
|
615
|
+
},
|
|
616
|
+
beginTransaction: async (targetVersion) => {
|
|
617
|
+
assertBound();
|
|
618
|
+
const transaction = await beginDurableSelfUpdate(targetVersion, {
|
|
619
|
+
...releaseOpts,
|
|
620
|
+
currentVersion,
|
|
621
|
+
...(stagedBinaryProbe ? { stagedBinaryProbe } : {}),
|
|
622
|
+
});
|
|
623
|
+
return {
|
|
624
|
+
...transaction,
|
|
625
|
+
install: async () => {
|
|
626
|
+
assertBound();
|
|
627
|
+
await transaction.install();
|
|
628
|
+
},
|
|
629
|
+
};
|
|
630
|
+
},
|
|
631
|
+
restart: restart ?? (() => { process.exit(78); }),
|
|
357
632
|
publicKey,
|
|
358
633
|
};
|
|
359
634
|
}
|
|
635
|
+
export function assertSelfUpdateProcessTargetBound(options, allowUnresolvedTarget = false) {
|
|
636
|
+
let targetPath;
|
|
637
|
+
try {
|
|
638
|
+
targetPath = resolveSelfUpdateTarget(options).path;
|
|
639
|
+
}
|
|
640
|
+
catch {
|
|
641
|
+
if (allowUnresolvedTarget)
|
|
642
|
+
return;
|
|
643
|
+
throw new Error('self_update_process_target_mismatch');
|
|
644
|
+
}
|
|
645
|
+
const processExecPath = options.processExecPath ?? process.execPath;
|
|
646
|
+
try {
|
|
647
|
+
if (canonicalExecutablePath(processExecPath) !== canonicalExecutablePath(targetPath)) {
|
|
648
|
+
throw new Error('self_update_process_target_mismatch');
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
catch {
|
|
652
|
+
throw new Error('self_update_process_target_mismatch');
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
function canonicalExecutablePath(path) {
|
|
656
|
+
const canonical = realpathSync.native(path);
|
|
657
|
+
if (process.platform !== 'win32')
|
|
658
|
+
return resolve(canonical);
|
|
659
|
+
const withoutNamespace = canonical
|
|
660
|
+
.replace(/^\\\\\?\\UNC\\/i, '\\\\')
|
|
661
|
+
.replace(/^\\\\\?\\/i, '');
|
|
662
|
+
return win32.normalize(withoutNamespace).toLowerCase();
|
|
663
|
+
}
|
|
664
|
+
function selfUpdateSupervisorAttested(env) {
|
|
665
|
+
const supervisor = env['EVOLVER_SELF_UPDATE_SUPERVISOR']?.trim();
|
|
666
|
+
return supervisor === 'systemd'
|
|
667
|
+
|| supervisor === 'launchd'
|
|
668
|
+
|| supervisor === 'windows-scheduled-task';
|
|
669
|
+
}
|
|
360
670
|
export function resolvePublicNodeSecret(deps) {
|
|
361
671
|
const envNodeSecret = process.env['EVOMAP_NODE_SECRET'] ?? process.env['A2A_NODE_SECRET'];
|
|
362
672
|
// version env precedence MUST mirror the node_secret precedence above (EVOMAP-first) so an operator
|
|
@@ -721,10 +721,10 @@ function classifyOperationError(error) {
|
|
|
721
721
|
const candidate = error;
|
|
722
722
|
const status = numberValue(candidate?.statusCode) ?? numberValue(candidate?.status);
|
|
723
723
|
const signature = `${String(candidate?.name ?? '')} ${String(candidate?.code ?? '')}`;
|
|
724
|
-
if (status === 401 || /auth/i.test(signature))
|
|
725
|
-
return { status: 401, code: 'unauthorized', message: 'Hub authentication failed', retryable: false };
|
|
726
724
|
if (status === 403 || /forbidden|permission/i.test(signature))
|
|
727
725
|
return { status: 403, code: 'forbidden', message: 'Hub permission denied', retryable: false };
|
|
726
|
+
if (status === 401 || /auth/i.test(signature))
|
|
727
|
+
return { status: 401, code: 'unauthorized', message: 'Hub authentication failed', retryable: false };
|
|
728
728
|
if (status === 404)
|
|
729
729
|
return { status: 404, code: 'not_found', message: 'Task or claim not found', retryable: false };
|
|
730
730
|
if (status === 409 || /conflict|already_claimed/i.test(signature))
|
|
@@ -741,7 +741,7 @@ function classifyOperationError(error) {
|
|
|
741
741
|
function isDurablyRecoverableAuthError(error) {
|
|
742
742
|
const candidate = error;
|
|
743
743
|
const status = numberValue(candidate?.statusCode) ?? numberValue(candidate?.status);
|
|
744
|
-
return
|
|
744
|
+
return status === 401;
|
|
745
745
|
}
|
|
746
746
|
class FacadeTimeoutError extends Error {
|
|
747
747
|
}
|
package/dist/llm/traceControl.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { verify } from 'node:crypto';
|
|
2
2
|
export const DEFAULT_TRACE_CONFIG_SIGNING_PUBLIC_KEY = [
|
|
3
3
|
'-----BEGIN PUBLIC KEY-----',
|
|
4
|
-
'MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEA7kJvWUP3HC4FJPQtkh74',
|
|
4
|
+
'MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEA7kJvWUP3HC4FJPQtkh74', // gitleaks:allow -- public verification key, not a credential.
|
|
5
5
|
'y75h9Rzc2NSZC9e4fiIWdax4iv+yWeMeIHGNsMr7YI8Ws7ck1BimJWt026gwRW8I',
|
|
6
6
|
'c2A7h97oZQ0Z0zFcjEZ8FpYFSu++Yz/dGrARAV7uCQg289jvo89F5fWNdX2k+lTH',
|
|
7
7
|
'hBoBm0G71vkiAYlbQEjq1xm1WzYf8CVXmbr+J1z+ydQf9jczcFL79u3eQZhIPs3R',
|