@minhspark/codex-mcp-bridge 1.17.0 → 1.18.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 CHANGED
@@ -4,6 +4,12 @@ Follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and [SemVer](ht
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [1.18.0] - 2026-09-23
8
+
9
+ ### Changed
10
+
11
+ - Guide agents to write every cross-agent prompt in English with a fixed section order: Goal, Context, Task, Scope, Constraints, Done when, Reply format. The Codex, Claude and VS Code bridges share one definition in their server instructions, sending-tool descriptions and prompt fields, and text supplied by the user is still sent unchanged.
12
+
7
13
  ## [1.17.0] - 2026-09-21
8
14
 
9
15
  ### Added
package/README.md CHANGED
@@ -231,6 +231,22 @@ Example requests:
231
231
 
232
232
  Use the exact project directory and destination task. If several Claude sessions match, specify the task ID. A `reply_received` receipt confirms a reply; a timeout does not mean the task stopped, so inspect it before retrying.
233
233
 
234
+ The sending tools ask agents to write each prompt in English with these sections, dropping any that do not apply: `Goal`, `Context`, `Task`, `Scope`, `Constraints`, `Done when`, `Reply format`. The first line names the sender, the project and the purpose. Text the user supplied is sent unchanged.
235
+
236
+ ```text
237
+ [From Claude Code · my-app · edit coordination]
238
+
239
+ ## Goal
240
+ Avoid conflicting edits while Claude Code patches `src/export.ps1`.
241
+
242
+ ## Task
243
+ 1. Do not modify `src/export.ps1` or `README.txt` until told otherwise.
244
+ 2. Write any unsaved edits to those files to disk now.
245
+
246
+ ## Reply format
247
+ Exactly one line: `DONE — changed: <files>` or `DONE — no changes`.
248
+ ```
249
+
234
250
  ## Important behavior
235
251
 
236
252
  - Desktop mode uses the native relay and the apps' permissions; it does not fall back to an external app-server.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@minhspark/codex-mcp-bridge",
3
- "version": "1.17.0",
3
+ "version": "1.18.0",
4
4
  "description": "Two-way MCP bridge between Claude and Codex: prompts into a live Codex thread, messages into a running Claude Code session.",
5
5
  "keywords": [
6
6
  "mcp",
@@ -22,7 +22,7 @@ import { createReleaseSnapshot, snapshotRoot } from "../src/release-snapshot.mjs
22
22
 
23
23
  exitForVersionRequest(import.meta.url);
24
24
 
25
- const VERSION = "1.17.0";
25
+ const VERSION = "1.18.0";
26
26
  const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
27
27
  const entry = path.join(root, "src", "mcp-supervisor.mjs");
28
28
  const entryArgs = ["native-relay-companion.mjs"];
@@ -22,10 +22,11 @@ import { readClaudeAccountContext } from "./desktop-account-context.mjs";
22
22
  import { assertAccountIdentity, bindUnsolicitedClaudeMessageAccount, publicAccountState, readBridgeAccounts, requireBridgeAccounts, sameAccountIdentity } from "./bridge-account-context.mjs";
23
23
  import { resolveClaudeDesktopSession, sameClaudeDesktopRecipient } from "./claude-session-router.mjs";
24
24
  import { createHardenedRootPolicy } from "./hardened-root-policy.mjs";
25
+ import { AGENT_PROMPT_GUIDANCE, PROMPT_FIELD_HINT } from "./prompt-guidance.mjs";
25
26
 
26
27
  exitForVersionRequest(import.meta.url);
27
28
 
28
- const VERSION = "1.17.0";
29
+ const VERSION = "1.18.0";
29
30
  void import("./telemetry.mjs").then(({ startUsageReporting }) => startUsageReporting({ version: VERSION })).catch(() => {});
30
31
  const FORWARD_MIN_INTERVAL_MS = 5000;
31
32
  const FORWARD_MAX_PER_SESSION = 50;
@@ -261,7 +262,8 @@ const server = new McpServer(
261
262
  "The sender class follows the verified approval policy; automated review flags are reported but never change it. " +
262
263
  "A Desktop recipient whose settings refuse or hold inbound messages, or whose task metadata shows a different permission class without an explicit accept, is refused before sending, because Claude Desktop cannot show the approval dialog. " +
263
264
  "Never launch a CLI session or an external app-server as a substitute. A receipt confirms a reply, not visual verification in the app. " +
264
- "A held receipt does not prove that Desktop exposes an approval button; verify the UI before asking the user to approve.",
265
+ "A held receipt does not prove that Desktop exposes an approval button; verify the UI before asking the user to approve. " +
266
+ AGENT_PROMPT_GUIDANCE,
265
267
  },
266
268
  );
267
269
 
@@ -280,7 +282,7 @@ registerTool(
280
282
  inputSchema: {
281
283
  requestId: z.string().describe("Stable UUID for this creation; reuse exactly when inspecting or retrying"),
282
284
  cwd: z.string().describe("Exact absolute directory of the existing Claude Desktop project"),
283
- prompt: z.string().describe("Initial prompt for the NEW conversation; shown for user confirmation"),
285
+ prompt: z.string().describe(`Initial prompt for the NEW conversation; shown for user confirmation. ${PROMPT_FIELD_HINT}`),
284
286
  },
285
287
  annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false },
286
288
  },
@@ -369,7 +371,7 @@ registerTool(
369
371
  "The sender's current permissions are verified per call; environment overrides and manual binding cannot bypass an unknown sender. It never creates a replacement session.",
370
372
  inputSchema: {
371
373
  target: z.string().describe("Use auto for current-account discovery in expectedCwd, an exact session identity, or new to prepare a separate Desktop conversation requiring user confirmation"),
372
- message: z.string().describe("The message text to deliver"),
374
+ message: z.string().describe(`The message text to deliver. ${PROMPT_FIELD_HINT}`),
373
375
  expectedCwd: z.string().optional().describe("Exact absolute project directory independently verified by the caller; required in Desktop-only mode"),
374
376
  expectedTaskId: z.string().optional().describe("Exact native Claude Desktop task ID from list_claude_sessions; verify its title in the app before sending"),
375
377
  waitSec: z
package/src/index.mjs CHANGED
@@ -30,10 +30,11 @@ import { createRuntimeState } from "./runtime-state.mjs";
30
30
  import { assertRoutingReload, clientReloadReason, createReloadControl } from "./reload-control.mjs";
31
31
  import { accountIdentity, assertAccountIdentity, publicAccountState, readBridgeAccounts, requireBridgeAccounts } from "./bridge-account-context.mjs";
32
32
  import { assertClaudeSenderContext, readClaudeSenderContext, requireClaudeSenderContext } from "./claude-sender-context.mjs";
33
+ import { AGENT_PROMPT_GUIDANCE, PROMPT_FIELD_HINT } from "./prompt-guidance.mjs";
33
34
 
34
35
  exitForVersionRequest(import.meta.url);
35
36
 
36
- const VERSION = "1.17.0";
37
+ const VERSION = "1.18.0";
37
38
  void import("./telemetry.mjs").then(({ startUsageReporting }) => startUsageReporting({ version: VERSION })).catch(() => {});
38
39
  const log = (msg) => process.stderr.write(`[codex-mcp-bridge] ${msg}\n`);
39
40
 
@@ -345,7 +346,8 @@ const server = new McpServer(
345
346
  "requested cwd. With Desktop tasks enabled it assigns the exact saved project and starts visibly in " +
346
347
  "Codex Desktop using Desktop permissions. Otherwise it releases the bridge writer lock and opens the exact thread in " +
347
348
  "Codex Desktop. Use send_to_codex_thread only when an existing threadId is intentional; use " +
348
- "list_codex_threads or read_codex_thread to inspect sessions and codex_bridge_status to inspect wiring.",
349
+ "list_codex_threads or read_codex_thread to inspect sessions and codex_bridge_status to inspect wiring. " +
350
+ AGENT_PROMPT_GUIDANCE,
349
351
  },
350
352
  );
351
353
 
@@ -400,7 +402,7 @@ registerTool(
400
402
  "return Codex's reply, and hand the session to Codex Desktop without leaving the bridge writer lock behind.",
401
403
  inputSchema: {
402
404
  cwd: z.string().describe("Absolute project directory where Codex must work"),
403
- prompt: z.string().describe("The complete task Claude is delegating to Codex"),
405
+ prompt: z.string().describe(`The complete task Claude is delegating to Codex. ${PROMPT_FIELD_HINT}`),
404
406
  name: z.string().min(1).max(200).optional().describe("Optional Codex session title; otherwise one is derived from the prompt"),
405
407
  timeoutSec: z
406
408
  .number()
@@ -494,7 +496,7 @@ registerTool(
494
496
  "Do not close the task, create a replacement, or ask the user to copy the message manually.",
495
497
  inputSchema: {
496
498
  threadId: z.string().describe("Codex thread id (UUID) - get it from list_codex_threads"),
497
- prompt: z.string().describe("The message to send to Codex, exactly as a user would type it"),
499
+ prompt: z.string().describe(`The message to send to Codex as a new user turn. ${PROMPT_FIELD_HINT}`),
498
500
  timeoutSec: z
499
501
  .number()
500
502
  .int()
@@ -703,7 +705,7 @@ registerTool(
703
705
  description: "Start a Codex task. In Desktop mode include the initial prompt to create and assign a visible task atomically; use delegate_to_codex to also wait for its reply.",
704
706
  inputSchema: {
705
707
  cwd: z.string().describe("Absolute working directory for the new Codex session"),
706
- prompt: z.string().min(1).optional().describe("Initial task; required with CODEX_BRIDGE_DESKTOP_TASKS=1, starts immediately"),
708
+ prompt: z.string().min(1).optional().describe(`Initial task; required with CODEX_BRIDGE_DESKTOP_TASKS=1, starts immediately. ${PROMPT_FIELD_HINT}`),
707
709
  model: z.string().optional().describe("Model override, e.g. gpt-5.6-luna"),
708
710
  name: z.string().min(1).max(200).optional().describe("Optional title to show for the new Codex session"),
709
711
  },
@@ -34,7 +34,7 @@ import { createHardenedRootPolicy } from "./hardened-root-policy.mjs";
34
34
 
35
35
  exitForVersionRequest(import.meta.url);
36
36
 
37
- const VERSION = "1.17.0";
37
+ const VERSION = "1.18.0";
38
38
  const log = (msg) => process.stderr.write(`[native-relay] ${msg}\n`);
39
39
 
40
40
  function errorResponse(code, message, sent) {
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Every message this bridge carries is a prompt one agent wrote for another.
3
+ * A single paragraph that runs the request into the reply format leaves the
4
+ * recipient guessing where the task ends, so the sending tools all describe
5
+ * one shape. It lives here so the Codex, Claude and VS Code bridges cannot
6
+ * drift apart.
7
+ */
8
+ export const PROMPT_SECTIONS = Object.freeze([
9
+ "Goal",
10
+ "Context",
11
+ "Task",
12
+ "Scope",
13
+ "Constraints",
14
+ "Done when",
15
+ "Reply format",
16
+ ]);
17
+
18
+ export const AGENT_PROMPT_GUIDANCE =
19
+ "Write every prompt you compose for another agent in English, in this order, omitting sections that do not apply: " +
20
+ "a first line naming the sender, project and purpose, e.g. [From Claude Code · <project> · <purpose>]; " +
21
+ "## Goal, the outcome in one sentence; ## Context, measured facts, paths with line numbers and existing code to reuse; " +
22
+ "## Task, numbered imperative steps; ## Scope, the files that may and must not change; " +
23
+ "## Constraints, conventions and tests, plus: if the current state differs from this description, stop and report instead of reconciling it; " +
24
+ "## Done when, checkable criteria; ## Reply format, the exact shape of the reply. " +
25
+ "A short coordination message needs only Goal, Context, Task and Reply format. " +
26
+ "Send text the user supplied verbatim, and keep file names, identifiers and user-facing strings in their original language.";
27
+
28
+ export const PROMPT_FIELD_HINT =
29
+ `The prompt for the recipient agent, in English with the sections ${PROMPT_SECTIONS.join(", ")} ` +
30
+ "(omit any that do not apply); text the user supplied is sent verbatim";
@@ -10,6 +10,7 @@ import { findRollout, readState, readCodexSenderContext } from "./codex-sender-c
10
10
  import { readProcessAncestry } from "./claude-sender-context.mjs";
11
11
  import { listClaudeSessions, assertClaudeSessionCwd, assertClaudeSessionProcess, PeerEndpoint } from "./peer-protocol.mjs";
12
12
  import { readClaudeInboundPolicy } from "./claude-inbound-policy.mjs";
13
+ import { AGENT_PROMPT_GUIDANCE, PROMPT_FIELD_HINT } from "./prompt-guidance.mjs";
13
14
 
14
15
  const host = process.argv[2];
15
16
  if (!["claude", "codex"].includes(host)) throw new Error("Specify the calling extension: claude or codex");
@@ -102,7 +103,7 @@ tool("vscode_bridge_status", "Verify this extension caller and list same-project
102
103
  });
103
104
 
104
105
  if (host === "claude") {
105
- tool("send_to_codex_vscode", "Send to an existing Codex extension task in this project, preserving its selected permissions. Never retries or opens another app. Use read_codex_vscode_reply after submission.", { threadId: z.string(), message: z.string().min(1).max(50000) }, async ({ threadId, message }, sender, meta) => {
106
+ tool("send_to_codex_vscode", `Send to an existing Codex extension task in this project, preserving its selected permissions. Never retries or opens another app. Use read_codex_vscode_reply after submission. ${AGENT_PROMPT_GUIDANCE}`, { threadId: z.string(), message: z.string().min(1).max(50000).describe(PROMPT_FIELD_HINT) }, async ({ threadId, message }, sender, meta) => {
106
107
  if (unresolved.has(threadId)) throw new Error("An earlier send is unresolved. Read its reply before sending again.");
107
108
  const inspect = () => {
108
109
  const state = codexTask(threadId, sender.cwd);
@@ -127,7 +128,7 @@ if (host === "claude") {
127
128
  return { threadId, ...completion };
128
129
  });
129
130
  } else {
130
- tool("send_to_claude_vscode", "Send to the exact Claude Code VS Code session in this project. Never changes recipient permissions. Do not ask the recipient to call back while this synchronous request waits.", { sessionId: z.string(), message: z.string().min(1).max(50000), waitSec: z.number().int().min(0).max(45).default(30) }, async ({ sessionId, message, waitSec }, sender, meta) => {
131
+ tool("send_to_claude_vscode", `Send to the exact Claude Code VS Code session in this project. Never changes recipient permissions. Do not ask the recipient to call back while this synchronous request waits. ${AGENT_PROMPT_GUIDANCE}`, { sessionId: z.string(), message: z.string().min(1).max(50000).describe(PROMPT_FIELD_HINT), waitSec: z.number().int().min(0).max(45).default(30) }, async ({ sessionId, message, waitSec }, sender, meta) => {
131
132
  const inspect = () => {
132
133
  const matches = listClaudeSessions().filter((session) => session.entrypoint === "claude-vscode" && session.sessionId === sessionId);
133
134
  if (matches.length !== 1) throw new Error("No unique live Claude Code VS Code session matches");