@minhspark/codex-mcp-bridge 1.13.9 → 1.15.0
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 +34 -1
- package/README.md +37 -18
- package/package.json +6 -6
- package/scripts/install-claude-desktop.mjs +12 -3
- 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 +90 -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 +86 -8
- package/src/mcp-supervisor.mjs +314 -0
- package/src/native-relay-companion.mjs +169 -34
- package/src/native-relay-launcher.mjs +3 -0
- package/src/native-relay.mjs +119 -47
- package/src/peer-protocol.mjs +120 -17
- package/src/platform.mjs +4 -1
- package/src/release-snapshot.mjs +95 -0
- package/src/reload-control.mjs +96 -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.0";
|
|
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,13 @@ 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;
|
|
317
372
|
const attempt = async () => {
|
|
318
373
|
if (stopped) return;
|
|
374
|
+
let nativeConnected = false;
|
|
319
375
|
try {
|
|
320
376
|
await nativeTools.connect();
|
|
377
|
+
nativeConnected = true;
|
|
321
378
|
if (stopped) {
|
|
322
379
|
nativeTools.close();
|
|
323
380
|
return;
|
|
@@ -331,8 +388,8 @@ export function startRelayWhenAvailable({ nativeTools, relay, log: logFn = () =>
|
|
|
331
388
|
resolveReady(true);
|
|
332
389
|
} catch (err) {
|
|
333
390
|
if (stopped) return;
|
|
334
|
-
nativeTools.close();
|
|
335
|
-
if (!nativeTools.socketPath) {
|
|
391
|
+
if (!nativeConnected) nativeTools.close();
|
|
392
|
+
if (!nativeTools.socketPath && !nativeTools.hasDiscoveredSocket) {
|
|
336
393
|
logFn(`native relay unavailable (${err.message})`);
|
|
337
394
|
resolveReady(false);
|
|
338
395
|
return;
|
|
@@ -340,22 +397,87 @@ export function startRelayWhenAvailable({ nativeTools, relay, log: logFn = () =>
|
|
|
340
397
|
logFn(`native relay unavailable (${err.message}); retrying in ${delayMs}ms`);
|
|
341
398
|
timer = globalThis.setTimeout(() => {
|
|
342
399
|
timer = null;
|
|
343
|
-
|
|
400
|
+
runAttempt();
|
|
344
401
|
}, delayMs);
|
|
345
402
|
timer.unref();
|
|
346
403
|
delayMs = Math.min(delayMs * 2, maxRetryDelayMs);
|
|
347
404
|
}
|
|
348
405
|
};
|
|
349
|
-
|
|
406
|
+
const runAttempt = () => {
|
|
407
|
+
const running = attempt();
|
|
408
|
+
pendingAttempt = running;
|
|
409
|
+
void running.finally(() => { if (pendingAttempt === running) pendingAttempt = null; });
|
|
410
|
+
return running;
|
|
411
|
+
};
|
|
412
|
+
const firstAttempt = runAttempt();
|
|
350
413
|
return {
|
|
351
414
|
ready,
|
|
415
|
+
firstAttempt,
|
|
416
|
+
get busy() { return pendingAttempt !== null; },
|
|
352
417
|
stop() {
|
|
353
|
-
if (stopped) return;
|
|
418
|
+
if (stopped) return pendingAttempt ?? Promise.resolve();
|
|
354
419
|
stopped = true;
|
|
355
420
|
globalThis.clearTimeout(timer);
|
|
356
421
|
nativeTools.close();
|
|
357
422
|
relay.stop();
|
|
358
423
|
resolveReady(false);
|
|
424
|
+
return (pendingAttempt ?? Promise.resolve()).then(() => relay.closed);
|
|
425
|
+
},
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
export function createNativeRelayLifecycle({ nativeTools, relays, log: logFn = () => {} }) {
|
|
430
|
+
const servers = [...new Set(relays)];
|
|
431
|
+
let startups = [];
|
|
432
|
+
let ownedSockets = [];
|
|
433
|
+
const inspect = () => {
|
|
434
|
+
if (servers.some((relay) => relay.handlers.size)) return "Native relay requests are still active";
|
|
435
|
+
if (servers.some((relay) => relay.connections.size)) return "Native relay clients are still connected";
|
|
436
|
+
if (nativeTools.pending.size) return "Native Desktop dispatches are still pending";
|
|
437
|
+
if (nativeTools.connecting || startups.some((startup) => startup.busy)) return "Native relay startup is still pending";
|
|
438
|
+
return null;
|
|
439
|
+
};
|
|
440
|
+
const stop = async () => {
|
|
441
|
+
for (const relay of servers) relay.accepting = false;
|
|
442
|
+
const active = startups;
|
|
443
|
+
startups = [];
|
|
444
|
+
await Promise.all(active.map((startup) => startup.stop()));
|
|
445
|
+
for (const relay of servers) relay.stop();
|
|
446
|
+
await Promise.all(servers.map((relay) => relay.closed));
|
|
447
|
+
nativeTools.close();
|
|
448
|
+
};
|
|
449
|
+
const activate = async () => {
|
|
450
|
+
if (startups.length) {
|
|
451
|
+
for (const relay of servers) relay.accepting = true;
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
for (const relay of servers) relay.accepting = true;
|
|
455
|
+
startups = servers.map((relay) => startRelayWhenAvailable({ nativeTools, relay, log: logFn }));
|
|
456
|
+
await Promise.all(startups.map((startup) => startup.firstAttempt));
|
|
457
|
+
if (servers.some((relay) => ownedSockets.includes(relay.socketPath) && !relay.started)) {
|
|
458
|
+
await stop();
|
|
459
|
+
throw new Error("The replacement native relay could not reclaim its listening sockets");
|
|
460
|
+
}
|
|
461
|
+
};
|
|
462
|
+
return {
|
|
463
|
+
inspect,
|
|
464
|
+
activate,
|
|
465
|
+
stop,
|
|
466
|
+
async quiesce() {
|
|
467
|
+
for (const relay of servers) relay.accepting = false;
|
|
468
|
+
const reason = inspect();
|
|
469
|
+
if (reason) throw new Error(reason);
|
|
470
|
+
ownedSockets = servers.filter((relay) => relay.started).map((relay) => relay.socketPath);
|
|
471
|
+
await stop();
|
|
472
|
+
},
|
|
473
|
+
exportState: () => ({ ownedSockets }),
|
|
474
|
+
restore(payload) {
|
|
475
|
+
if (Object.keys(payload).some((key) => key !== "ownedSockets") || !Array.isArray(payload.ownedSockets)
|
|
476
|
+
|| payload.ownedSockets.length > servers.length || new Set(payload.ownedSockets).size !== payload.ownedSockets.length
|
|
477
|
+
|| payload.ownedSockets.some((socket) => typeof socket !== "string" || !servers.some((relay) => relay.socketPath === socket))) {
|
|
478
|
+
throw new Error("Native relay listener state does not match this worker");
|
|
479
|
+
}
|
|
480
|
+
ownedSockets = [...payload.ownedSockets];
|
|
359
481
|
},
|
|
360
482
|
};
|
|
361
483
|
}
|
|
@@ -379,19 +501,29 @@ if (invokedDirectly) {
|
|
|
379
501
|
);
|
|
380
502
|
|
|
381
503
|
const nativeTools = new NativeToolsClient();
|
|
382
|
-
const dispatch = (args) => nativeTools.dispatch(args);
|
|
504
|
+
const dispatch = (args, options) => nativeTools.dispatch(args, options);
|
|
383
505
|
|
|
384
506
|
const relay = new RelaySocketServer({
|
|
385
507
|
socketPath: relaySocketPath(),
|
|
386
508
|
dispatch,
|
|
387
|
-
dispatchDesktop: (args) => nativeTools.dispatchDesktop(args),
|
|
509
|
+
dispatchDesktop: (args, options) => nativeTools.dispatchDesktop(args, options),
|
|
388
510
|
log,
|
|
389
511
|
});
|
|
390
512
|
const desktopRelay = desktopTaskSocketPath() === relay.socketPath ? relay : new RelaySocketServer({
|
|
391
513
|
socketPath: desktopTaskSocketPath(),
|
|
392
|
-
dispatchDesktop: (args) => nativeTools.dispatchDesktop(args),
|
|
514
|
+
dispatchDesktop: (args, options) => nativeTools.dispatchDesktop(args, options),
|
|
393
515
|
log,
|
|
394
516
|
});
|
|
517
|
+
const accountRelay = new RelaySocketServer({
|
|
518
|
+
socketPath: accountRelaySocketPath(),
|
|
519
|
+
requireAccountContext: true,
|
|
520
|
+
dispatch,
|
|
521
|
+
dispatchDesktop: (args, options) => nativeTools.dispatchDesktop(args, options),
|
|
522
|
+
log,
|
|
523
|
+
});
|
|
524
|
+
const runtime = createRuntimeState();
|
|
525
|
+
const lifecycle = createNativeRelayLifecycle({ nativeTools, relays: [relay, desktopRelay, accountRelay], log });
|
|
526
|
+
const reload = createReloadControl({ entry: "native-relay-companion.mjs", ...lifecycle });
|
|
395
527
|
|
|
396
528
|
mcp.registerTool(
|
|
397
529
|
"native_relay_status",
|
|
@@ -406,7 +538,7 @@ if (invokedDirectly) {
|
|
|
406
538
|
openWorldHint: false,
|
|
407
539
|
},
|
|
408
540
|
},
|
|
409
|
-
async () => {
|
|
541
|
+
async () => reload.run(async () => {
|
|
410
542
|
const listening = await relay.isListening();
|
|
411
543
|
let executor = "(unconfigured)";
|
|
412
544
|
try {
|
|
@@ -416,14 +548,17 @@ if (invokedDirectly) {
|
|
|
416
548
|
executor = err.message;
|
|
417
549
|
}
|
|
418
550
|
return {
|
|
551
|
+
structuredContent: { runtime: runtime.status() },
|
|
419
552
|
content: [
|
|
420
553
|
{
|
|
421
554
|
type: "text",
|
|
422
555
|
text: [
|
|
423
556
|
`platform: ${PLATFORM_LABEL} (${process.platform}/${process.arch})`,
|
|
424
557
|
`companion: codex-native-relay ${VERSION}`,
|
|
558
|
+
`runtime state: ${runtime.status().current ? "current" : "stale"} (${reload.inspect().phase}, PID ${process.pid})`,
|
|
425
559
|
`relay socket: ${relay.started ? relay.socketPath : `${relay.socketPath} (${listening ? "shared companion listening" : "not listening"})`}`,
|
|
426
560
|
`desktop tasks: ${desktopRelay.socketPath} (${await desktopRelay.isListening() ? "listening" : "not listening"})`,
|
|
561
|
+
`account relay: ${accountRelay.socketPath} (protocol ${ACCOUNT_RELAY_PROTOCOL_VERSION}, ${await accountRelay.isListening() ? "listening" : "not listening"})`,
|
|
427
562
|
`executor: ${executor}`,
|
|
428
563
|
`dispatch: ${process.env.CODEX_NATIVE_RELAY_METHOD ?? NATIVE_DISPATCH_METHOD}`,
|
|
429
564
|
`native pipe: ${nativeTools.socketPath ?? "unavailable (requires Codex Desktop)"}`,
|
|
@@ -431,12 +566,12 @@ if (invokedDirectly) {
|
|
|
431
566
|
},
|
|
432
567
|
],
|
|
433
568
|
};
|
|
434
|
-
},
|
|
569
|
+
}),
|
|
435
570
|
);
|
|
436
571
|
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
mcp.server.onclose = () => { startup.stop(); desktopStartup.stop(); };
|
|
572
|
+
if (!reload.staged) void lifecycle.activate().catch((error) => log(`native relay startup failed: ${error.message}`));
|
|
573
|
+
mcp.server.onclose = () => { void lifecycle.stop(); };
|
|
440
574
|
await mcp.connect(new StdioServerTransport());
|
|
575
|
+
reload.listen();
|
|
441
576
|
log(`ready on ${PLATFORM_LABEL} (${relay.started ? relay.socketPath : "socket down"})`);
|
|
442
577
|
}
|