@minhspark/codex-mcp-bridge 1.12.0 → 1.12.2

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.
@@ -17,12 +17,17 @@ import { runTurn } from "./turn.mjs";
17
17
  */
18
18
  export const NATIVE_BACKEND = "codex-desktop-native";
19
19
  export const APP_SERVER_BACKEND = "app-server";
20
+ const RELEASE_STATUSES = new Set(["completed", "interrupted", "failed"]);
20
21
 
21
22
  export function createThreadDelivery({
22
23
  codex,
23
24
  relay = new NativeDesktopRelay(),
24
25
  log = () => {},
25
26
  timeoutMs = 240000,
27
+ releaseAfterTurn =
28
+ process.env.CODEX_BRIDGE_RELEASE_AFTER_TURN !== undefined
29
+ ? process.env.CODEX_BRIDGE_RELEASE_AFTER_TURN === "1"
30
+ : process.platform === "win32",
26
31
  } = {}) {
27
32
  let reportedUnavailable = null;
28
33
 
@@ -42,7 +47,7 @@ export function createThreadDelivery({
42
47
  reportedUnavailable = null;
43
48
  return { backend: NATIVE_BACKEND, threadId, ack };
44
49
  } catch (err) {
45
- if (err.reachedCompanion) throw err;
50
+ if (err.reachedCompanion || err.code !== "RELAY_UNREACHABLE") throw err;
46
51
  log(`native relay unreachable (${err.message}); falling back to the app-server path`);
47
52
  }
48
53
  } else if (status.reason !== reportedUnavailable) {
@@ -51,13 +56,24 @@ export function createThreadDelivery({
51
56
  }
52
57
 
53
58
  if (!codex) throw new Error("No Codex app-server client is configured to deliver this message");
54
- await codex.ensureThreadAttached(threadId);
55
- const turn = await runTurn(codex, {
56
- threadId,
57
- input: [{ type: "text", text }],
58
- timeoutMs,
59
- });
60
- return { backend: APP_SERVER_BACKEND, threadId, turn };
59
+ const send = async () => {
60
+ await codex.ensureThreadAttached(threadId);
61
+ const turn = await runTurn(codex, {
62
+ threadId,
63
+ input: [{ type: "text", text }],
64
+ timeoutMs,
65
+ });
66
+ if (releaseAfterTurn && RELEASE_STATUSES.has(turn.status) && typeof codex.releaseThread === "function") {
67
+ try {
68
+ const released = await codex.releaseThread(threadId);
69
+ if (!released?.released) log("thread release pending: " + (released.reason ?? released.status ?? "awaiting unload"));
70
+ } catch (err) {
71
+ log("thread release failed: " + err.message);
72
+ }
73
+ }
74
+ return { backend: APP_SERVER_BACKEND, threadId, turn };
75
+ };
76
+ return codex.withThread ? codex.withThread(threadId, send) : send();
61
77
  }
62
78
 
63
79
  function describe() {
package/src/turn.mjs CHANGED
@@ -54,10 +54,16 @@ export async function runTurn(client, { threadId, input, timeoutMs = 240000, tur
54
54
  });
55
55
 
56
56
  const process = (msg) => {
57
+ if (settled) return;
57
58
  const params = msg.params ?? {};
58
59
  if (turnId && params.turnId && params.turnId !== turnId) return;
59
60
 
60
61
  switch (msg.method) {
62
+ case "thread/closed": {
63
+ settled = true;
64
+ resolveDone({ status: "disconnected", error: { message: "The thread closed before its turn completed" } });
65
+ return;
66
+ }
61
67
  case "item/completed": {
62
68
  const summary = summarizeItem(params.item);
63
69
  if (!summary) return;
@@ -90,7 +96,7 @@ export async function runTurn(client, { threadId, input, timeoutMs = 240000, tur
90
96
  };
91
97
 
92
98
  const unsubscribe = client.subscribe(threadId, (msg) => {
93
- if (!turnId) {
99
+ if (!turnId && msg.method !== "thread/closed") {
94
100
  buffered.push(msg);
95
101
  return;
96
102
  }
@@ -113,15 +119,27 @@ export async function runTurn(client, { threadId, input, timeoutMs = 240000, tur
113
119
  });
114
120
 
115
121
  try {
116
- const started = await client.request(
117
- "turn/start",
118
- { threadId, input, ...turnOverrides },
119
- { timeoutMs: Math.min(timeoutMs, 60000) },
120
- );
121
- turnId = started?.turn?.id ?? null;
122
- for (const msg of buffered.splice(0)) process(msg);
123
-
124
- const outcome = await done;
122
+ const start = await Promise.race([
123
+ client.request(
124
+ "turn/start",
125
+ { ...turnOverrides, threadId, input },
126
+ { timeoutMs: Math.min(timeoutMs, 60000) },
127
+ ).then((started) => ({ started }), (error) => ({ error })),
128
+ done.then((outcome) => ({ outcome })),
129
+ ]);
130
+ if (start.error) throw start.error;
131
+ let outcome = start.outcome;
132
+ if (!outcome) {
133
+ turnId = start.started?.turn?.id ?? null;
134
+ if (typeof turnId !== "string" || !turnId.trim()) {
135
+ throw new Error("Codex app-server did not return a turn id for turn/start");
136
+ }
137
+ for (const msg of buffered.splice(0)) process(msg);
138
+ if (TERMINAL_STATUSES.has(start.started?.turn?.status)) {
139
+ process({ method: "turn/completed", params: { turn: start.started.turn } });
140
+ }
141
+ outcome = await done;
142
+ }
125
143
  return {
126
144
  threadId,
127
145
  turnId,