@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.
@@ -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
  }