@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 +6 -0
- package/README.md +16 -0
- package/package.json +1 -1
- package/scripts/install-native-relay.mjs +1 -1
- package/src/claude-bridge.mjs +6 -4
- package/src/index.mjs +7 -5
- package/src/native-relay-companion.mjs +1 -1
- package/src/prompt-guidance.mjs +30 -0
- package/src/vscode-bridge.mjs +3 -2
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
|
@@ -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.
|
|
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"];
|
package/src/claude-bridge.mjs
CHANGED
|
@@ -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.
|
|
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(
|
|
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(
|
|
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.
|
|
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(
|
|
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(
|
|
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(
|
|
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.
|
|
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";
|
package/src/vscode-bridge.mjs
CHANGED
|
@@ -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",
|
|
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",
|
|
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");
|