@mindexec/cli 0.2.122 → 0.2.124
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/package.json +3 -2
- package/scripts/remote-agent-managed-smoke.mjs +231 -0
- package/server.js +119 -5
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mindexec/cli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.124",
|
|
4
4
|
"description": "MindExec local runtime and bridge CLI",
|
|
5
5
|
"main": "server.js",
|
|
6
6
|
"type": "module",
|
|
@@ -20,12 +20,13 @@
|
|
|
20
20
|
"scripts": {
|
|
21
21
|
"start": "node launch-bridge.cjs",
|
|
22
22
|
"dev": "node launch-bridge.cjs --watch",
|
|
23
|
-
"test:syntax": "node --check server.js && node --check remote-hub.js && node --check codex-runtime.js && node --check launch-bridge.cjs && node --check port-guard.cjs && node --check scripts/setup-tree-sitter-grammars.mjs && node --check scripts/auth-session-smoke.mjs && node --check scripts/remote-hub-smoke.mjs && node --check scripts/remote-hub-scale-smoke.mjs && node --check scripts/remote-fleet-render-smoke.mjs && node --check scripts/remote-http-smoke.mjs && node --check scripts/remote-frame-ws-smoke.mjs && node --check scripts/remote-agent-ws-smoke.mjs",
|
|
23
|
+
"test:syntax": "node --check server.js && node --check remote-hub.js && node --check codex-runtime.js && node --check launch-bridge.cjs && node --check port-guard.cjs && node --check scripts/setup-tree-sitter-grammars.mjs && node --check scripts/auth-session-smoke.mjs && node --check scripts/remote-hub-smoke.mjs && node --check scripts/remote-hub-scale-smoke.mjs && node --check scripts/remote-fleet-render-smoke.mjs && node --check scripts/remote-http-smoke.mjs && node --check scripts/remote-frame-ws-smoke.mjs && node --check scripts/remote-agent-ws-smoke.mjs && node --check scripts/remote-agent-managed-smoke.mjs",
|
|
24
24
|
"test:auth": "node scripts/auth-session-smoke.mjs",
|
|
25
25
|
"test:remote": "node scripts/remote-hub-smoke.mjs",
|
|
26
26
|
"test:remote:scale": "node scripts/remote-hub-scale-smoke.mjs",
|
|
27
27
|
"test:remote:render": "node scripts/remote-fleet-render-smoke.mjs",
|
|
28
28
|
"test:remote:http": "node scripts/remote-http-smoke.mjs",
|
|
29
|
+
"test:remote:managed": "node scripts/remote-agent-managed-smoke.mjs",
|
|
29
30
|
"pack:dry": "npm pack --dry-run",
|
|
30
31
|
"setup:grammars": "node scripts/setup-tree-sitter-grammars.mjs",
|
|
31
32
|
"postinstall": "npm run setup:grammars"
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import assert from 'node:assert/strict';
|
|
4
|
+
import { spawn } from 'node:child_process';
|
|
5
|
+
import { mkdtemp, readFile, rm } from 'node:fs/promises';
|
|
6
|
+
import net from 'node:net';
|
|
7
|
+
import os from 'node:os';
|
|
8
|
+
import path from 'node:path';
|
|
9
|
+
import { fileURLToPath } from 'node:url';
|
|
10
|
+
|
|
11
|
+
const BRIDGE_TOKEN = 'remote-agent-managed-smoke-token';
|
|
12
|
+
const PAIR_TOKEN = 'remote-agent-managed-pair-token';
|
|
13
|
+
const LEASE_ID = 'remote-agent-managed-smoke-lease';
|
|
14
|
+
const LOCAL_BRIDGE_DIR = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
15
|
+
|
|
16
|
+
function wait(ms) {
|
|
17
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async function findFreePort() {
|
|
21
|
+
return await new Promise((resolve, reject) => {
|
|
22
|
+
const server = net.createServer();
|
|
23
|
+
server.unref();
|
|
24
|
+
server.once('error', reject);
|
|
25
|
+
server.listen(0, '127.0.0.1', () => {
|
|
26
|
+
const address = server.address();
|
|
27
|
+
const port = typeof address === 'object' && address ? address.port : 0;
|
|
28
|
+
server.close(() => resolve(port));
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function fetchJson(url, options = {}) {
|
|
34
|
+
const response = await fetch(url, {
|
|
35
|
+
...options,
|
|
36
|
+
headers: {
|
|
37
|
+
...(options.body ? { 'Content-Type': 'application/json' } : {}),
|
|
38
|
+
'X-Bridge-Token': options.token || BRIDGE_TOKEN,
|
|
39
|
+
...(options.headers || {})
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
let payload = null;
|
|
44
|
+
try {
|
|
45
|
+
payload = await response.json();
|
|
46
|
+
} catch {
|
|
47
|
+
// Some failure responses may be empty.
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
status: response.status,
|
|
52
|
+
ok: response.ok,
|
|
53
|
+
payload
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async function waitFor(predicate, timeoutMs = 30000, label = 'condition') {
|
|
58
|
+
const startedAt = Date.now();
|
|
59
|
+
let lastError = null;
|
|
60
|
+
while (Date.now() - startedAt < timeoutMs) {
|
|
61
|
+
try {
|
|
62
|
+
const value = await predicate();
|
|
63
|
+
if (value) {
|
|
64
|
+
return value;
|
|
65
|
+
}
|
|
66
|
+
} catch (err) {
|
|
67
|
+
lastError = err;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
await wait(100);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
throw new Error(`Timed out waiting for ${label}${lastError ? `: ${lastError.message}` : ''}.`);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function spawnBridge({ bridgePort, remoteHubPort, workspacePath, label }) {
|
|
77
|
+
const child = spawn(process.execPath, ['server.js'], {
|
|
78
|
+
cwd: LOCAL_BRIDGE_DIR,
|
|
79
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
80
|
+
windowsHide: true,
|
|
81
|
+
env: {
|
|
82
|
+
...process.env,
|
|
83
|
+
BRIDGE_PORT: String(bridgePort),
|
|
84
|
+
BRIDGE_TOKEN,
|
|
85
|
+
BRIDGE_REQUIRE_TOKEN: '1',
|
|
86
|
+
MINDEXEC_REMOTE_HUB: '1',
|
|
87
|
+
REMOTE_HUB_HOST: '127.0.0.1',
|
|
88
|
+
REMOTE_HUB_PORT: String(remoteHubPort),
|
|
89
|
+
REMOTE_HUB_PAIR_TOKEN: PAIR_TOKEN,
|
|
90
|
+
WORKSPACE_PATH: workspacePath,
|
|
91
|
+
NO_COLOR: '1'
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
let stdout = '';
|
|
96
|
+
let stderr = '';
|
|
97
|
+
child.stdout.on('data', chunk => {
|
|
98
|
+
stdout += chunk.toString();
|
|
99
|
+
});
|
|
100
|
+
child.stderr.on('data', chunk => {
|
|
101
|
+
stderr += chunk.toString();
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
const exitPromise = new Promise(resolve => child.once('exit', resolve));
|
|
105
|
+
const details = () => `${label} stdout=${stdout}\n${label} stderr=${stderr}`;
|
|
106
|
+
const stop = async () => {
|
|
107
|
+
if (child.exitCode === null && !child.killed) {
|
|
108
|
+
child.kill('SIGTERM');
|
|
109
|
+
await Promise.race([
|
|
110
|
+
exitPromise,
|
|
111
|
+
wait(5000)
|
|
112
|
+
]);
|
|
113
|
+
if (child.exitCode === null && !child.killed) {
|
|
114
|
+
child.kill('SIGKILL');
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
baseUrl: `http://127.0.0.1:${bridgePort}`,
|
|
121
|
+
child,
|
|
122
|
+
details,
|
|
123
|
+
stop
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async function waitForBridge(bridge) {
|
|
128
|
+
return await waitFor(async () => {
|
|
129
|
+
const result = await fetchJson(`${bridge.baseUrl}/api/status`);
|
|
130
|
+
return result.ok && result.payload?.status === 'ok' ? result.payload : null;
|
|
131
|
+
}, 30000, `bridge startup\n${bridge.details()}`);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async function connectManagedAgent(clientBridge, managerEndpoint, staleEndpoint) {
|
|
135
|
+
const result = await fetchJson(`${clientBridge.baseUrl}/api/remote/agent/connect`, {
|
|
136
|
+
method: 'POST',
|
|
137
|
+
body: JSON.stringify({
|
|
138
|
+
managerCandidates: [staleEndpoint, managerEndpoint],
|
|
139
|
+
pairToken: PAIR_TOKEN,
|
|
140
|
+
leaseId: LEASE_ID,
|
|
141
|
+
nodeId: 'remote-agent-managed-smoke-node',
|
|
142
|
+
engine: 'auto',
|
|
143
|
+
source: 'smoke'
|
|
144
|
+
})
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
assert.equal(result.ok, true, JSON.stringify(result.payload));
|
|
148
|
+
assert.equal(result.payload?.ok, true, JSON.stringify(result.payload));
|
|
149
|
+
return result.payload.agent;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async function main() {
|
|
153
|
+
const tempRoot = await mkdtemp(path.join(os.tmpdir(), 'mindexec-remote-managed-smoke-'));
|
|
154
|
+
const hostWorkspace = path.join(tempRoot, 'host');
|
|
155
|
+
const clientWorkspace = path.join(tempRoot, 'client');
|
|
156
|
+
const hostBridgePort = await findFreePort();
|
|
157
|
+
const hostRemotePort = await findFreePort();
|
|
158
|
+
const clientBridgePort = await findFreePort();
|
|
159
|
+
const clientRemotePort = await findFreePort();
|
|
160
|
+
const stalePort = await findFreePort();
|
|
161
|
+
const managerEndpoint = `127.0.0.1:${hostRemotePort}`;
|
|
162
|
+
const staleEndpoint = `127.0.0.1:${stalePort}`;
|
|
163
|
+
|
|
164
|
+
let hostBridge = null;
|
|
165
|
+
let clientBridge = null;
|
|
166
|
+
try {
|
|
167
|
+
hostBridge = spawnBridge({
|
|
168
|
+
bridgePort: hostBridgePort,
|
|
169
|
+
remoteHubPort: hostRemotePort,
|
|
170
|
+
workspacePath: hostWorkspace,
|
|
171
|
+
label: 'host'
|
|
172
|
+
});
|
|
173
|
+
await waitForBridge(hostBridge);
|
|
174
|
+
|
|
175
|
+
clientBridge = spawnBridge({
|
|
176
|
+
bridgePort: clientBridgePort,
|
|
177
|
+
remoteHubPort: clientRemotePort,
|
|
178
|
+
workspacePath: clientWorkspace,
|
|
179
|
+
label: 'client-1'
|
|
180
|
+
});
|
|
181
|
+
await waitForBridge(clientBridge);
|
|
182
|
+
|
|
183
|
+
const firstAgent = await connectManagedAgent(clientBridge, managerEndpoint, staleEndpoint);
|
|
184
|
+
assert.equal(firstAgent.running, true, JSON.stringify(firstAgent));
|
|
185
|
+
assert.equal(firstAgent.ready, true, JSON.stringify(firstAgent));
|
|
186
|
+
assert.equal(firstAgent.usingNpx, false, JSON.stringify(firstAgent));
|
|
187
|
+
assert.match(String(firstAgent.launcher || ''), /mindexec-remote-fast/i);
|
|
188
|
+
|
|
189
|
+
const connectedDevice = await waitFor(async () => {
|
|
190
|
+
const result = await fetchJson(`${hostBridge.baseUrl}/api/remote/devices`);
|
|
191
|
+
return result.payload?.devices?.find(device => device.connected === true) || null;
|
|
192
|
+
}, 10000, `host device registration\n${hostBridge.details()}\n${clientBridge.details()}`);
|
|
193
|
+
assert.equal(connectedDevice.capabilities?.binaryFrames, true);
|
|
194
|
+
|
|
195
|
+
await clientBridge.stop();
|
|
196
|
+
clientBridge = null;
|
|
197
|
+
|
|
198
|
+
const cachePath = path.join(clientWorkspace, '.mindexec', 'cache', 'remote-agent-recent-managers.json');
|
|
199
|
+
const cache = JSON.parse(await readFile(cachePath, 'utf8'));
|
|
200
|
+
assert.equal(cache.version, 1);
|
|
201
|
+
assert.equal(cache.entries?.[0]?.endpoint, managerEndpoint, JSON.stringify(cache));
|
|
202
|
+
|
|
203
|
+
clientBridge = spawnBridge({
|
|
204
|
+
bridgePort: clientBridgePort,
|
|
205
|
+
remoteHubPort: clientRemotePort,
|
|
206
|
+
workspacePath: clientWorkspace,
|
|
207
|
+
label: 'client-2'
|
|
208
|
+
});
|
|
209
|
+
await waitForBridge(clientBridge);
|
|
210
|
+
|
|
211
|
+
const secondAgent = await connectManagedAgent(clientBridge, managerEndpoint, staleEndpoint);
|
|
212
|
+
assert.equal(secondAgent.running, true, JSON.stringify(secondAgent));
|
|
213
|
+
assert.equal(secondAgent.ready, true, JSON.stringify(secondAgent));
|
|
214
|
+
assert.equal(secondAgent.usingNpx, false, JSON.stringify(secondAgent));
|
|
215
|
+
assert.match(String(secondAgent.launcher || ''), /mindexec-remote-fast/i);
|
|
216
|
+
assert.equal(secondAgent.managerCandidates?.[0], managerEndpoint, JSON.stringify(secondAgent.managerCandidates));
|
|
217
|
+
assert.equal(secondAgent.managerCandidates?.[1], staleEndpoint, JSON.stringify(secondAgent.managerCandidates));
|
|
218
|
+
|
|
219
|
+
console.log('RemoteAgent managed supervisor smoke OK');
|
|
220
|
+
} finally {
|
|
221
|
+
if (clientBridge) {
|
|
222
|
+
await clientBridge.stop();
|
|
223
|
+
}
|
|
224
|
+
if (hostBridge) {
|
|
225
|
+
await hostBridge.stop();
|
|
226
|
+
}
|
|
227
|
+
await rm(tempRoot, { recursive: true, force: true });
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
await main();
|
package/server.js
CHANGED
|
@@ -2909,12 +2909,17 @@ const REMOTE_AGENT_DEFAULT_ENGINE = 'auto';
|
|
|
2909
2909
|
const REMOTE_AGENT_SYNC_REPORT_LOG_REPEAT_MS = 30000;
|
|
2910
2910
|
const REMOTE_AGENT_RACE_START_STAGGER_MS = 120;
|
|
2911
2911
|
const REMOTE_AGENT_MAX_PARALLEL_CANDIDATES = 6;
|
|
2912
|
+
const REMOTE_AGENT_RECENT_MANAGER_CACHE_TTL_MS = 30 * 24 * 60 * 60 * 1000;
|
|
2913
|
+
const REMOTE_AGENT_RECENT_MANAGER_CACHE_LIMIT = 64;
|
|
2914
|
+
const REMOTE_AGENT_RECENT_MANAGER_DEFAULT_KEY = 'default';
|
|
2912
2915
|
let remoteAgentState = createRemoteAgentIdleState();
|
|
2913
2916
|
let remoteAgentSyncReportState = null;
|
|
2914
2917
|
let remoteAgentSyncReportLogKey = '';
|
|
2915
2918
|
let remoteAgentSyncReportLogAt = 0;
|
|
2916
2919
|
let remoteAgentConnectPromise = null;
|
|
2917
2920
|
const remoteAgentRecentSuccessfulManagers = new Map();
|
|
2921
|
+
let remoteAgentRecentSuccessfulManagersLoaded = false;
|
|
2922
|
+
let remoteAgentRecentSuccessfulManagersSavePromise = null;
|
|
2918
2923
|
|
|
2919
2924
|
function createRemoteAgentIdleState(overrides = {}) {
|
|
2920
2925
|
return {
|
|
@@ -3216,7 +3221,104 @@ function createRemoteAgentConnectionKey(manager, leaseId) {
|
|
|
3216
3221
|
}
|
|
3217
3222
|
|
|
3218
3223
|
function getRemoteAgentRecentManagerKey(leaseId = '') {
|
|
3219
|
-
return safeRemoteAgentField(leaseId ||
|
|
3224
|
+
return safeRemoteAgentField(leaseId || REMOTE_AGENT_RECENT_MANAGER_DEFAULT_KEY, 128)
|
|
3225
|
+
|| REMOTE_AGENT_RECENT_MANAGER_DEFAULT_KEY;
|
|
3226
|
+
}
|
|
3227
|
+
|
|
3228
|
+
function getRemoteAgentRecentManagerCachePath() {
|
|
3229
|
+
return path.join(getDataRoot(workspacePath), 'cache', 'remote-agent-recent-managers.json');
|
|
3230
|
+
}
|
|
3231
|
+
|
|
3232
|
+
function normalizeRemoteAgentRecentManagerEntry(entry) {
|
|
3233
|
+
const endpoint = normalizeRemoteManagerEndpoint(entry?.endpoint);
|
|
3234
|
+
const updatedAt = Number(entry?.updatedAt || 0);
|
|
3235
|
+
if (!endpoint || !Number.isFinite(updatedAt) || updatedAt <= 0) {
|
|
3236
|
+
return null;
|
|
3237
|
+
}
|
|
3238
|
+
|
|
3239
|
+
const now = Date.now();
|
|
3240
|
+
if (now - updatedAt > REMOTE_AGENT_RECENT_MANAGER_CACHE_TTL_MS) {
|
|
3241
|
+
return null;
|
|
3242
|
+
}
|
|
3243
|
+
|
|
3244
|
+
return {
|
|
3245
|
+
endpoint,
|
|
3246
|
+
updatedAt
|
|
3247
|
+
};
|
|
3248
|
+
}
|
|
3249
|
+
|
|
3250
|
+
function loadRemoteAgentRecentSuccessfulManagers() {
|
|
3251
|
+
if (remoteAgentRecentSuccessfulManagersLoaded) {
|
|
3252
|
+
return;
|
|
3253
|
+
}
|
|
3254
|
+
|
|
3255
|
+
remoteAgentRecentSuccessfulManagersLoaded = true;
|
|
3256
|
+
const cachePath = getRemoteAgentRecentManagerCachePath();
|
|
3257
|
+
let payload = null;
|
|
3258
|
+
try {
|
|
3259
|
+
payload = JSON.parse(readFileSync(cachePath, 'utf8'));
|
|
3260
|
+
} catch (err) {
|
|
3261
|
+
if (err?.code !== 'ENOENT') {
|
|
3262
|
+
logWarn('remote', `managed RemoteAgent recent manager cache ignored: ${err?.message || err}`);
|
|
3263
|
+
}
|
|
3264
|
+
return;
|
|
3265
|
+
}
|
|
3266
|
+
|
|
3267
|
+
const rawEntries = Array.isArray(payload?.entries)
|
|
3268
|
+
? payload.entries
|
|
3269
|
+
: Object.entries(payload?.managers || {}).map(([key, value]) => ({ key, ...value }));
|
|
3270
|
+
let loaded = 0;
|
|
3271
|
+
for (const rawEntry of rawEntries) {
|
|
3272
|
+
if (loaded >= REMOTE_AGENT_RECENT_MANAGER_CACHE_LIMIT) {
|
|
3273
|
+
break;
|
|
3274
|
+
}
|
|
3275
|
+
|
|
3276
|
+
const key = getRemoteAgentRecentManagerKey(rawEntry?.key || rawEntry?.leaseId || '');
|
|
3277
|
+
const entry = normalizeRemoteAgentRecentManagerEntry(rawEntry);
|
|
3278
|
+
if (!entry) {
|
|
3279
|
+
continue;
|
|
3280
|
+
}
|
|
3281
|
+
|
|
3282
|
+
remoteAgentRecentSuccessfulManagers.set(key, entry);
|
|
3283
|
+
loaded += 1;
|
|
3284
|
+
}
|
|
3285
|
+
}
|
|
3286
|
+
|
|
3287
|
+
async function saveRemoteAgentRecentSuccessfulManagers() {
|
|
3288
|
+
const entries = Array.from(remoteAgentRecentSuccessfulManagers.entries())
|
|
3289
|
+
.map(([key, value]) => ({
|
|
3290
|
+
key,
|
|
3291
|
+
endpoint: normalizeRemoteManagerEndpoint(value?.endpoint),
|
|
3292
|
+
updatedAt: Number(value?.updatedAt || 0)
|
|
3293
|
+
}))
|
|
3294
|
+
.map(entry => ({
|
|
3295
|
+
...entry,
|
|
3296
|
+
normalized: normalizeRemoteAgentRecentManagerEntry(entry)
|
|
3297
|
+
}))
|
|
3298
|
+
.filter(entry => entry.normalized)
|
|
3299
|
+
.sort((left, right) => Number(right.updatedAt || 0) - Number(left.updatedAt || 0))
|
|
3300
|
+
.slice(0, REMOTE_AGENT_RECENT_MANAGER_CACHE_LIMIT)
|
|
3301
|
+
.map(entry => ({
|
|
3302
|
+
key: entry.key,
|
|
3303
|
+
endpoint: entry.normalized.endpoint,
|
|
3304
|
+
updatedAt: entry.normalized.updatedAt
|
|
3305
|
+
}));
|
|
3306
|
+
|
|
3307
|
+
const payload = {
|
|
3308
|
+
version: 1,
|
|
3309
|
+
savedAt: new Date().toISOString(),
|
|
3310
|
+
entries
|
|
3311
|
+
};
|
|
3312
|
+
await writeFileAtomically(getRemoteAgentRecentManagerCachePath(), JSON.stringify(payload, null, 2), 'utf8');
|
|
3313
|
+
}
|
|
3314
|
+
|
|
3315
|
+
function scheduleRemoteAgentRecentSuccessfulManagersSave() {
|
|
3316
|
+
remoteAgentRecentSuccessfulManagersSavePromise = Promise.resolve(remoteAgentRecentSuccessfulManagersSavePromise)
|
|
3317
|
+
.catch(() => null)
|
|
3318
|
+
.then(() => saveRemoteAgentRecentSuccessfulManagers())
|
|
3319
|
+
.catch(err => {
|
|
3320
|
+
logWarn('remote', `managed RemoteAgent recent manager cache save failed: ${err?.message || err}`);
|
|
3321
|
+
});
|
|
3220
3322
|
}
|
|
3221
3323
|
|
|
3222
3324
|
function rememberRemoteAgentSuccessfulManager(manager, leaseId = '') {
|
|
@@ -3225,10 +3327,14 @@ function rememberRemoteAgentSuccessfulManager(manager, leaseId = '') {
|
|
|
3225
3327
|
return;
|
|
3226
3328
|
}
|
|
3227
3329
|
|
|
3228
|
-
|
|
3330
|
+
loadRemoteAgentRecentSuccessfulManagers();
|
|
3331
|
+
const entry = {
|
|
3229
3332
|
endpoint,
|
|
3230
3333
|
updatedAt: Date.now()
|
|
3231
|
-
}
|
|
3334
|
+
};
|
|
3335
|
+
remoteAgentRecentSuccessfulManagers.set(getRemoteAgentRecentManagerKey(leaseId), entry);
|
|
3336
|
+
remoteAgentRecentSuccessfulManagers.set(REMOTE_AGENT_RECENT_MANAGER_DEFAULT_KEY, entry);
|
|
3337
|
+
scheduleRemoteAgentRecentSuccessfulManagersSave();
|
|
3232
3338
|
}
|
|
3233
3339
|
|
|
3234
3340
|
function prioritizeRemoteAgentManagers(managers, leaseId = '') {
|
|
@@ -3237,8 +3343,10 @@ function prioritizeRemoteAgentManagers(managers, leaseId = '') {
|
|
|
3237
3343
|
return normalized;
|
|
3238
3344
|
}
|
|
3239
3345
|
|
|
3240
|
-
|
|
3241
|
-
const
|
|
3346
|
+
loadRemoteAgentRecentSuccessfulManagers();
|
|
3347
|
+
const leaseRecent = remoteAgentRecentSuccessfulManagers.get(getRemoteAgentRecentManagerKey(leaseId));
|
|
3348
|
+
const defaultRecent = remoteAgentRecentSuccessfulManagers.get(REMOTE_AGENT_RECENT_MANAGER_DEFAULT_KEY);
|
|
3349
|
+
const recentEndpoint = normalizeRemoteManagerEndpoint(leaseRecent?.endpoint || defaultRecent?.endpoint);
|
|
3242
3350
|
if (!recentEndpoint) {
|
|
3243
3351
|
return normalized;
|
|
3244
3352
|
}
|
|
@@ -10333,6 +10441,12 @@ async function shutdownBridge(signal) {
|
|
|
10333
10441
|
// Ignore managed RemoteAgent close errors during shutdown
|
|
10334
10442
|
}
|
|
10335
10443
|
|
|
10444
|
+
try {
|
|
10445
|
+
await remoteAgentRecentSuccessfulManagersSavePromise;
|
|
10446
|
+
} catch {
|
|
10447
|
+
// Ignore recent-manager cache save errors during shutdown
|
|
10448
|
+
}
|
|
10449
|
+
|
|
10336
10450
|
try {
|
|
10337
10451
|
await remoteHub.close();
|
|
10338
10452
|
} catch {
|