@minhspark/codex-mcp-bridge 1.25.2 → 1.26.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.
Files changed (50) hide show
  1. package/.codex-plugin/plugin.json +1 -1
  2. package/CHANGELOG.md +58 -0
  3. package/README.md +81 -32
  4. package/docs/new-macos-setup.md +47 -21
  5. package/docs/new-windows-setup.md +34 -9
  6. package/docs/project-onboarding.md +21 -8
  7. package/package.json +2 -1
  8. package/plugin.json +1 -1
  9. package/scripts/background-relay-hidden.vbs +11 -0
  10. package/scripts/install-antigravity-bridge.mjs +65 -23
  11. package/scripts/install-background-relay.mjs +428 -0
  12. package/scripts/install-claude-code.mjs +2 -2
  13. package/scripts/install-claude-desktop.mjs +12 -7
  14. package/scripts/install-native-relay.mjs +2 -2
  15. package/scripts/setup-background-relay.mjs +58 -0
  16. package/scripts/setup.mjs +3 -3
  17. package/scripts/worker-relay.mjs +41 -0
  18. package/src/antigravity-bridge.mjs +96 -27
  19. package/src/antigravity-cli.mjs +13 -2
  20. package/src/antigravity-deliveries.mjs +32 -8
  21. package/src/antigravity-ipc.mjs +18 -9
  22. package/src/antigravity-outbound-bridge.mjs +71 -23
  23. package/src/antigravity-outbound-targets.mjs +100 -10
  24. package/src/background-antigravity-worker.mjs +192 -0
  25. package/src/background-claude-worker.mjs +240 -0
  26. package/src/background-codex-worker.mjs +344 -0
  27. package/src/background-relay-client.mjs +107 -0
  28. package/src/background-relay-config.mjs +98 -0
  29. package/src/background-relay-daemon.mjs +60 -0
  30. package/src/background-relay-mcp.mjs +45 -0
  31. package/src/background-relay-protocol.mjs +54 -0
  32. package/src/background-relay-registration.mjs +198 -0
  33. package/src/background-relay-service.mjs +311 -0
  34. package/src/background-relay-storage.mjs +203 -0
  35. package/src/background-relay-storage.ps1 +64 -0
  36. package/src/background-relay.mjs +373 -0
  37. package/src/bridge-readiness.mjs +2 -2
  38. package/src/claude-bridge.mjs +43 -25
  39. package/src/claude-code-registration.mjs +2 -1
  40. package/src/claude-session-router.mjs +8 -15
  41. package/src/index.mjs +39 -22
  42. package/src/mcp-supervisor.mjs +26 -16
  43. package/src/native-relay-companion.mjs +4 -1
  44. package/src/native-relay.mjs +5 -1
  45. package/src/peer-protocol.mjs +9 -6
  46. package/src/recipient-preflight.mjs +81 -0
  47. package/src/reload-control.mjs +2 -2
  48. package/src/setup.mjs +62 -25
  49. package/src/thread-delivery.mjs +10 -5
  50. package/src/windows-pipe-acl.ps1 +1 -1
package/src/setup.mjs CHANGED
@@ -46,14 +46,23 @@ function execute(command, args, env, timeout = 120000) {
46
46
  const result = spawnSync(command, args, { env, encoding: "utf8", windowsHide: true, shell: false, timeout,
47
47
  maxBuffer: 4 * 1024 * 1024, stdio: ["ignore", "pipe", "pipe"] });
48
48
  if (result.error || result.status !== 0) {
49
- const lines = String(result.stderr || "").split(/\r?\n/).filter((line) => /^Error:|^(?:Expected|Refusing|Invalid|Unknown|Shared|Claude|Codex|Configuration|The existing|Missing|Unsafe)/.test(line)
50
- && !/Command failed|--env|password|secret|token|authorization/i.test(line));
51
- const detail = lines.slice(0, 3).join(" ") || result.error?.code || `exit ${result.status}`;
52
- throw new Error(`${path.basename(command)} ${path.basename(args[0] ?? "")}: ${detail}`);
49
+ throw setupProcessFailure(command, args, result);
53
50
  }
54
51
  return result.stdout || "";
55
52
  }
56
53
 
54
+ export function setupProcessFailure(command, args, result) {
55
+ let structured;
56
+ try { structured = JSON.parse(String(result.stdout || "")); } catch {}
57
+ const validCode = value => typeof value === "string" && /^[A-Z][A-Z0-9_]{0,79}$/.test(value);
58
+ const codes = [structured?.code, structured?.service?.code, structured?.permissions?.code, structured?.diagnostic?.code,
59
+ ...String(result.stderr || "").split(/\r?\n/).map(line => /^([A-Z][A-Z0-9_]{0,79}) \| /.exec(line)?.[1])].filter(validCode);
60
+ const lines = String(result.stderr || "").split(/\r?\n/).filter(line => /^Error:|^(?:Expected|Refusing|Invalid|Unknown|Shared|Claude|Codex|Configuration|The existing|Missing|Unsafe)/.test(line)
61
+ && !/Command failed|--env|password|secret|token|authorization/i.test(line));
62
+ const detail = [...new Set(codes)].join(", ") || lines.slice(0, 3).join(" ") || result.error?.code || `exit ${result.status}`;
63
+ return Object.assign(new Error(`${path.basename(command)} ${path.basename(args[0] ?? "")}: ${detail}`), codes[0] ? { code: codes[0] } : {});
64
+ }
65
+
57
66
  function usableBinary(binary, platform) {
58
67
  return typeof binary === "string" && path.isAbsolute(binary) && fs.existsSync(binary)
59
68
  && fs.statSync(binary).isFile() && (platform !== "win32" || /\.(exe|com)$/i.test(binary));
@@ -83,24 +92,25 @@ export function ensureSetupCodex({ env, paths, platform, download = true, run =
83
92
  }
84
93
 
85
94
  export function setupEnvironment(env, paths, codex) {
86
- const result = Object.fromEntries(Object.entries(env).filter(([name]) => !/^(?:CODEX_BRIDGE_|CLAUDE_BRIDGE_|CODEX_NATIVE_RELAY_|CODEX_RELAY_ID$|CODEX_THREAD_ID$|CODEX_APP_TOOLS_PIPE_PATH$)/.test(name)));
95
+ const result = Object.fromEntries(Object.entries(env).filter(([name]) => !/^(?:CODEX_BRIDGE_|CLAUDE_BRIDGE_|CODEX_NATIVE_RELAY_|CODEX_RELAY_ID$|CODEX_THREAD_ID$|CODEX_APP_TOOLS_PIPE_PATH$|AGENT_RELAY_(?:TOKEN|WORKER_ID|REQUEST_ID|ENDPOINT)$)/.test(name)));
87
96
  return { ...result, HOME: paths.home, USERPROFILE: paths.home, CODEX_HOME: paths.codex,
88
97
  CLAUDE_DESKTOP_CONFIG: paths.desktop, CODEX_EXE: codex, CODEX_BIN: codex };
89
98
  }
90
99
 
91
100
  function configurationCheck(label, check, components) {
92
- try { check(); components.push({ name: label, status: "configured" }); }
101
+ try { const details = check(); components.push({ name: label, status: "configured", ...details }); }
93
102
  catch (error) { components.push({ name: label, status: "incomplete", detail: error.message }); }
94
103
  }
95
104
 
96
105
  function assertEntry(entry, worker, root, { caller } = {}) {
97
106
  if (!entry || typeof entry.command !== "string" || !fs.existsSync(entry.command)) throw new Error("MCP command is missing or unavailable");
98
107
  const args = entry.args ?? [];
99
- const expected = worker === "index.mjs" || worker === "claude-bridge.mjs" || worker === "native-relay-companion.mjs"
108
+ const expected = ["index.mjs", "claude-bridge.mjs", "native-relay-companion.mjs", "antigravity-bridge.mjs", "antigravity-outbound-bridge.mjs"].includes(worker)
100
109
  ? [path.join(root, "src", "mcp-supervisor.mjs"), worker] : [path.join(root, "src", worker)];
101
110
  if (JSON.stringify(args) !== JSON.stringify(expected)) throw new Error("MCP entry points to a different installation or unsupported launcher");
102
- if (entry.enabled === false || entry.disabled === true) throw new Error("MCP entry is disabled; the saved choice was preserved");
103
111
  if (caller && entry.env?.ANTIGRAVITY_BRIDGE_CALLER !== caller) throw new Error("Antigravity caller registration is missing or conflicting");
112
+ if (entry.enabled === false || entry.disabled === true) return { deliveryEnabled: false,
113
+ notices: ["This MCP registration is intentionally disabled. Its saved choice was preserved; delivery through this connector is disabled."] };
104
114
  }
105
115
 
106
116
  export function checkSetup({ env = process.env, platform = process.platform, root = ROOT, run = execute, resolve = resolveCodexBin } = {}) {
@@ -119,8 +129,9 @@ export function checkSetup({ env = process.env, platform = process.platform, roo
119
129
  const item = inventory?.find((server) => server.name === name);
120
130
  if (!item) throw new Error("MCP registration is missing");
121
131
  const entry = JSON.parse(run(codex, ["mcp", "get", name, "--json"], setupEnvironment(env, paths, codex), 30000));
122
- assertEntry({ ...entry.transport, enabled: entry.enabled }, worker, root, name === "antigravity-bridge" ? { caller: "codex" } : {});
132
+ const details = assertEntry({ ...entry.transport, enabled: entry.enabled }, worker, root, name === "antigravity-bridge" ? { caller: "codex" } : {});
123
133
  if (name === "codex-native-relay" && !entry.transport.env_vars?.includes("CODEX_APP_TOOLS_PIPE_PATH")) throw new Error("Native pipe forwarding is missing");
134
+ return details;
124
135
  }, components);
125
136
  }
126
137
  for (const [label, file, name, worker, caller] of [
@@ -130,14 +141,26 @@ export function checkSetup({ env = process.env, platform = process.platform, roo
130
141
  ["Antigravity / agent-bridge", path.join(paths.gemini, "mcp_config.json"), "agent-bridge", "antigravity-outbound-bridge.mjs"],
131
142
  ]) configurationCheck(label, () => {
132
143
  const entry = readJson(file)?.mcpServers?.[name];
133
- assertEntry(entry, worker, root, { caller });
144
+ const details = assertEntry(entry, worker, root, { caller });
145
+ if (worker === "index.mjs" && entry.env?.CODEX_BRIDGE_OPEN_IN_APP === "1") {
146
+ throw new Error("Managed bridge settings still allow opening Codex Desktop. Run codex-bridge-setup to apply the current silent configuration.");
147
+ }
134
148
  if (name === "codex-bridge-desktop" && entry.env?.CODEX_BRIDGE_DESKTOP_TASKS === "1") {
135
- throw new Error("Shared Desktop chat cannot use native Code routing. Its saved choice was preserved; explicitly set CODEX_BRIDGE_DESKTOP_TASKS=0 for the local app-server route and CODEX_BRIDGE_AUTOSTART=1 if the bridge should start that server.");
149
+ return { ...details, nativeCodeOnly: true, sharedChatDelivery: "unsupported",
150
+ notices: [...(details?.notices ?? []), "The saved native Code-only policy was preserved. Ordinary Claude Desktop Chat cannot deliver through this connector; native delivery uses the registered Claude Desktop Code connector and remains unverified by setup."] };
136
151
  }
152
+ return details;
153
+ }, components);
154
+ configurationCheck("Claude Desktop / background preference", () => {
155
+ if (readJson(paths.desktop)?.preferences?.menuBarEnabled !== true) throw new Error("The managed Claude background preference is not enabled. Run codex-bridge-setup to apply it; live activation is verified separately.");
137
156
  }, components);
138
157
  configurationCheck("Antigravity / codex-bridge sidecar", () => {
139
- assertEntry(readJson(path.join(paths.gemini, "sidecars", "codex-bridge", "sidecar.json")), "antigravity-sidecar.mjs", root);
140
- if (readJson(path.join(paths.gemini, "config.json"))?.sidecars?.["codex-bridge"]?.enabled !== true) throw new Error("Antigravity sidecar is not enabled");
158
+ const details = assertEntry(readJson(path.join(paths.gemini, "sidecars", "codex-bridge", "sidecar.json")), "antigravity-sidecar.mjs", root);
159
+ const enabled = readJson(path.join(paths.gemini, "config.json"))?.sidecars?.["codex-bridge"]?.enabled;
160
+ if (typeof enabled !== "boolean") throw new Error("Antigravity sidecar enabled setting is missing or invalid");
161
+ if (!enabled) return { deliveryEnabled: false,
162
+ notices: [...(details?.notices ?? []), "The Antigravity bridge sidecar is intentionally disabled. Its saved choice was preserved; native Desktop delivery through this sidecar is disabled."] };
163
+ return details;
141
164
  }, components);
142
165
  if (native) configurationCheck("Native relay executor", () => {
143
166
  const relay = readJson(path.join(paths.codex, "native-relay.json"));
@@ -146,17 +169,22 @@ export function checkSetup({ env = process.env, platform = process.platform, roo
146
169
  configurationCheck("Claude registration names", () => {
147
170
  if (readJson(paths.desktop)?.mcpServers?.["codex-bridge"]) throw new Error("Shared codex-bridge shadows the Claude Code registration");
148
171
  }, components);
172
+ configurationCheck("Independent background relay", () => {
173
+ const details = JSON.parse(run(process.execPath, [path.join(root, "scripts", "setup-background-relay.mjs"), "--check", "--json"], setupEnvironment(env, paths, codex), 90000));
174
+ if (details.configured !== true) throw new Error("Independent worker relay configuration is incomplete");
175
+ return { liveVerified: details.liveVerified === true, transportReady: details.transportReady === true, notices: details.notices ?? [] };
176
+ }, components);
149
177
  return { configured: components.every((item) => item.status === "configured"), liveVerified: false, components };
150
178
  }
151
179
 
152
180
  export function runSetup({ args = [], env = process.env, platform = process.platform, root = ROOT, run = execute, resolve = resolveCodexBin } = {}) {
153
181
  const options = setupArguments(args);
154
- if (options.help) return { help: "Usage: codex-bridge-setup [--check|--dry-run] [--json] [--no-download]\nGlobal npm installation configures Claude Desktop, Claude Code, Codex and Antigravity automatically. CODEX_BRIDGE_SKIP_SETUP=1 opts out." };
182
+ if (options.help) return { help: "Usage: codex-bridge-setup [--check|--dry-run] [--json] [--no-download]\nUpdate the package and apply managed settings with: npm install -g @minhspark/codex-mcp-bridge@latest --allow-scripts=@minhspark/codex-mcp-bridge --foreground-scripts\nGlobal npm installation configures Claude Desktop, Claude Code, Codex, Antigravity and the independent worker relay automatically. CODEX_BRIDGE_SKIP_SETUP=1 opts out. Saved configuration, background relay health and loaded native app connections are verified separately." };
155
183
  if (options.postinstall && !automaticSetupEnabled(env)) return { skipped: true, reason: "Automatic configuration runs only for a global npm install; use codex-bridge-setup for manual setup." };
156
184
  if (options.check) return checkSetup({ env, platform, root, run, resolve });
157
185
  const native = platform === "win32" || platform === "darwin";
158
186
  const paths = setupPaths(env, platform);
159
- const planned = ["Claude Code / codex-bridge", "Claude Desktop / codex-bridge-desktop", ...(native ? ["Codex / codex-native-relay and executor"] : []), "Codex / claude-bridge", "Antigravity / all components"];
187
+ const planned = ["Claude Code / codex-bridge", "Claude Desktop / codex-bridge-desktop", ...(native ? ["Codex / codex-native-relay and executor"] : []), "Codex / claude-bridge", "Antigravity / all components", "Independent background relay"];
160
188
  if (options.dryRun) return { dryRun: true, configured: false, liveVerified: false, components: planned.map((name) => ({ name, status: "planned" })), files: [paths.code, paths.desktop, path.join(paths.codex, "config.toml"), paths.gemini] };
161
189
  if (env.SUDO_USER && env.SUDO_USER !== "root" && typeof process.getuid === "function" && process.getuid() === 0) throw new Error("Run the global install without sudo in the account that runs the apps; setup will not write another account's configuration.");
162
190
  const runtime = ensureSetupCodex({ env, paths, platform, download: !options.noDownload, run, resolve });
@@ -166,39 +194,48 @@ export function runSetup({ args = [], env = process.env, platform = process.plat
166
194
  const install = (name, script, arguments_, environment = base) => {
167
195
  try {
168
196
  const output = run(process.execPath, [path.join(root, "scripts", script), ...arguments_], environment, 180000);
169
- let notices = [];
170
- try { notices = JSON.parse(output).notices ?? []; } catch {}
171
- components.push({ name, status: "configured", ...(notices.length ? { notices } : {}) });
197
+ let details = {};
198
+ try { details = JSON.parse(output); } catch {}
199
+ const notices = details.notices ?? [];
200
+ components.push({ name, status: "configured", ...(notices.length ? { notices } : {}),
201
+ ...(typeof details.liveVerified === "boolean" ? { liveVerified: details.liveVerified, transportReady: details.transportReady === true } : {}),
202
+ ...(details.backgroundPreference ? { backgroundPreference: details.backgroundPreference } : {}) });
172
203
  return true;
173
204
  } catch (error) { components.push({ name, status: "incomplete", detail: error.message }); return false; }
174
205
  };
175
- const code = install(planned[0], "install-claude-code.mjs", ["--install", "--desktop-config", paths.desktop], {
206
+ const code = install(planned[0], "install-claude-code.mjs", ["--install", "--setup", "--desktop-config", paths.desktop], {
176
207
  ...base, CODEX_BRIDGE_DESKTOP_TASKS: native ? "1" : "0", CODEX_BRIDGE_AUTOSTART: native ? "0" : "1", CODEX_BRIDGE_THREAD_POLICY: "roots",
177
208
  });
178
- if (code) install(planned[1], "install-claude-desktop.mjs", ["--entry-name", "codex-bridge-desktop", "--quiet"]);
209
+ if (code) install(planned[1], "install-claude-desktop.mjs", ["--entry-name", "codex-bridge-desktop", "--quiet", "--setup"]);
179
210
  if (native) {
180
211
  const relay = readJson(path.join(paths.codex, "native-relay.json"));
181
212
  install("Codex / codex-native-relay and executor", "install-native-relay.mjs", ["--setup", ...(relay?.desktopTasks === undefined ? ["--desktop-tasks"] : [])]);
182
213
  }
183
214
  install("Codex / claude-bridge", "install-codex-mcp.mjs", ["--setup"]);
184
215
  if (code) install("Antigravity / all components", "install-antigravity-bridge.mjs", ["--all", "--setup"]);
216
+ install("Independent background relay", "setup-background-relay.mjs", ["--json"]);
185
217
  const inspection = checkSetup({ env: base, platform, root, run, resolve: () => runtime.binary });
186
218
  return { configured: components.every((item) => item.status === "configured") && inspection.configured, liveVerified: false,
187
219
  cliInstalled: runtime.installed, components, verification: inspection.components,
188
- nextActions: ["Open or reconnect Claude, Codex and Antigravity after active work finishes so they load the new entries.",
189
- "Use the same saved project in Claude Desktop Code and Codex Desktop. Keep the apps signed in.",
190
- "Verify codex_bridge_status in Claude Code, native_relay_status in Codex, and agent_bridge_status in Antigravity. Shared Claude Desktop chat uses the local app-server route."] };
220
+ nextActions: ["Running MCP processes may still hold earlier settings. Setup does not start apps or open conversations.",
221
+ "Existing compatible supervisors reload installed source when idle; configuration changes are not proof of loaded-process readiness.",
222
+ "Native delivery uses the same saved project and signed-in accounts in Claude Desktop Code and Codex Desktop.",
223
+ "codex_bridge_status, native_relay_status and agent_bridge_status report native readiness separately from this configuration check.",
224
+ "worker_status or worker-relay status reports the independent service and CLI workers, which do not require a responsive Desktop host."] };
191
225
  }
192
226
 
193
227
  export function printSetup(result, { json = false, output = console.log } = {}) {
194
228
  if (json) { output(JSON.stringify(result, null, 2)); return; }
195
229
  if (result.help) { output(result.help); return; }
196
230
  if (result.skipped) { output(result.reason); return; }
197
- output(result.dryRun ? "Bridge setup preview (no changes)" : result.configured ? "Bridge configuration verified. Live connections still require the apps to load it." : "Bridge setup is incomplete. Fix the items below, then run codex-bridge-setup again.");
231
+ output(result.dryRun ? "Bridge setup preview (no changes)" : result.configured ? "Bridge configuration verified. Running app connections have not been verified." : "Bridge setup is incomplete. Fix the items below, then run codex-bridge-setup again.");
198
232
  for (const item of result.components ?? []) {
199
233
  output(`[${item.status}] ${item.name}${item.detail ? `: ${item.detail}` : ""}`);
200
234
  for (const notice of item.notices ?? []) output(` ${notice}`);
201
235
  }
202
- for (const item of result.verification ?? []) if (item.status !== "configured") output(`[${item.status}] ${item.name}: ${item.detail}`);
236
+ for (const item of result.verification ?? []) {
237
+ if (item.status !== "configured") output(`[${item.status}] ${item.name}: ${item.detail}`);
238
+ for (const notice of item.notices ?? []) output(`${item.name}: ${notice}`);
239
+ }
203
240
  for (const action of result.nextActions ?? []) output(action);
204
241
  }
@@ -9,6 +9,7 @@ import { captureCodexRolloutWatermark, inspectCodexNativeTurn, readCodexNativeTu
9
9
  import { captureProjectScope, recheckProjectScope } from "./project-scope.mjs";
10
10
  import { isRolloutChangedDuringRead } from "./rollout-segments.mjs";
11
11
  import { requireClaudeSenderContext } from "./claude-sender-context.mjs";
12
+ import { bridgeDiagnostic } from "./recipient-preflight.mjs";
12
13
 
13
14
  /**
14
15
  * Which backend puts a message into a Codex thread.
@@ -74,7 +75,7 @@ export function matchDesktopProject(projects, cwd, { canonicalize = realpathSync
74
75
  return matches[0];
75
76
  }
76
77
 
77
- export const RELAY_DOWN_GUIDANCE = "The Codex Desktop native relay is not listening after waiting for silent recovery. Its Desktop-owned supervisor restarts crashed relay workers without replaying requests. If Desktop has not loaded the relay MCP, reconnect it in a regular project task; automatic recovery never opens a task or window. Do not open the internal Native Relay executor task.";
78
+ export const RELAY_DOWN_GUIDANCE = "The Codex Desktop native relay is not listening after waiting for silent recovery. Its Desktop-owned supervisor restarts crashed relay workers without replaying requests. An unloaded native host cannot be cold-started by this bridge. Inspect codex_bridge_status and include its diagnostic in a report. Never open an app or an old, new, empty or failed chat to load a relay.";
78
79
 
79
80
  function relayUnreachable(error) {
80
81
  return error?.code === "RELAY_UNREACHABLE" && error.sent === false;
@@ -196,13 +197,17 @@ export class DesktopTaskDelivery {
196
197
  async status() {
197
198
  const accountContext = this.accountContext?.();
198
199
  const socketPath = this.relay.status?.({ accountContext })?.socketPath ?? this.relay.socketPath;
199
- if (this.accountContext && !accountContext) return { available: false, socketPath, reason: "The current Claude and Codex accounts could not both be verified. Finish signing in and inspect the account diagnostics before sending work." };
200
+ if (this.accountContext && !accountContext) {
201
+ const reason = "The current Claude and Codex accounts could not both be verified. Finish signing in and inspect the account diagnostics before sending work.";
202
+ return { available: false, socketPath, code: "BRIDGE_ACCOUNT_UNVERIFIED", reason, diagnostic: bridgeDiagnostic(new Error(reason), { component: "codex-bridge", operation: "codex_bridge_status", code: "BRIDGE_ACCOUNT_UNVERIFIED", readOnly: true }) };
203
+ }
200
204
  try {
201
205
  const response = await this.request("list_projects", {});
202
206
  if (!Array.isArray(response?.projects)) throw new Error("Desktop returned no project list");
203
207
  return { available: true, socketPath, localProjects: response.projects.filter((project) => project.projectKind === "local" && project.hostId === "local").length };
204
208
  } catch (err) {
205
- return { available: false, socketPath, reason: err.message };
209
+ const diagnostic = bridgeDiagnostic(err, { component: "codex-bridge", operation: "codex_bridge_status", readOnly: true });
210
+ return { available: false, socketPath, code: diagnostic.code, reason: err.message, diagnostic };
206
211
  }
207
212
  }
208
213
 
@@ -336,11 +341,11 @@ export class DesktopTaskDelivery {
336
341
  const confirmedId = response?.threadId ?? response?.conversationId;
337
342
  threadId = confirmedId;
338
343
  if (!threadId && (response?.status === "outcome-unknown" || response?.firstTurn?.status === "outcome-unknown")) threadId = response?.clientThreadId;
339
- if (typeof confirmedId !== "string" || !confirmedId.trim() || response?.status === "outcome-unknown" || response.hostId !== "local") throw new Error(`Desktop creation is not confirmed: ${JSON.stringify(response)}`);
344
+ if (typeof confirmedId !== "string" || !confirmedId.trim() || response?.status === "outcome-unknown" || response.hostId !== "local") throw Object.assign(new Error("Desktop creation is not confirmed; inspect the original native receipt."), { code: "CODEX_CREATION_UNCONFIRMED", deliveryState: "unknown", phase: "creation" });
340
345
  await this.receipts.write(identity.key, { ...receipt, state: "known", threadId });
341
346
  creationConfirmed = true;
342
347
  this.security.registerThread(threadId);
343
- if (response.firstTurn && response.firstTurn.status !== "accepted") throw new Error(`Desktop created task ${threadId}; its first turn reports ${response.firstTurn.status ?? "an unconfirmed state"}: ${JSON.stringify(response)}`);
348
+ if (response.firstTurn && response.firstTurn.status !== "accepted") throw Object.assign(new Error(`Desktop created task ${threadId}; its first turn reports ${["failed", "waitingOnApproval", "waitingOnUserInput", "outcome-unknown"].includes(response.firstTurn.status) ? response.firstTurn.status : "an unconfirmed state"}. Inspect the original native receipt.`), { code: "CODEX_FIRST_TURN_NOT_ACCEPTED", deliveryState: "sent", phase: "first_turn" });
344
349
  const created = { threadId, name, cwd, projectId: project.projectId, projectName: project.label, backend: NATIVE_BACKEND };
345
350
  const delivered = { ...created, previousTurnId: null, deliveryStatus: "accepted", responseObservation: {
346
351
  operation: "create_thread", threadId, previousTurnId: null, expectedCwd: cwd,
@@ -1,6 +1,6 @@
1
1
  param([Parameter(Mandatory)][string]$PipeName,[Parameter(Mandatory)][uint32]$ServerPid)
2
2
  $ErrorActionPreference='Stop'
3
- if($PipeName -notmatch '^LOCAL\\(?:codex-native-relay|codex-bridge|cc-msg)-[A-Za-z0-9_.-]+$'){throw 'Refusing a pipe outside the bridge-owned namespace.'}
3
+ if($PipeName -notmatch '^LOCAL\\(?:(?:codex-native-relay|codex-bridge|cc-msg)-[A-Za-z0-9_.-]+|minhspark-agent-relay-[a-f0-9]{24})$'){throw 'Refusing a pipe outside the bridge-owned namespace.'}
4
4
  Add-Type @'
5
5
  using System;
6
6
  using System.ComponentModel;