@evomap/evolver-proxy 2.0.0-beta.1 → 2.0.0-beta.10
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
|
@@ -56,6 +56,8 @@ export interface ProxyDaemonDeps {
|
|
|
56
56
|
dir: string;
|
|
57
57
|
env?: NodeJS.ProcessEnv;
|
|
58
58
|
};
|
|
59
|
+
/** V1 collaboration task operations are synchronous; tests may shorten the Hub timeout. */
|
|
60
|
+
collaborationOperationTimeoutMs?: number;
|
|
59
61
|
}
|
|
60
62
|
export interface ProxyTickReport {
|
|
61
63
|
outbound: OutboundResult;
|
|
@@ -118,6 +120,7 @@ export declare class ProxyDaemon {
|
|
|
118
120
|
private readonly reuseResultReporter;
|
|
119
121
|
private readonly validator;
|
|
120
122
|
private readonly atp;
|
|
123
|
+
private readonly collaborationFacade;
|
|
121
124
|
private ipc;
|
|
122
125
|
private readonly now;
|
|
123
126
|
private readonly random;
|
|
@@ -148,6 +151,7 @@ export declare class ProxyDaemon {
|
|
|
148
151
|
private recordTickError;
|
|
149
152
|
/** 启动: 锁 + IPC 监听 + 初次 hello. 返回 IPC 端口. */
|
|
150
153
|
start(): Promise<number>;
|
|
154
|
+
private listenIpc;
|
|
151
155
|
/** 单轮: core pump/TTL/wake + proxy 出站 + hub 入站 + 到点心跳. */
|
|
152
156
|
tick(): Promise<ProxyTickReport>;
|
|
153
157
|
/** 下一轮建议延时: inbound 背压/idle 与 outbound pending cadence 取更快者. */
|
|
@@ -1,15 +1,17 @@
|
|
|
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';
|
|
6
6
|
import { reportPendingSelfUpdateLastUpdate, reportSelfUpdateLastUpdate } from '../selfUpdate/lastUpdate.js';
|
|
7
7
|
import { backfillProxyTraceUploads } from '../llm/traceBackfill.js';
|
|
8
8
|
import { hubAuthFailureHint } from './selectHub.js';
|
|
9
|
+
import { CollaborationFacade } from './collaborationFacade.js';
|
|
9
10
|
export const DEFAULT_IPC_PORT = 19820;
|
|
10
11
|
const MAX_TIMER_DELAY_MS = 2_147_483_647;
|
|
11
12
|
const MAX_PROXY_TICK_ERROR_LENGTH = 2_000;
|
|
12
13
|
const MAX_HEARTBEAT_TICK_ERROR_LENGTH = 1_000;
|
|
14
|
+
const MAX_EPHEMERAL_IPC_LISTEN_ATTEMPTS = 5;
|
|
13
15
|
/**
|
|
14
16
|
* ProxyDaemon(M6-4) 装配层: 把 core(MailboxStore/Dispatcher/MailboxDaemon/IpcServer) +
|
|
15
17
|
* HubBindings(M6-1) + SyncEngine(M6-2) + LifecycleManager(M6-3) 拼成系统级 proxy.
|
|
@@ -28,6 +30,7 @@ export class ProxyDaemon {
|
|
|
28
30
|
reuseResultReporter;
|
|
29
31
|
validator;
|
|
30
32
|
atp;
|
|
33
|
+
collaborationFacade;
|
|
31
34
|
ipc;
|
|
32
35
|
now;
|
|
33
36
|
random;
|
|
@@ -93,9 +96,20 @@ export class ProxyDaemon {
|
|
|
93
96
|
pumpHandlers: ['core'], // proxy 出站归 SyncEngine, 不在此双 claim
|
|
94
97
|
...(deps.lockPath ? { lockPath: deps.lockPath } : {}),
|
|
95
98
|
});
|
|
99
|
+
this.collaborationFacade = new CollaborationFacade({
|
|
100
|
+
store: this.store,
|
|
101
|
+
hub: hubToUse,
|
|
102
|
+
now: this.now,
|
|
103
|
+
notifyOutbound: () => this.notifyNewOutbound(),
|
|
104
|
+
...(deps.runtimeNamespace ? { runtimeNamespace: deps.runtimeNamespace } : {}),
|
|
105
|
+
...(deps.collaborationOperationTimeoutMs !== undefined ? { operationTimeoutMs: deps.collaborationOperationTimeoutMs } : {}),
|
|
106
|
+
});
|
|
96
107
|
this.sync = new SyncEngine({
|
|
97
108
|
store: this.store, hub: hubToUse, proxyHandler, now: this.now,
|
|
98
109
|
...(deps.runtimeNamespace ? { runtimeNamespace: deps.runtimeNamespace } : {}),
|
|
110
|
+
onOutboundSucceeded: (envelope, result) => this.collaborationFacade.handleOutboundSucceeded(envelope, result),
|
|
111
|
+
onOutboundTerminal: (envelope, error) => this.collaborationFacade.handleOutboundTerminal(envelope, error),
|
|
112
|
+
normalizeInboundEnvelope: (envelope) => this.collaborationFacade.normalizeInboundEnvelope(envelope),
|
|
99
113
|
...(deps.traceBackfill ? { onOutboundFlushed: () => { this.drainProxyTraceBackfill(); } } : {}),
|
|
100
114
|
});
|
|
101
115
|
this.lifecycle = new LifecycleManager({
|
|
@@ -151,7 +165,7 @@ export class ProxyDaemon {
|
|
|
151
165
|
...(this.deps.onIpcAuthFailure ? { onAuthFailure: this.deps.onIpcAuthFailure } : {}),
|
|
152
166
|
extraRoutes: [(ctx) => this.handleProxyRoute(ctx)],
|
|
153
167
|
});
|
|
154
|
-
const port = await this.
|
|
168
|
+
const port = await this.listenIpc(this.ipc);
|
|
155
169
|
try {
|
|
156
170
|
this.deps.onIpcListen?.(port);
|
|
157
171
|
}
|
|
@@ -175,6 +189,22 @@ export class ProxyDaemon {
|
|
|
175
189
|
throw err;
|
|
176
190
|
}
|
|
177
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
|
+
}
|
|
178
208
|
/** 单轮: core pump/TTL/wake + proxy 出站 + hub 入站 + 到点心跳. */
|
|
179
209
|
async tick() {
|
|
180
210
|
const errors = [];
|
|
@@ -555,6 +585,8 @@ export class ProxyDaemon {
|
|
|
555
585
|
return Number.isFinite(n) && n > 0 ? n : null;
|
|
556
586
|
}
|
|
557
587
|
async handleProxyRoute(ctx) {
|
|
588
|
+
if (await this.collaborationFacade.handle(ctx))
|
|
589
|
+
return true;
|
|
558
590
|
const handledAtp = await this.handleAtpRoute(ctx);
|
|
559
591
|
if (handledAtp)
|
|
560
592
|
return true;
|
|
@@ -711,6 +743,60 @@ export class ProxyDaemon {
|
|
|
711
743
|
ctx.json(200, { ...distill, queued: submission !== null, submission });
|
|
712
744
|
return true;
|
|
713
745
|
}
|
|
746
|
+
if (ctx.route === 'POST /agent/search') {
|
|
747
|
+
const body = asRecord(await ctx.readJson());
|
|
748
|
+
const directory = this.deps.hub.agentDirectory ?? hubNs.unsupportedAgentDirectoryCapability();
|
|
749
|
+
const parsed = parseAgentSearchRequest(body);
|
|
750
|
+
if (!parsed.ok) {
|
|
751
|
+
respondAgentDirectory(ctx, parsed);
|
|
752
|
+
return true;
|
|
753
|
+
}
|
|
754
|
+
const result = await directory.search(parsed.value);
|
|
755
|
+
respondAgentDirectory(ctx, result);
|
|
756
|
+
return true;
|
|
757
|
+
}
|
|
758
|
+
if (ctx.route === 'POST /agent/profile') {
|
|
759
|
+
const body = asRecord(await ctx.readJson());
|
|
760
|
+
const directory = this.deps.hub.agentDirectory ?? hubNs.unsupportedAgentDirectoryCapability();
|
|
761
|
+
let agentId;
|
|
762
|
+
let timeoutMs;
|
|
763
|
+
try {
|
|
764
|
+
agentId = hubNs.normalizeAgentId(typeof body['agent_id'] === 'string' ? body['agent_id'] : '');
|
|
765
|
+
timeoutMs = hubNs.normalizeAgentDirectoryTimeout(typeof body['timeout_ms'] === 'number' ? body['timeout_ms'] : undefined);
|
|
766
|
+
}
|
|
767
|
+
catch (error) {
|
|
768
|
+
respondAgentDirectory(ctx, invalidAgentDirectoryRequest(error));
|
|
769
|
+
return true;
|
|
770
|
+
}
|
|
771
|
+
const result = await directory.getProfile(agentId, { timeoutMs });
|
|
772
|
+
respondAgentDirectory(ctx, result);
|
|
773
|
+
return true;
|
|
774
|
+
}
|
|
775
|
+
if (ctx.route === 'POST /agent/discover') {
|
|
776
|
+
const body = asRecord(await ctx.readJson());
|
|
777
|
+
const directory = this.deps.hub.agentDirectory ?? hubNs.unsupportedAgentDirectoryCapability();
|
|
778
|
+
let request;
|
|
779
|
+
try {
|
|
780
|
+
request = hubNs.normalizeAgentTaskDiscoveryRequest({
|
|
781
|
+
title: typeof body['title'] === 'string' ? body['title'] : '',
|
|
782
|
+
...(typeof body['description'] === 'string' ? { description: body['description'] } : {}),
|
|
783
|
+
...(Array.isArray(body['signals']) ? { signals: body['signals'] } : {}),
|
|
784
|
+
...(typeof body['availability'] === 'string' ? { availability: body['availability'] } : {}),
|
|
785
|
+
...(typeof body['sort'] === 'string' ? { sort: body['sort'] } : {}),
|
|
786
|
+
...(typeof body['order'] === 'string' ? { order: body['order'] } : {}),
|
|
787
|
+
...(typeof body['cursor'] === 'string' ? { cursor: body['cursor'] } : {}),
|
|
788
|
+
...(typeof body['limit'] === 'number' ? { limit: body['limit'] } : {}),
|
|
789
|
+
...(typeof body['timeout_ms'] === 'number' ? { timeoutMs: body['timeout_ms'] } : {}),
|
|
790
|
+
});
|
|
791
|
+
}
|
|
792
|
+
catch (error) {
|
|
793
|
+
respondAgentDirectory(ctx, invalidAgentDirectoryRequest(error));
|
|
794
|
+
return true;
|
|
795
|
+
}
|
|
796
|
+
const result = await directory.discoverForTask(request);
|
|
797
|
+
respondAgentDirectory(ctx, result);
|
|
798
|
+
return true;
|
|
799
|
+
}
|
|
714
800
|
}
|
|
715
801
|
async searchAssets(query) {
|
|
716
802
|
const limit = Math.max(1, Math.min(Number(query.limit ?? 5), 25));
|
|
@@ -862,6 +948,48 @@ function assetKind(value) {
|
|
|
862
948
|
function asRecord(value) {
|
|
863
949
|
return value && typeof value === 'object' && !Array.isArray(value) ? value : {};
|
|
864
950
|
}
|
|
951
|
+
function respondAgentDirectory(ctx, result) {
|
|
952
|
+
if (result.ok) {
|
|
953
|
+
ctx.json(200, result);
|
|
954
|
+
return;
|
|
955
|
+
}
|
|
956
|
+
const status = {
|
|
957
|
+
invalid_request: 400,
|
|
958
|
+
permission_denied: 403,
|
|
959
|
+
capability_unavailable: 501,
|
|
960
|
+
invalid_response: 502,
|
|
961
|
+
hub_unavailable: 503,
|
|
962
|
+
timeout: 504,
|
|
963
|
+
}[result.error.code];
|
|
964
|
+
ctx.json(status, result);
|
|
965
|
+
}
|
|
966
|
+
function parseAgentSearchRequest(body) {
|
|
967
|
+
try {
|
|
968
|
+
return { ok: true, value: hubNs.normalizeAgentSearchRequest({
|
|
969
|
+
...(typeof body['query'] === 'string' ? { query: body['query'] } : {}),
|
|
970
|
+
...(Array.isArray(body['signals']) ? { signals: body['signals'] } : {}),
|
|
971
|
+
...(typeof body['availability'] === 'string' ? { availability: body['availability'] } : {}),
|
|
972
|
+
...(typeof body['sort'] === 'string' ? { sort: body['sort'] } : {}),
|
|
973
|
+
...(typeof body['order'] === 'string' ? { order: body['order'] } : {}),
|
|
974
|
+
...(typeof body['cursor'] === 'string' ? { cursor: body['cursor'] } : {}),
|
|
975
|
+
...(typeof body['limit'] === 'number' ? { limit: body['limit'] } : {}),
|
|
976
|
+
...(typeof body['timeout_ms'] === 'number' ? { timeoutMs: body['timeout_ms'] } : {}),
|
|
977
|
+
}) };
|
|
978
|
+
}
|
|
979
|
+
catch (error) {
|
|
980
|
+
return invalidAgentDirectoryRequest(error);
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
function invalidAgentDirectoryRequest(error) {
|
|
984
|
+
return {
|
|
985
|
+
ok: false,
|
|
986
|
+
error: {
|
|
987
|
+
code: 'invalid_request',
|
|
988
|
+
retryable: false,
|
|
989
|
+
message: error instanceof Error ? error.message.slice(0, 120) : 'invalid_request',
|
|
990
|
+
},
|
|
991
|
+
};
|
|
992
|
+
}
|
|
865
993
|
function stringBody(body, key) {
|
|
866
994
|
const v = body[key];
|
|
867
995
|
return typeof v === 'string' && v.length > 0 ? v : undefined;
|
package/dist/daemon/selectHub.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { resolveHubUrl as resolvePublicHubUrl } from '@evomap/evolver-adapter-public';
|
|
1
2
|
/** 据 EVOMAP_HUB_MODE 选 hub 实现(public|private). 缺省 public. bin 据此挂对应 adapter. */
|
|
2
3
|
export function resolveHubMode(env) {
|
|
3
4
|
const m = (env['EVOMAP_HUB_MODE'] ?? 'public').toLowerCase();
|
|
@@ -6,7 +7,22 @@ export function resolveHubMode(env) {
|
|
|
6
7
|
return m;
|
|
7
8
|
}
|
|
8
9
|
export function resolveHubUrl(env) {
|
|
9
|
-
|
|
10
|
+
if ((env['EVOMAP_HUB_MODE'] ?? 'public').toLowerCase() === 'private')
|
|
11
|
+
return resolvePrivateHubUrl(env);
|
|
12
|
+
return resolvePublicHubUrl(env);
|
|
13
|
+
}
|
|
14
|
+
function resolvePrivateHubUrl(env) {
|
|
15
|
+
return trimmed(env['EVOMAP_HUB_URL'])
|
|
16
|
+
?? trimmed(env['A2A_HUB_URL'])
|
|
17
|
+
?? trimmed(env['EVOLVER_DEFAULT_HUB_URL'])
|
|
18
|
+
?? resolvePublicHubUrl({});
|
|
19
|
+
}
|
|
20
|
+
function trimmed(value) {
|
|
21
|
+
const v = value?.trim();
|
|
22
|
+
if (!v)
|
|
23
|
+
return undefined;
|
|
24
|
+
const normalized = v.replace(/\/+$/, '');
|
|
25
|
+
return normalized || undefined;
|
|
10
26
|
}
|
|
11
27
|
/**
|
|
12
28
|
* Actionable hint for a hub AUTH failure (401/403), tailored to the hub's error code so it does NOT misdirect
|
package/dist/index.d.ts
CHANGED
|
@@ -5,4 +5,5 @@ export * from './daemon/proxyDaemon.js';
|
|
|
5
5
|
export * from './lifecycle/deployGuard.js';
|
|
6
6
|
export * from './router/index.js';
|
|
7
7
|
export * from './llm/index.js';
|
|
8
|
-
export * from './selfUpdate/index.js';
|
|
8
|
+
export * from './selfUpdate/index.js';
|
|
9
|
+
export * from './private/adapterLoader.js';
|
package/dist/index.js
CHANGED
|
@@ -5,4 +5,5 @@ export * from './daemon/proxyDaemon.js';
|
|
|
5
5
|
export * from './lifecycle/deployGuard.js';
|
|
6
6
|
export * from './router/index.js';
|
|
7
7
|
export * from './llm/index.js';
|
|
8
|
-
export * from './selfUpdate/index.js';
|
|
8
|
+
export * from './selfUpdate/index.js';
|
|
9
|
+
export * from './private/adapterLoader.js';
|
|
@@ -12,31 +12,29 @@ export interface LegacyNodeIdCandidateOptions {
|
|
|
12
12
|
* Legacy node_id file locations, in priority order. Mirror of v1
|
|
13
13
|
* `_loadPersistedNodeId`:
|
|
14
14
|
*
|
|
15
|
-
* 0. `<
|
|
15
|
+
* 0. `<EVOMAP_HOME>/node_id` — when an explicit identity home is configured, it
|
|
16
|
+
* must precede every state-root candidate. The legacy node_secret resolver uses
|
|
17
|
+
* the same priority, keeping both credentials on one identity in split layouts.
|
|
18
|
+
* 1. `<EVOMAP_DIR>/node_id` — the explicit dir the CLI recipe / ATP / proxy
|
|
16
19
|
* anti-abuse code write under (`recipeHomeCandidates` puts EVOMAP_DIR ahead
|
|
17
20
|
* of EVOLVER_HOME/EVOMAP_HOME). `resolveEvomapHome` never consults EVOMAP_DIR,
|
|
18
21
|
* so a deployment that pivots on it would otherwise send no node_id on hello
|
|
19
|
-
* and let the hub mint a duplicate.
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* EVOMAP_HOME after dropping blank/relative overrides. It wins first so a
|
|
25
|
-
* home relocated via EVOLVER_HOME reads the file the v1 writer put there —
|
|
26
|
-
* the lesson of v1 #120, which routed the reader through the same helper as
|
|
27
|
-
* the writer.
|
|
28
|
-
* 2. `~/.evomap/node_id` — the UNCONDITIONAL v1 location. v1's writer pivots on
|
|
22
|
+
* and let the hub mint a duplicate. It remains first when EVOMAP_HOME is not
|
|
23
|
+
* configured, preserving the existing EVOMAP_DIR-only contract. Deduped.
|
|
24
|
+
* 2. `<EVOLVER_HOME>/node_id` — the state-home compatibility candidate, kept
|
|
25
|
+
* after the identity roots so readers still walk every dir a writer may have used.
|
|
26
|
+
* 3. `~/.evomap/node_id` — the UNCONDITIONAL v1 location. v1's writer pivots on
|
|
29
27
|
* `getEvomapDir` = `EVOLVER_HOME || ~/.evomap` and ignores EVOMAP_HOME
|
|
30
28
|
* entirely, so unless EVOLVER_HOME was set a v1 file always physically lands
|
|
31
29
|
* here. We probe it explicitly so a v2 install that sets only EVOMAP_HOME
|
|
32
30
|
* (which steers candidate 1 away from `~/.evomap`) still recovers the v1
|
|
33
31
|
* identity instead of letting the hub mint a duplicate orphan node. Deduped
|
|
34
32
|
* against candidate 1 for the common no-override case where they coincide.
|
|
35
|
-
*
|
|
33
|
+
* 4. `<proxy package>/.evomap_node_id` — the install-root file the writer falls
|
|
36
34
|
* back to when `~/.evomap/` isn't writable (read-only $HOME in
|
|
37
35
|
* containers / restricted CI). Kept first for parity with the old v2
|
|
38
36
|
* candidate.
|
|
39
|
-
*
|
|
37
|
+
* 5. `<outer package/workspace root>/.evomap_node_id` — the v1/outer install
|
|
40
38
|
* root fallback. In v2's multi-package layout the proxy code lives below
|
|
41
39
|
* `packages/evolver-proxy`, so only checking the proxy package root misses
|
|
42
40
|
* a file written at the install/workspace root.
|
|
@@ -17,11 +17,23 @@ 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
|
+
// legacyNodeIdCandidates() splices that explicit identity home ahead of EVOMAP_DIR so node_id and
|
|
24
|
+
// node_secret cannot come from different halves of the split layout. Both homes stay in the candidate union,
|
|
25
|
+
// so single-home setups resolve exactly as before.
|
|
26
|
+
function identityHomeCandidates(opts, fallbackHomeDir) {
|
|
27
|
+
const fromOpts = cleanAbsolutePath(opts.evomapHomeDir);
|
|
28
|
+
if (fromOpts !== undefined)
|
|
29
|
+
return [fromOpts];
|
|
30
|
+
const homes = [
|
|
31
|
+
cleanAbsolutePath(process.env['EVOMAP_HOME']),
|
|
32
|
+
cleanAbsolutePath(process.env['EVOLVER_HOME']),
|
|
33
|
+
].filter((value) => value !== undefined);
|
|
34
|
+
if (homes.length > 0)
|
|
35
|
+
return homes;
|
|
36
|
+
return fallbackHomeDir === undefined ? [] : [join(fallbackHomeDir, '.evomap')];
|
|
25
37
|
}
|
|
26
38
|
function packageNameAt(dir) {
|
|
27
39
|
const pkgPath = join(dir, 'package.json');
|
|
@@ -59,31 +71,29 @@ function installRootNodeIdCandidates(moduleDir) {
|
|
|
59
71
|
* Legacy node_id file locations, in priority order. Mirror of v1
|
|
60
72
|
* `_loadPersistedNodeId`:
|
|
61
73
|
*
|
|
62
|
-
* 0. `<
|
|
74
|
+
* 0. `<EVOMAP_HOME>/node_id` — when an explicit identity home is configured, it
|
|
75
|
+
* must precede every state-root candidate. The legacy node_secret resolver uses
|
|
76
|
+
* the same priority, keeping both credentials on one identity in split layouts.
|
|
77
|
+
* 1. `<EVOMAP_DIR>/node_id` — the explicit dir the CLI recipe / ATP / proxy
|
|
63
78
|
* anti-abuse code write under (`recipeHomeCandidates` puts EVOMAP_DIR ahead
|
|
64
79
|
* of EVOLVER_HOME/EVOMAP_HOME). `resolveEvomapHome` never consults EVOMAP_DIR,
|
|
65
80
|
* so a deployment that pivots on it would otherwise send no node_id on hello
|
|
66
|
-
* and let the hub mint a duplicate.
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
* EVOMAP_HOME after dropping blank/relative overrides. It wins first so a
|
|
72
|
-
* home relocated via EVOLVER_HOME reads the file the v1 writer put there —
|
|
73
|
-
* the lesson of v1 #120, which routed the reader through the same helper as
|
|
74
|
-
* the writer.
|
|
75
|
-
* 2. `~/.evomap/node_id` — the UNCONDITIONAL v1 location. v1's writer pivots on
|
|
81
|
+
* and let the hub mint a duplicate. It remains first when EVOMAP_HOME is not
|
|
82
|
+
* configured, preserving the existing EVOMAP_DIR-only contract. Deduped.
|
|
83
|
+
* 2. `<EVOLVER_HOME>/node_id` — the state-home compatibility candidate, kept
|
|
84
|
+
* after the identity roots so readers still walk every dir a writer may have used.
|
|
85
|
+
* 3. `~/.evomap/node_id` — the UNCONDITIONAL v1 location. v1's writer pivots on
|
|
76
86
|
* `getEvomapDir` = `EVOLVER_HOME || ~/.evomap` and ignores EVOMAP_HOME
|
|
77
87
|
* entirely, so unless EVOLVER_HOME was set a v1 file always physically lands
|
|
78
88
|
* here. We probe it explicitly so a v2 install that sets only EVOMAP_HOME
|
|
79
89
|
* (which steers candidate 1 away from `~/.evomap`) still recovers the v1
|
|
80
90
|
* identity instead of letting the hub mint a duplicate orphan node. Deduped
|
|
81
91
|
* against candidate 1 for the common no-override case where they coincide.
|
|
82
|
-
*
|
|
92
|
+
* 4. `<proxy package>/.evomap_node_id` — the install-root file the writer falls
|
|
83
93
|
* back to when `~/.evomap/` isn't writable (read-only $HOME in
|
|
84
94
|
* containers / restricted CI). Kept first for parity with the old v2
|
|
85
95
|
* candidate.
|
|
86
|
-
*
|
|
96
|
+
* 5. `<outer package/workspace root>/.evomap_node_id` — the v1/outer install
|
|
87
97
|
* root fallback. In v2's multi-package layout the proxy code lives below
|
|
88
98
|
* `packages/evolver-proxy`, so only checking the proxy package root misses
|
|
89
99
|
* a file written at the install/workspace root.
|
|
@@ -97,12 +107,17 @@ function installRootNodeIdCandidates(moduleDir) {
|
|
|
97
107
|
export function legacyNodeIdCandidates(opts = {}) {
|
|
98
108
|
const home = cleanAbsolutePath(opts.homeDir) ?? cleanAbsolutePath(homedir());
|
|
99
109
|
const moduleDir = opts.moduleDir ?? _moduleDir;
|
|
100
|
-
const
|
|
110
|
+
const evomapHomes = identityHomeCandidates(opts, home);
|
|
111
|
+
const identityHome = cleanAbsolutePath(opts.evomapHomeDir)
|
|
112
|
+
?? cleanAbsolutePath(process.env['EVOMAP_HOME']);
|
|
101
113
|
const evomapDir = cleanAbsolutePath(opts.evomapDir) ?? cleanAbsolutePath(process.env['EVOMAP_DIR']);
|
|
102
114
|
return [
|
|
103
115
|
...new Set([
|
|
116
|
+
...(identityHome === undefined ? [] : [join(identityHome, 'node_id')]),
|
|
104
117
|
...(evomapDir === undefined ? [] : [join(evomapDir, 'node_id')]),
|
|
105
|
-
...
|
|
118
|
+
...evomapHomes
|
|
119
|
+
.filter((dir) => dir !== identityHome)
|
|
120
|
+
.map((dir) => join(dir, 'node_id')),
|
|
106
121
|
...(home === undefined ? [] : [join(home, '.evomap', 'node_id')]),
|
|
107
122
|
...installRootNodeIdCandidates(moduleDir),
|
|
108
123
|
]),
|
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',
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { hub } from '@evomap/evolver-core';
|
|
2
|
+
import { type AccountAssetListOptions, type AccountAssetListResult } from '@evomap/evolver-adapter-public';
|
|
3
|
+
export interface PrivateAccountAssetHub {
|
|
4
|
+
listAccountAssets(opts: AccountAssetListOptions): Promise<AccountAssetListResult>;
|
|
5
|
+
}
|
|
6
|
+
interface PrivateCompatibilityResponse {
|
|
7
|
+
status: number;
|
|
8
|
+
json(): Promise<unknown>;
|
|
9
|
+
}
|
|
10
|
+
export type PrivateCompatibilityFetch = (url: string, init: {
|
|
11
|
+
method: string;
|
|
12
|
+
headers: Record<string, string>;
|
|
13
|
+
body?: string;
|
|
14
|
+
}) => Promise<PrivateCompatibilityResponse>;
|
|
15
|
+
interface PrivateAccountAssetCompatibilityOptions {
|
|
16
|
+
baseUrl: string;
|
|
17
|
+
auth: hub.AuthProvider;
|
|
18
|
+
senderId: () => string | undefined;
|
|
19
|
+
env: Record<string, string | undefined>;
|
|
20
|
+
fetchFn?: PrivateCompatibilityFetch;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Older official private adapters predate account inventory listing. Keep the
|
|
24
|
+
* compatibility wire at the private composition edge, and never replace a
|
|
25
|
+
* future adapter's native implementation.
|
|
26
|
+
*/
|
|
27
|
+
export declare function withPrivateAccountAssetCompatibility<T extends object>(hubCapability: T, opts: PrivateAccountAssetCompatibilityOptions): T & PrivateAccountAssetHub;
|
|
28
|
+
export {};
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { AuthError, HubClientError, HubUnreachableError, } from '@evomap/evolver-adapter-public';
|
|
2
|
+
const PRIVATE_PUBLISHED_ASSETS_PATH = '/a2a/assets/published-by-me';
|
|
3
|
+
const PRIVATE_PUBLISHED_MAX_PAGE_SIZE = 500;
|
|
4
|
+
const PRIVATE_CURSOR_MAX_LENGTH = 4096;
|
|
5
|
+
/**
|
|
6
|
+
* Older official private adapters predate account inventory listing. Keep the
|
|
7
|
+
* compatibility wire at the private composition edge, and never replace a
|
|
8
|
+
* future adapter's native implementation.
|
|
9
|
+
*/
|
|
10
|
+
export function withPrivateAccountAssetCompatibility(hubCapability, opts) {
|
|
11
|
+
const candidate = hubCapability;
|
|
12
|
+
if (typeof candidate['listAccountAssets'] === 'function') {
|
|
13
|
+
return hubCapability;
|
|
14
|
+
}
|
|
15
|
+
if (candidate['listAccountAssets'] !== undefined) {
|
|
16
|
+
throw new Error('private Hub adapter exposes an invalid account asset sync capability');
|
|
17
|
+
}
|
|
18
|
+
const client = new PrivateAccountAssetCompatibility(opts);
|
|
19
|
+
try {
|
|
20
|
+
Object.defineProperty(hubCapability, 'listAccountAssets', {
|
|
21
|
+
configurable: false,
|
|
22
|
+
enumerable: false,
|
|
23
|
+
writable: false,
|
|
24
|
+
value: (listOpts) => client.list(listOpts),
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
throw new Error('private Hub adapter cannot be extended with account asset sync compatibility');
|
|
29
|
+
}
|
|
30
|
+
return hubCapability;
|
|
31
|
+
}
|
|
32
|
+
class PrivateAccountAssetCompatibility {
|
|
33
|
+
opts;
|
|
34
|
+
baseUrl;
|
|
35
|
+
fetchFn;
|
|
36
|
+
constructor(opts) {
|
|
37
|
+
this.opts = opts;
|
|
38
|
+
this.baseUrl = normalizePrivateHubBaseUrl(opts.baseUrl, opts.env);
|
|
39
|
+
this.fetchFn = opts.fetchFn ?? globalPrivateCompatibilityFetch;
|
|
40
|
+
}
|
|
41
|
+
async list(opts) {
|
|
42
|
+
assertAccountAssetListOptions(opts);
|
|
43
|
+
if (opts.scope === 'purchased') {
|
|
44
|
+
// Private Hub has no marketplace/purchase ledger. An empty inventory is
|
|
45
|
+
// distinct from published assets and avoids importing arbitrary recall hits.
|
|
46
|
+
if (opts.cursor)
|
|
47
|
+
throw new HubClientError(400, { code: 'private_marketplace_cursor_unsupported' });
|
|
48
|
+
return { assets: [], count: 0, hasMore: false };
|
|
49
|
+
}
|
|
50
|
+
const limit = Math.min(opts.limit ?? 100, PRIVATE_PUBLISHED_MAX_PAGE_SIZE);
|
|
51
|
+
const query = new URLSearchParams({ limit: String(limit) });
|
|
52
|
+
const senderId = this.opts.senderId()?.trim();
|
|
53
|
+
if (senderId)
|
|
54
|
+
query.set('sender_id', senderId);
|
|
55
|
+
if (opts.cursor)
|
|
56
|
+
query.set('cursor', opts.cursor);
|
|
57
|
+
if (opts.type)
|
|
58
|
+
query.set('type', opts.type);
|
|
59
|
+
if (opts.status && opts.status !== 'all')
|
|
60
|
+
query.set('status', opts.status);
|
|
61
|
+
const signed = await this.opts.auth.authenticate({ method: 'GET', path: PRIVATE_PUBLISHED_ASSETS_PATH });
|
|
62
|
+
const headers = accountAssetHeaders(signed);
|
|
63
|
+
const url = `${this.baseUrl}${PRIVATE_PUBLISHED_ASSETS_PATH}?${query.toString()}`;
|
|
64
|
+
assertPrivateCompatibilityUrlSecure(url, this.opts.env);
|
|
65
|
+
let response;
|
|
66
|
+
try {
|
|
67
|
+
response = await this.fetchFn(url, { method: 'GET', headers });
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
if (error instanceof AuthError || error instanceof HubClientError || error instanceof HubUnreachableError)
|
|
71
|
+
throw error;
|
|
72
|
+
throw new HubUnreachableError('Private Hub account asset request failed before a response arrived', {
|
|
73
|
+
context: `GET ${PRIVATE_PUBLISHED_ASSETS_PATH}`,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
const body = await parseCompatibilityResponse(response);
|
|
77
|
+
if (response.status === 401 || response.status === 403)
|
|
78
|
+
throw new AuthError(response.status, body);
|
|
79
|
+
if (response.status >= 400 && response.status < 500)
|
|
80
|
+
throw new HubClientError(response.status, body);
|
|
81
|
+
if (response.status >= 500)
|
|
82
|
+
throw new Error(`private hub ${response.status} ${PRIVATE_PUBLISHED_ASSETS_PATH}`);
|
|
83
|
+
return parsePublishedPage(body, limit);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
function assertAccountAssetListOptions(opts) {
|
|
87
|
+
if (!opts || (opts.scope !== 'purchased' && opts.scope !== 'published')) {
|
|
88
|
+
throw new HubClientError(400, { code: 'invalid_account_asset_scope' });
|
|
89
|
+
}
|
|
90
|
+
if (opts.limit !== undefined && (!Number.isSafeInteger(opts.limit) || opts.limit <= 0)) {
|
|
91
|
+
throw new HubClientError(400, { code: 'invalid_account_asset_limit' });
|
|
92
|
+
}
|
|
93
|
+
if (opts.cursor !== undefined && (!opts.cursor.trim() || opts.cursor.length > PRIVATE_CURSOR_MAX_LENGTH)) {
|
|
94
|
+
throw new HubClientError(400, { code: 'invalid_account_asset_cursor' });
|
|
95
|
+
}
|
|
96
|
+
if (opts.type !== undefined && opts.type !== 'Gene' && opts.type !== 'Capsule') {
|
|
97
|
+
throw new HubClientError(400, { code: 'invalid_account_asset_type' });
|
|
98
|
+
}
|
|
99
|
+
if (opts.status !== undefined && opts.status !== 'draft' && opts.status !== 'promoted' && opts.status !== 'all') {
|
|
100
|
+
throw new HubClientError(400, { code: 'invalid_account_asset_status' });
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
function accountAssetHeaders(signed) {
|
|
104
|
+
const headers = { accept: 'application/json', ...signed.headers };
|
|
105
|
+
const hasAuthorization = Object.keys(headers).some((key) => key.toLowerCase() === 'authorization');
|
|
106
|
+
const bodyNodeSecret = signed.bodyFields?.['node_secret'];
|
|
107
|
+
if (!hasAuthorization && typeof bodyNodeSecret === 'string' && bodyNodeSecret) {
|
|
108
|
+
headers['authorization'] = `Bearer ${bodyNodeSecret}`;
|
|
109
|
+
}
|
|
110
|
+
return headers;
|
|
111
|
+
}
|
|
112
|
+
async function parseCompatibilityResponse(response) {
|
|
113
|
+
if (!Number.isInteger(response.status) || response.status < 100 || response.status > 599) {
|
|
114
|
+
throw new HubUnreachableError('Private Hub account asset response has an invalid HTTP status', {
|
|
115
|
+
context: `GET ${PRIVATE_PUBLISHED_ASSETS_PATH}`,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
try {
|
|
119
|
+
const parsed = await response.json();
|
|
120
|
+
const record = asRecord(parsed);
|
|
121
|
+
if (record)
|
|
122
|
+
return record;
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
// The normalized error below intentionally excludes response data.
|
|
126
|
+
}
|
|
127
|
+
throw new HubUnreachableError('Private Hub account asset response is not a JSON object', {
|
|
128
|
+
status: response.status,
|
|
129
|
+
context: `GET ${PRIVATE_PUBLISHED_ASSETS_PATH}`,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
function parsePublishedPage(body, limit) {
|
|
133
|
+
const payload = asRecord(body['payload']) ?? body;
|
|
134
|
+
const assets = payload['assets'];
|
|
135
|
+
const hasMore = payload['has_more'] ?? payload['hasMore'];
|
|
136
|
+
const rawCursor = payload['next_cursor'] ?? payload['nextCursor'];
|
|
137
|
+
const count = payload['count'];
|
|
138
|
+
if (!Array.isArray(assets) || assets.length > limit || typeof hasMore !== 'boolean') {
|
|
139
|
+
throw malformedPublishedPage();
|
|
140
|
+
}
|
|
141
|
+
if (count !== undefined && (!Number.isSafeInteger(count) || count < 0)) {
|
|
142
|
+
throw malformedPublishedPage();
|
|
143
|
+
}
|
|
144
|
+
const nextCursor = rawCursor === null || rawCursor === undefined ? undefined : rawCursor;
|
|
145
|
+
if (nextCursor !== undefined && (typeof nextCursor !== 'string' || !nextCursor.trim() || nextCursor.length > PRIVATE_CURSOR_MAX_LENGTH)) {
|
|
146
|
+
throw malformedPublishedPage();
|
|
147
|
+
}
|
|
148
|
+
if (hasMore && nextCursor === undefined)
|
|
149
|
+
throw malformedPublishedPage();
|
|
150
|
+
return {
|
|
151
|
+
assets: assets,
|
|
152
|
+
...(count !== undefined ? { count: count } : {}),
|
|
153
|
+
hasMore,
|
|
154
|
+
...(nextCursor !== undefined ? { nextCursor } : {}),
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
function malformedPublishedPage() {
|
|
158
|
+
return new HubUnreachableError('Private Hub returned a malformed published asset page', {
|
|
159
|
+
context: `GET ${PRIVATE_PUBLISHED_ASSETS_PATH}`,
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
function normalizePrivateHubBaseUrl(raw, env) {
|
|
163
|
+
const normalized = raw.trim().replace(/\/+$/, '');
|
|
164
|
+
assertPrivateCompatibilityUrlSecure(normalized, env);
|
|
165
|
+
const parsed = new URL(normalized);
|
|
166
|
+
if (parsed.username || parsed.password || parsed.search || parsed.hash) {
|
|
167
|
+
throw new Error('Private Hub URL must not contain credentials, query parameters, or a fragment');
|
|
168
|
+
}
|
|
169
|
+
return normalized;
|
|
170
|
+
}
|
|
171
|
+
function assertPrivateCompatibilityUrlSecure(url, env) {
|
|
172
|
+
let parsed;
|
|
173
|
+
try {
|
|
174
|
+
parsed = new URL(url);
|
|
175
|
+
}
|
|
176
|
+
catch {
|
|
177
|
+
throw new Error('Private Hub URL is invalid');
|
|
178
|
+
}
|
|
179
|
+
if (parsed.protocol === 'https:')
|
|
180
|
+
return;
|
|
181
|
+
if (parsed.protocol === 'http:' && env['EVOLVER_PRIVATE_ALLOW_INSECURE'] === '1')
|
|
182
|
+
return;
|
|
183
|
+
throw new Error('Private Hub URL must use https');
|
|
184
|
+
}
|
|
185
|
+
const globalPrivateCompatibilityFetch = async (url, init) => {
|
|
186
|
+
const response = await fetch(url, {
|
|
187
|
+
method: init.method,
|
|
188
|
+
headers: init.headers,
|
|
189
|
+
...(init.body ? { body: init.body } : {}),
|
|
190
|
+
redirect: 'error',
|
|
191
|
+
});
|
|
192
|
+
return { status: response.status, json: () => response.json() };
|
|
193
|
+
};
|
|
194
|
+
function asRecord(value) {
|
|
195
|
+
return value && typeof value === 'object' && !Array.isArray(value) ? value : undefined;
|
|
196
|
+
}
|