@minhspark/codex-mcp-bridge 1.13.9 → 1.15.1
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/CHANGELOG.md +47 -0
- package/README.md +53 -24
- package/package.json +6 -6
- package/scripts/check.mjs +6 -1
- package/scripts/install-claude-desktop.mjs +22 -7
- package/scripts/install-codex-mcp.mjs +8 -5
- package/scripts/install-native-relay.mjs +15 -9
- package/src/bridge-account-context.mjs +77 -0
- package/src/claude-bridge-launcher.mjs +3 -0
- package/src/claude-bridge.mjs +91 -28
- package/src/claude-desktop-context.mjs +11 -6
- package/src/claude-sender-context.mjs +206 -0
- package/src/claude-session-router.mjs +92 -0
- package/src/codex-account-context.mjs +116 -0
- package/src/codex-bridge-launcher.mjs +3 -0
- package/src/codex-mcp-registration.mjs +14 -9
- package/src/desktop-account-context.mjs +140 -0
- package/src/desktop-task-receipts.mjs +3 -1
- package/src/index.mjs +91 -9
- package/src/mcp-supervisor.mjs +326 -0
- package/src/native-relay-companion.mjs +173 -34
- package/src/native-relay-launcher.mjs +3 -0
- package/src/native-relay.mjs +234 -50
- package/src/peer-protocol.mjs +120 -17
- package/src/platform.mjs +4 -1
- package/src/release-snapshot.mjs +198 -0
- package/src/reload-control.mjs +102 -0
- package/src/reply-forwarder.mjs +43 -0
- package/src/thread-delivery.mjs +27 -11
|
@@ -11,7 +11,11 @@ import {
|
|
|
11
11
|
MAX_FRAME_BYTES,
|
|
12
12
|
NATIVE_DISPATCH_METHOD,
|
|
13
13
|
NativeToolsClient,
|
|
14
|
+
NativeRelayError,
|
|
14
15
|
RELAY_PROTOCOL_VERSION,
|
|
16
|
+
ACCOUNT_RELAY_PROTOCOL_VERSION,
|
|
17
|
+
accountRelaySocketPath,
|
|
18
|
+
relayAccountContext,
|
|
15
19
|
relaySocketPath,
|
|
16
20
|
resolveRelayThreadId,
|
|
17
21
|
desktopTaskSocketPath,
|
|
@@ -20,14 +24,32 @@ import {
|
|
|
20
24
|
} from "./native-relay.mjs";
|
|
21
25
|
import { IS_WINDOWS, PLATFORM_LABEL } from "./platform.mjs";
|
|
22
26
|
import { exitForVersionRequest } from "./cli-version.mjs";
|
|
27
|
+
import { assertAccountIdentity } from "./bridge-account-context.mjs";
|
|
28
|
+
import { createReloadControl } from "./reload-control.mjs";
|
|
29
|
+
import { createRuntimeState } from "./runtime-state.mjs";
|
|
23
30
|
|
|
24
31
|
exitForVersionRequest(import.meta.url);
|
|
25
32
|
|
|
26
|
-
const VERSION = "1.
|
|
33
|
+
const VERSION = "1.15.1";
|
|
27
34
|
const log = (msg) => process.stderr.write(`[native-relay] ${msg}\n`);
|
|
28
35
|
|
|
29
|
-
function errorResponse(code, message) {
|
|
30
|
-
return { ok: false, v: RELAY_PROTOCOL_VERSION, error: { code, message } };
|
|
36
|
+
function errorResponse(code, message, sent) {
|
|
37
|
+
return { ok: false, v: RELAY_PROTOCOL_VERSION, error: { code, message, ...(sent === false ? { sent: false } : {}) } };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function boundRequest(payload) {
|
|
41
|
+
return payload?.v === ACCOUNT_RELAY_PROTOCOL_VERSION;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function validProtocol(payload) {
|
|
45
|
+
return boundRequest(payload) ? Object.hasOwn(payload, "accountContext") : (payload?.v === undefined || payload.v === RELAY_PROTOCOL_VERSION) && !Object.hasOwn(payload, "accountContext");
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async function checkAccountContext(payload, assertAccount) {
|
|
49
|
+
const accounts = relayAccountContext(payload.accountContext);
|
|
50
|
+
if (boundRequest(payload) && !accounts) throw new NativeRelayError("Protocol 2 requires the original account context", "RELAY_BAD_REQUEST");
|
|
51
|
+
if (accounts) await assertAccount(accounts);
|
|
52
|
+
return accounts;
|
|
31
53
|
}
|
|
32
54
|
|
|
33
55
|
/**
|
|
@@ -46,14 +68,13 @@ function errorCode(err) {
|
|
|
46
68
|
*/
|
|
47
69
|
export async function handleRelayRequest(
|
|
48
70
|
payload,
|
|
49
|
-
{ dispatch, dispatchDesktop, resolveExecutor = resolveRelayThreadId, env = process.env } = {},
|
|
71
|
+
{ dispatch, dispatchDesktop, resolveExecutor = resolveRelayThreadId, env = process.env, assertAccount = assertAccountIdentity } = {},
|
|
50
72
|
) {
|
|
51
73
|
if (payload && typeof payload === "object" && Object.hasOwn(payload, "operation")) {
|
|
52
|
-
return handleDesktopRequest(payload, { dispatchDesktop, resolveExecutor, env });
|
|
74
|
+
return handleDesktopRequest(payload, { dispatchDesktop, resolveExecutor, env, assertAccount });
|
|
53
75
|
}
|
|
54
76
|
if (!payload || typeof payload !== "object" || Array.isArray(payload) ||
|
|
55
|
-
Object.keys(payload).some((key) => !["v", "targetThreadId", "message"].includes(key)) ||
|
|
56
|
-
(payload.v !== undefined && payload.v !== RELAY_PROTOCOL_VERSION)) {
|
|
77
|
+
Object.keys(payload).some((key) => !["v", "targetThreadId", "message", "accountContext"].includes(key)) || !validProtocol(payload)) {
|
|
57
78
|
return errorResponse("RELAY_BAD_REQUEST", "expected a relay request with targetThreadId and message");
|
|
58
79
|
}
|
|
59
80
|
const targetThreadId = typeof payload?.targetThreadId === "string" ? payload.targetThreadId.trim() : "";
|
|
@@ -63,10 +84,12 @@ export async function handleRelayRequest(
|
|
|
63
84
|
if (!message.trim()) return errorResponse("RELAY_BAD_REQUEST", "message must be a non-empty string");
|
|
64
85
|
|
|
65
86
|
let executorThreadId;
|
|
87
|
+
let accountContext;
|
|
66
88
|
try {
|
|
89
|
+
accountContext = await checkAccountContext(payload, assertAccount);
|
|
67
90
|
executorThreadId = resolveExecutor(env).threadId;
|
|
68
91
|
} catch (err) {
|
|
69
|
-
return errorResponse(errorCode(err), err.message);
|
|
92
|
+
return errorResponse(errorCode(err), err.message, false);
|
|
70
93
|
}
|
|
71
94
|
|
|
72
95
|
/**
|
|
@@ -83,24 +106,30 @@ export async function handleRelayRequest(
|
|
|
83
106
|
}
|
|
84
107
|
|
|
85
108
|
try {
|
|
86
|
-
const result = await dispatch({ executorThreadId, targetThreadId, message });
|
|
109
|
+
const result = await dispatch({ executorThreadId, targetThreadId, message }, { accountContext });
|
|
87
110
|
if (result?.success !== true || result?.isError === true) {
|
|
88
111
|
const detail = typeof result?.error === "string" ? result.error : result?.error?.message;
|
|
89
112
|
return errorResponse("NATIVE_DISPATCH_FAILED", detail ?? "Codex Desktop did not confirm successful native dispatch");
|
|
90
113
|
}
|
|
91
|
-
return { ok: true, v: RELAY_PROTOCOL_VERSION, targetThreadId, executorThreadId, result: result ?? null };
|
|
114
|
+
return { ok: true, v: boundRequest(payload) ? ACCOUNT_RELAY_PROTOCOL_VERSION : RELAY_PROTOCOL_VERSION, targetThreadId, executorThreadId, result: result ?? null };
|
|
92
115
|
} catch (err) {
|
|
93
|
-
return errorResponse(errorCode(err), err?.message ?? String(err));
|
|
116
|
+
return errorResponse(errorCode(err), err?.message ?? String(err), err?.sent === false && err?.reachedCompanion !== true ? false : undefined);
|
|
94
117
|
}
|
|
95
118
|
}
|
|
96
119
|
|
|
97
|
-
async function handleDesktopRequest(payload, { dispatchDesktop, resolveExecutor, env }) {
|
|
98
|
-
if (Array.isArray(payload) || payload.v
|
|
99
|
-
Object.keys(payload).some((key) => !["v", "operation", "arguments"].includes(key))) {
|
|
120
|
+
async function handleDesktopRequest(payload, { dispatchDesktop, resolveExecutor, env, assertAccount }) {
|
|
121
|
+
if (Array.isArray(payload) || ![RELAY_PROTOCOL_VERSION, ACCOUNT_RELAY_PROTOCOL_VERSION].includes(payload.v) || !validProtocol(payload) ||
|
|
122
|
+
Object.keys(payload).some((key) => !["v", "operation", "arguments", "accountContext"].includes(key))) {
|
|
100
123
|
return errorResponse("RELAY_BAD_REQUEST", "expected an allowlisted Desktop operation");
|
|
101
124
|
}
|
|
102
125
|
try {
|
|
103
126
|
validateDesktopOperation(payload.operation, payload.arguments);
|
|
127
|
+
let accountContext;
|
|
128
|
+
try {
|
|
129
|
+
accountContext = await checkAccountContext(payload, assertAccount);
|
|
130
|
+
} catch (error) {
|
|
131
|
+
return errorResponse(errorCode(error), error.message, false);
|
|
132
|
+
}
|
|
104
133
|
if (typeof dispatchDesktop !== "function") {
|
|
105
134
|
return errorResponse("NATIVE_OPERATION_UNAVAILABLE", "This companion does not support Desktop operations; reload the native relay");
|
|
106
135
|
}
|
|
@@ -112,16 +141,16 @@ async function handleDesktopRequest(payload, { dispatchDesktop, resolveExecutor,
|
|
|
112
141
|
executorThreadId,
|
|
113
142
|
operation: payload.operation,
|
|
114
143
|
arguments: payload.arguments,
|
|
115
|
-
});
|
|
144
|
+
}, { accountContext });
|
|
116
145
|
return {
|
|
117
146
|
ok: true,
|
|
118
|
-
v: RELAY_PROTOCOL_VERSION,
|
|
147
|
+
v: boundRequest(payload) ? ACCOUNT_RELAY_PROTOCOL_VERSION : RELAY_PROTOCOL_VERSION,
|
|
119
148
|
operation: payload.operation,
|
|
120
149
|
executorThreadId,
|
|
121
150
|
result: decodeNativeToolResult(nativeResult),
|
|
122
151
|
};
|
|
123
152
|
} catch (err) {
|
|
124
|
-
return errorResponse(errorCode(err), err?.message ?? String(err));
|
|
153
|
+
return errorResponse(errorCode(err), err?.message ?? String(err), err?.sent === false && err?.reachedCompanion !== true ? false : undefined);
|
|
125
154
|
}
|
|
126
155
|
}
|
|
127
156
|
|
|
@@ -137,6 +166,8 @@ export class RelaySocketServer {
|
|
|
137
166
|
dispatch,
|
|
138
167
|
dispatchDesktop,
|
|
139
168
|
resolveExecutor = resolveRelayThreadId,
|
|
169
|
+
assertAccount = assertAccountIdentity,
|
|
170
|
+
requireAccountContext = false,
|
|
140
171
|
restrictSocket = (target) => {
|
|
141
172
|
if (!IS_WINDOWS) fs.chmodSync(target, 0o600);
|
|
142
173
|
},
|
|
@@ -146,11 +177,16 @@ export class RelaySocketServer {
|
|
|
146
177
|
this.dispatch = dispatch;
|
|
147
178
|
this.dispatchDesktop = dispatchDesktop;
|
|
148
179
|
this.resolveExecutor = resolveExecutor;
|
|
180
|
+
this.assertAccount = assertAccount;
|
|
181
|
+
this.requireAccountContext = requireAccountContext;
|
|
149
182
|
this.restrictSocket = restrictSocket;
|
|
150
183
|
this.log = logFn;
|
|
151
184
|
this.server = null;
|
|
152
185
|
this.started = false;
|
|
153
186
|
this.connections = new Set();
|
|
187
|
+
this.handlers = new Set();
|
|
188
|
+
this.accepting = true;
|
|
189
|
+
this.closed = Promise.resolve();
|
|
154
190
|
this.processHandlers = new Map();
|
|
155
191
|
}
|
|
156
192
|
|
|
@@ -248,6 +284,11 @@ export class RelaySocketServer {
|
|
|
248
284
|
}
|
|
249
285
|
|
|
250
286
|
#handleConnection(socket) {
|
|
287
|
+
if (!this.accepting) {
|
|
288
|
+
socket.on("error", () => {});
|
|
289
|
+
socket.end(`${JSON.stringify(errorResponse("RELAY_RELOADING", "The native relay is reloading; no message was sent", false))}\n`);
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
251
292
|
let buffer = Buffer.alloc(0);
|
|
252
293
|
let handled = false;
|
|
253
294
|
this.connections.add(socket);
|
|
@@ -265,7 +306,12 @@ export class RelaySocketServer {
|
|
|
265
306
|
const index = buffer.indexOf(10);
|
|
266
307
|
if (index < 0) return;
|
|
267
308
|
handled = true;
|
|
268
|
-
|
|
309
|
+
const handler = this.#handleLine(socket, buffer.subarray(0, index).toString("utf8"));
|
|
310
|
+
this.handlers.add(handler);
|
|
311
|
+
void handler.catch((error) => {
|
|
312
|
+
this.log(`relay request failed: ${error.message}`);
|
|
313
|
+
this.#reply(socket, errorResponse("NATIVE_DISPATCH_FAILED", "The native relay request failed before delivery was confirmed"));
|
|
314
|
+
}).finally(() => this.handlers.delete(handler));
|
|
269
315
|
buffer = Buffer.alloc(0);
|
|
270
316
|
});
|
|
271
317
|
}
|
|
@@ -278,10 +324,15 @@ export class RelaySocketServer {
|
|
|
278
324
|
this.#reply(socket, errorResponse("RELAY_BAD_REQUEST", `malformed JSON: ${err.message}`));
|
|
279
325
|
return;
|
|
280
326
|
}
|
|
327
|
+
if (this.requireAccountContext && !boundRequest(payload)) {
|
|
328
|
+
this.#reply(socket, errorResponse("RELAY_BAD_REQUEST", "The account relay requires protocol 2 and the original account context", false));
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
281
331
|
const response = await handleRelayRequest(payload, {
|
|
282
332
|
dispatch: this.dispatch,
|
|
283
333
|
dispatchDesktop: this.dispatchDesktop,
|
|
284
334
|
resolveExecutor: this.resolveExecutor,
|
|
335
|
+
assertAccount: this.assertAccount,
|
|
285
336
|
});
|
|
286
337
|
if (!response.ok) this.log(`relay refused ${payload?.targetThreadId ?? "?"}: ${response.error.message}`);
|
|
287
338
|
else this.log(response.operation ? `completed Desktop operation ${response.operation}` : `relayed a message into thread ${response.targetThreadId}`);
|
|
@@ -298,9 +349,12 @@ export class RelaySocketServer {
|
|
|
298
349
|
this.processHandlers.clear();
|
|
299
350
|
for (const socket of this.connections) socket.destroy();
|
|
300
351
|
this.connections.clear();
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
352
|
+
const server = this.server;
|
|
353
|
+
this.closed = new Promise((resolve) => {
|
|
354
|
+
if (!server) return resolve();
|
|
355
|
+
try { server.close(() => resolve()); }
|
|
356
|
+
catch { resolve(); }
|
|
357
|
+
});
|
|
304
358
|
try {
|
|
305
359
|
if (this.started && !IS_WINDOWS) fs.rmSync(this.socketPath, { force: true });
|
|
306
360
|
} catch {}
|
|
@@ -314,10 +368,14 @@ export function startRelayWhenAvailable({ nativeTools, relay, log: logFn = () =>
|
|
|
314
368
|
let delayMs = retryDelayMs;
|
|
315
369
|
let resolveReady;
|
|
316
370
|
const ready = new Promise((resolve) => { resolveReady = resolve; });
|
|
371
|
+
let pendingAttempt = null;
|
|
372
|
+
let nativeConnected = false;
|
|
317
373
|
const attempt = async () => {
|
|
318
374
|
if (stopped) return;
|
|
375
|
+
nativeConnected = false;
|
|
319
376
|
try {
|
|
320
377
|
await nativeTools.connect();
|
|
378
|
+
nativeConnected = true;
|
|
321
379
|
if (stopped) {
|
|
322
380
|
nativeTools.close();
|
|
323
381
|
return;
|
|
@@ -331,8 +389,8 @@ export function startRelayWhenAvailable({ nativeTools, relay, log: logFn = () =>
|
|
|
331
389
|
resolveReady(true);
|
|
332
390
|
} catch (err) {
|
|
333
391
|
if (stopped) return;
|
|
334
|
-
nativeTools.close();
|
|
335
|
-
if (!nativeTools.socketPath) {
|
|
392
|
+
if (!nativeConnected) nativeTools.close();
|
|
393
|
+
if (!nativeTools.socketPath && !nativeTools.hasDiscoveredSocket) {
|
|
336
394
|
logFn(`native relay unavailable (${err.message})`);
|
|
337
395
|
resolveReady(false);
|
|
338
396
|
return;
|
|
@@ -340,22 +398,90 @@ export function startRelayWhenAvailable({ nativeTools, relay, log: logFn = () =>
|
|
|
340
398
|
logFn(`native relay unavailable (${err.message}); retrying in ${delayMs}ms`);
|
|
341
399
|
timer = globalThis.setTimeout(() => {
|
|
342
400
|
timer = null;
|
|
343
|
-
|
|
401
|
+
runAttempt();
|
|
344
402
|
}, delayMs);
|
|
345
403
|
timer.unref();
|
|
346
404
|
delayMs = Math.min(delayMs * 2, maxRetryDelayMs);
|
|
347
405
|
}
|
|
348
406
|
};
|
|
349
|
-
|
|
407
|
+
const runAttempt = () => {
|
|
408
|
+
const running = attempt();
|
|
409
|
+
pendingAttempt = running;
|
|
410
|
+
void running.finally(() => { if (pendingAttempt === running) pendingAttempt = null; });
|
|
411
|
+
return running;
|
|
412
|
+
};
|
|
413
|
+
const firstAttempt = runAttempt();
|
|
350
414
|
return {
|
|
351
415
|
ready,
|
|
416
|
+
firstAttempt,
|
|
417
|
+
get connected() { return nativeConnected; },
|
|
418
|
+
get busy() { return pendingAttempt !== null; },
|
|
352
419
|
stop() {
|
|
353
|
-
if (stopped) return;
|
|
420
|
+
if (stopped) return pendingAttempt ?? Promise.resolve();
|
|
354
421
|
stopped = true;
|
|
355
422
|
globalThis.clearTimeout(timer);
|
|
356
423
|
nativeTools.close();
|
|
357
424
|
relay.stop();
|
|
358
425
|
resolveReady(false);
|
|
426
|
+
return (pendingAttempt ?? Promise.resolve()).then(() => relay.closed);
|
|
427
|
+
},
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
export function createNativeRelayLifecycle({ nativeTools, relays, log: logFn = () => {} }) {
|
|
432
|
+
const servers = [...new Set(relays)];
|
|
433
|
+
let startups = [];
|
|
434
|
+
let ownedSockets = [];
|
|
435
|
+
const inspect = () => {
|
|
436
|
+
if (servers.some((relay) => relay.handlers.size)) return "Native relay requests are still active";
|
|
437
|
+
if (servers.some((relay) => relay.connections.size)) return "Native relay clients are still connected";
|
|
438
|
+
if (nativeTools.pending.size) return "Native Desktop dispatches are still pending";
|
|
439
|
+
if (nativeTools.connecting || startups.some((startup) => startup.busy)) return "Native relay startup is still pending";
|
|
440
|
+
return null;
|
|
441
|
+
};
|
|
442
|
+
const stop = async () => {
|
|
443
|
+
for (const relay of servers) relay.accepting = false;
|
|
444
|
+
const active = startups;
|
|
445
|
+
startups = [];
|
|
446
|
+
await Promise.all(active.map((startup) => startup.stop()));
|
|
447
|
+
for (const relay of servers) relay.stop();
|
|
448
|
+
await Promise.all(servers.map((relay) => relay.closed));
|
|
449
|
+
nativeTools.close();
|
|
450
|
+
};
|
|
451
|
+
const activate = async () => {
|
|
452
|
+
if (startups.length) {
|
|
453
|
+
for (const relay of servers) relay.accepting = true;
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
for (const relay of servers) relay.accepting = true;
|
|
457
|
+
startups = servers.map((relay) => startRelayWhenAvailable({ nativeTools, relay, log: logFn }));
|
|
458
|
+
await Promise.all(startups.map((startup) => startup.firstAttempt));
|
|
459
|
+
const restoredListeners = await Promise.all(servers.map(async (relay, index) =>
|
|
460
|
+
!ownedSockets.includes(relay.socketPath) || startups[index].connected && await relay.isListening()));
|
|
461
|
+
if (restoredListeners.some((available) => !available)) {
|
|
462
|
+
await stop();
|
|
463
|
+
throw new Error("The replacement native relay could not reclaim its listening sockets");
|
|
464
|
+
}
|
|
465
|
+
};
|
|
466
|
+
return {
|
|
467
|
+
inspect,
|
|
468
|
+
activate,
|
|
469
|
+
stop,
|
|
470
|
+
async quiesce() {
|
|
471
|
+
for (const relay of servers) relay.accepting = false;
|
|
472
|
+
const reason = inspect();
|
|
473
|
+
if (reason) throw new Error(reason);
|
|
474
|
+
ownedSockets = servers.filter((relay) => relay.started).map((relay) => relay.socketPath);
|
|
475
|
+
await stop();
|
|
476
|
+
},
|
|
477
|
+
exportState: () => ({ ownedSockets }),
|
|
478
|
+
restore(payload) {
|
|
479
|
+
if (Object.keys(payload).some((key) => key !== "ownedSockets") || !Array.isArray(payload.ownedSockets)
|
|
480
|
+
|| payload.ownedSockets.length > servers.length || new Set(payload.ownedSockets).size !== payload.ownedSockets.length
|
|
481
|
+
|| payload.ownedSockets.some((socket) => typeof socket !== "string" || !servers.some((relay) => relay.socketPath === socket))) {
|
|
482
|
+
throw new Error("Native relay listener state does not match this worker");
|
|
483
|
+
}
|
|
484
|
+
ownedSockets = [...payload.ownedSockets];
|
|
359
485
|
},
|
|
360
486
|
};
|
|
361
487
|
}
|
|
@@ -379,19 +505,29 @@ if (invokedDirectly) {
|
|
|
379
505
|
);
|
|
380
506
|
|
|
381
507
|
const nativeTools = new NativeToolsClient();
|
|
382
|
-
const dispatch = (args) => nativeTools.dispatch(args);
|
|
508
|
+
const dispatch = (args, options) => nativeTools.dispatch(args, options);
|
|
383
509
|
|
|
384
510
|
const relay = new RelaySocketServer({
|
|
385
511
|
socketPath: relaySocketPath(),
|
|
386
512
|
dispatch,
|
|
387
|
-
dispatchDesktop: (args) => nativeTools.dispatchDesktop(args),
|
|
513
|
+
dispatchDesktop: (args, options) => nativeTools.dispatchDesktop(args, options),
|
|
388
514
|
log,
|
|
389
515
|
});
|
|
390
516
|
const desktopRelay = desktopTaskSocketPath() === relay.socketPath ? relay : new RelaySocketServer({
|
|
391
517
|
socketPath: desktopTaskSocketPath(),
|
|
392
|
-
dispatchDesktop: (args) => nativeTools.dispatchDesktop(args),
|
|
518
|
+
dispatchDesktop: (args, options) => nativeTools.dispatchDesktop(args, options),
|
|
393
519
|
log,
|
|
394
520
|
});
|
|
521
|
+
const accountRelay = new RelaySocketServer({
|
|
522
|
+
socketPath: accountRelaySocketPath(),
|
|
523
|
+
requireAccountContext: true,
|
|
524
|
+
dispatch,
|
|
525
|
+
dispatchDesktop: (args, options) => nativeTools.dispatchDesktop(args, options),
|
|
526
|
+
log,
|
|
527
|
+
});
|
|
528
|
+
const runtime = createRuntimeState();
|
|
529
|
+
const lifecycle = createNativeRelayLifecycle({ nativeTools, relays: [relay, desktopRelay, accountRelay], log });
|
|
530
|
+
const reload = createReloadControl({ entry: "native-relay-companion.mjs", ...lifecycle });
|
|
395
531
|
|
|
396
532
|
mcp.registerTool(
|
|
397
533
|
"native_relay_status",
|
|
@@ -406,7 +542,7 @@ if (invokedDirectly) {
|
|
|
406
542
|
openWorldHint: false,
|
|
407
543
|
},
|
|
408
544
|
},
|
|
409
|
-
async () => {
|
|
545
|
+
async () => reload.run(async () => {
|
|
410
546
|
const listening = await relay.isListening();
|
|
411
547
|
let executor = "(unconfigured)";
|
|
412
548
|
try {
|
|
@@ -416,14 +552,17 @@ if (invokedDirectly) {
|
|
|
416
552
|
executor = err.message;
|
|
417
553
|
}
|
|
418
554
|
return {
|
|
555
|
+
structuredContent: { runtime: runtime.status() },
|
|
419
556
|
content: [
|
|
420
557
|
{
|
|
421
558
|
type: "text",
|
|
422
559
|
text: [
|
|
423
560
|
`platform: ${PLATFORM_LABEL} (${process.platform}/${process.arch})`,
|
|
424
561
|
`companion: codex-native-relay ${VERSION}`,
|
|
562
|
+
`runtime state: ${runtime.status().current ? "current" : "stale"} (${reload.inspect().phase}, PID ${process.pid})`,
|
|
425
563
|
`relay socket: ${relay.started ? relay.socketPath : `${relay.socketPath} (${listening ? "shared companion listening" : "not listening"})`}`,
|
|
426
564
|
`desktop tasks: ${desktopRelay.socketPath} (${await desktopRelay.isListening() ? "listening" : "not listening"})`,
|
|
565
|
+
`account relay: ${accountRelay.socketPath} (protocol ${ACCOUNT_RELAY_PROTOCOL_VERSION}, ${await accountRelay.isListening() ? "listening" : "not listening"})`,
|
|
427
566
|
`executor: ${executor}`,
|
|
428
567
|
`dispatch: ${process.env.CODEX_NATIVE_RELAY_METHOD ?? NATIVE_DISPATCH_METHOD}`,
|
|
429
568
|
`native pipe: ${nativeTools.socketPath ?? "unavailable (requires Codex Desktop)"}`,
|
|
@@ -431,12 +570,12 @@ if (invokedDirectly) {
|
|
|
431
570
|
},
|
|
432
571
|
],
|
|
433
572
|
};
|
|
434
|
-
},
|
|
573
|
+
}),
|
|
435
574
|
);
|
|
436
575
|
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
mcp.server.onclose = () => { startup.stop(); desktopStartup.stop(); };
|
|
576
|
+
if (!reload.staged) void lifecycle.activate().catch((error) => log(`native relay startup failed: ${error.message}`));
|
|
577
|
+
mcp.server.onclose = () => { void lifecycle.stop(); };
|
|
440
578
|
await mcp.connect(new StdioServerTransport());
|
|
579
|
+
reload.listen();
|
|
441
580
|
log(`ready on ${PLATFORM_LABEL} (${relay.started ? relay.socketPath : "socket down"})`);
|
|
442
581
|
}
|