@minhspark/codex-mcp-bridge 1.21.5 → 1.22.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/.codex-plugin/plugin.json +1 -1
- package/CHANGELOG.md +29 -0
- package/README.md +1 -1
- package/docs/daily-handoffs.md +1 -1
- package/docs/new-macos-setup.md +67 -0
- package/docs/{new-machine-setup.md → new-windows-setup.md} +1 -1
- package/package.json +3 -2
- package/plugin.json +1 -1
- package/scripts/install-native-relay.mjs +1 -1
- package/src/claude-bridge.mjs +1 -1
- package/src/codex-native-response.mjs +78 -26
- package/src/desktop-read-tools.mjs +208 -0
- package/src/index.mjs +20 -84
- package/src/native-relay-companion.mjs +21 -12
- package/src/native-relay.mjs +28 -22
- package/src/response-preferences.mjs +233 -0
- package/src/rollout-segments.mjs +1 -1
- package/src/thread-delivery.mjs +31 -5
package/src/thread-delivery.mjs
CHANGED
|
@@ -27,6 +27,13 @@ export const NATIVE_BACKEND = "codex-desktop-native";
|
|
|
27
27
|
export const APP_SERVER_BACKEND = "app-server";
|
|
28
28
|
export const DESKTOP_TOOL_BUDGET_MS = 40000;
|
|
29
29
|
const RELEASE_STATUSES = new Set(["completed", "interrupted", "failed"]);
|
|
30
|
+
const METADATA_READ_OPTIONS = { includeOutputs: false, maxOutputCharsPerItem: 1 };
|
|
31
|
+
|
|
32
|
+
function unavailableWaitRenderer(error) {
|
|
33
|
+
const cause = error.cause ?? error;
|
|
34
|
+
return cause.code === "NATIVE_DISPATCH_FAILED" &&
|
|
35
|
+
/^(Codex app tool request failed\.?|The app tool call ended before producing a result\.?)$/.test(cause.message);
|
|
36
|
+
}
|
|
30
37
|
|
|
31
38
|
function sameAccountContext(expected, current) {
|
|
32
39
|
return Boolean(expected && current && ["claude", "codex"].every((provider) =>
|
|
@@ -112,6 +119,7 @@ export class DesktopTaskDelivery {
|
|
|
112
119
|
});
|
|
113
120
|
return includeRelayContext ? { result: response.result, executorThreadId: response.executorThreadId } : response.result;
|
|
114
121
|
} catch (err) {
|
|
122
|
+
if (err.code === "RESPONSE_TOO_LARGE") throw err;
|
|
115
123
|
if (err.code?.startsWith("PROJECT_SCOPE_") || err.code === "CLAUDE_SENDER_CONTEXT_UNVERIFIED") throw err;
|
|
116
124
|
throw new Error(`Codex Desktop operation ${operation} failed: ${err.message}. Desktop-only mode will not start or use an external app-server. Open Codex Desktop and reconnect its native relay, then inspect the existing task before retrying a send.`, { cause: err });
|
|
117
125
|
}
|
|
@@ -194,7 +202,7 @@ export class DesktopTaskDelivery {
|
|
|
194
202
|
let response;
|
|
195
203
|
try {
|
|
196
204
|
this.security.assertThread(receipt.threadId, cwd);
|
|
197
|
-
response = await this.request("read_thread", { threadId: receipt.threadId, hostId: "local", turnLimit: 1 }, { deadline });
|
|
205
|
+
response = await this.request("read_thread", { threadId: receipt.threadId, hostId: "local", turnLimit: 1, ...METADATA_READ_OPTIONS }, { deadline });
|
|
198
206
|
} catch (err) {
|
|
199
207
|
throw new Error(`Existing Desktop task ${receipt.threadId} could not be verified. No prompt was resent and no duplicate task was created: ${err.message}`, { cause: err });
|
|
200
208
|
}
|
|
@@ -364,7 +372,7 @@ export class DesktopTaskDelivery {
|
|
|
364
372
|
}
|
|
365
373
|
|
|
366
374
|
async #inspectMetadata(threadId, cwd, { deadline } = {}) {
|
|
367
|
-
const envelope = await this.request("read_thread", { threadId, hostId: "local", turnLimit: 1 }, { deadline, includeRelayContext: true });
|
|
375
|
+
const envelope = await this.request("read_thread", { threadId, hostId: "local", turnLimit: 1, ...METADATA_READ_OPTIONS }, { deadline, includeRelayContext: true });
|
|
368
376
|
const response = envelope.result;
|
|
369
377
|
const thread = response?.thread;
|
|
370
378
|
if (thread?.id !== threadId || thread.hostId !== "local" || !thread.cwd) throw new Error("Desktop did not confirm the task's local workspace.");
|
|
@@ -471,23 +479,41 @@ export class DesktopTaskDelivery {
|
|
|
471
479
|
const startedAt = this.now();
|
|
472
480
|
let cursor;
|
|
473
481
|
let turnId = null;
|
|
482
|
+
let readSnapshots = false;
|
|
483
|
+
const readSnapshot = async () => {
|
|
484
|
+
const result = await this.request("read_thread", { threadId, hostId: "local", turnLimit: 1, ...METADATA_READ_OPTIONS }, { deadline: startedAt + timeoutMs });
|
|
485
|
+
const thread = result?.thread;
|
|
486
|
+
if (thread?.id !== threadId || thread.hostId !== "local" || !thread.cwd) throw new Error("Desktop did not confirm the observed task's local workspace.");
|
|
487
|
+
this.security.assertCwd(thread.cwd);
|
|
488
|
+
this.security.assertThread(threadId, thread.cwd);
|
|
489
|
+
this.senderProjectScope(thread.cwd);
|
|
490
|
+
if (responseObservation?.expectedCwd && realpathSync.native(thread.cwd) !== responseObservation.expectedCwd) throw new Error("The selected native task workspace changed during observation");
|
|
491
|
+
return { polls: [{ thread, latestTurn: result.turns?.[0] ?? null }] };
|
|
492
|
+
};
|
|
474
493
|
const expired = () => ({ threadId, turnId, status: "timeout", text: "", responseStatus: "unavailable", assistantItems: [], replySha256: null, activity: [], errors: [], durationMs: this.now() - startedAt });
|
|
475
494
|
for (;;) {
|
|
476
495
|
if (this.now() - startedAt >= timeoutMs) return expired();
|
|
477
496
|
let response;
|
|
478
497
|
try {
|
|
479
|
-
response = await this.request("wait_threads", {
|
|
498
|
+
response = readSnapshots ? await readSnapshot() : await this.request("wait_threads", {
|
|
480
499
|
targets: [{ threadId, hostId: "local", ...(cursor ? { afterCursor: cursor } : {}) }], timeoutMs: 0,
|
|
481
500
|
}, { deadline: startedAt + timeoutMs });
|
|
482
501
|
} catch (err) {
|
|
483
502
|
if (this.now() - startedAt >= timeoutMs) return expired();
|
|
484
|
-
throw err;
|
|
503
|
+
if (readSnapshots || !unavailableWaitRenderer(err)) throw err;
|
|
504
|
+
readSnapshots = true;
|
|
505
|
+
try {
|
|
506
|
+
response = await readSnapshot();
|
|
507
|
+
} catch (readError) {
|
|
508
|
+
if (this.now() - startedAt >= timeoutMs) return expired();
|
|
509
|
+
throw readError;
|
|
510
|
+
}
|
|
485
511
|
}
|
|
486
512
|
const poll = response?.polls?.find((item) => item.thread?.id === threadId && item.thread?.hostId === "local");
|
|
487
513
|
if (response?.errors?.length || !poll) throw new Error(`Could not observe task ${threadId}; it may still be running. Read it before retrying: ${JSON.stringify(response)}`);
|
|
488
514
|
cursor = poll.cursor;
|
|
489
515
|
const turn = poll.latestTurn;
|
|
490
|
-
const threadStatus = poll.thread.status?.type;
|
|
516
|
+
const threadStatus = typeof poll.thread.status === "string" ? poll.thread.status : poll.thread.status?.type;
|
|
491
517
|
if (["systemError", "waitingOnApproval", "waitingOnUserInput"].includes(threadStatus)) {
|
|
492
518
|
return { threadId, turnId: turn?.id !== previousTurnId ? turn?.id ?? null : null, status: threadStatus, text: "", activity: [], errors: [], durationMs: this.now() - startedAt };
|
|
493
519
|
}
|