@minhspark/codex-mcp-bridge 1.22.19 → 1.22.21
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/.codex-plugin/plugin.json +1 -1
- package/CHANGELOG.md +32 -0
- package/package.json +2 -2
- package/plugin.json +1 -1
- package/scripts/install-native-relay.mjs +1 -1
- package/src/claude-bridge.mjs +1 -1
- package/src/index.mjs +1 -1
- package/src/native-relay-companion.mjs +1 -1
- package/src/peer-protocol.mjs +41 -8
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,38 @@ Follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and [SemVer](ht
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [1.22.21] - 2026-10-26
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- Recognize native Claude background-task completion notifications stored as inline attachments. A completed command could otherwise remain outstanding, hiding the final reply and blocking later messages with `PEER_REPLY_PENDING`. Both notification formats retain native-origin, exact task/tool-call identity and transcript ancestry checks; observing the original reply never resends its action.
|
|
12
|
+
- Resolve Claude reply ancestry when a final response is persisted before its parent attachment. Process unambiguous records with ancestors first, reject cycles, and preserve input boundaries and pending ownership until the required parent arrives.
|
|
13
|
+
|
|
14
|
+
### Verification
|
|
15
|
+
|
|
16
|
+
- Three focused regressions fail against 1.22.20 and pass with this fix: inline completion, child-before-parent ancestry, and pending ownership recovery without repeating the original action.
|
|
17
|
+
- Independent parser checks cover 1,320 record-order and invalid-notification combinations. Endpoint tests verify that missing parents keep ownership pending and that a distinct follow-up is allowed only after the original reply resolves.
|
|
18
|
+
|
|
19
|
+
### Limitations
|
|
20
|
+
|
|
21
|
+
- Missing or ambiguous transcript ancestry still cannot establish a reply. Inspect the existing task instead of resending its original action. An already blocked older runtime may need a client restart after upgrading to load this fix.
|
|
22
|
+
|
|
23
|
+
## [1.22.20] - 2026-10-24
|
|
24
|
+
|
|
25
|
+
### Fixed
|
|
26
|
+
|
|
27
|
+
- A Claude Desktop target no longer stays blocked for good when another sender's peer message lands in a reply chain that is waiting on a background task. Reply observation used to drop that message and everything after it, so the delivery never resolved, every later send to that session failed with `PEER_REPLY_PENDING`, and reload stayed refused until Codex Desktop restarted. A peer message for a different message id now ends observation the same way human input does: the delivery is reported as interrupted, the task outcome stays unknown, and it is not marked safe to retry.
|
|
28
|
+
- Peer interruption requires a nonblank string message id distinct from the tracked request. Missing, blank, non-string and matching ids cannot release pending ownership. Existing transcript ancestry, ambiguity and open-branch checks remain in force.
|
|
29
|
+
|
|
30
|
+
### Tests
|
|
31
|
+
|
|
32
|
+
- Transcript regressions cover a background wait interrupted by another peer, malformed peer ids, unrelated ancestry, incomplete or duplicate records, and unresolved sibling branches.
|
|
33
|
+
- Endpoint regressions verify late interruption after a timeout, pending-send and reload recovery, retained interruption receipts across reload, and a separately requested follow-up without resending the original action.
|
|
34
|
+
|
|
35
|
+
### Limitations
|
|
36
|
+
|
|
37
|
+
- Interruption ends reply observation only. Background work may still be running; inspect the existing task before issuing a follow-up. The original action is never marked safe to retry.
|
|
38
|
+
|
|
7
39
|
## [1.22.19] - 2026-10-22
|
|
8
40
|
|
|
9
41
|
### Fixed
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@minhspark/codex-mcp-bridge",
|
|
3
|
-
"version": "1.22.
|
|
3
|
+
"version": "1.22.21",
|
|
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",
|
|
@@ -83,6 +83,6 @@
|
|
|
83
83
|
},
|
|
84
84
|
"dependencies": {
|
|
85
85
|
"@modelcontextprotocol/sdk": "^1.32.1",
|
|
86
|
-
"zod": "^
|
|
86
|
+
"zod": "^4.6.7"
|
|
87
87
|
}
|
|
88
88
|
}
|
package/plugin.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
|
|
3
3
|
"name": "codex-mcp-bridge",
|
|
4
|
-
"version": "1.22.
|
|
4
|
+
"version": "1.22.21",
|
|
5
5
|
"description": "Connect ChatGPT to your own Codex Desktop projects and conversations.",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Bui Dang Minh",
|
|
@@ -25,7 +25,7 @@ import { removeRelayFile } from "../src/native-relay-files.mjs";
|
|
|
25
25
|
|
|
26
26
|
exitForVersionRequest(import.meta.url);
|
|
27
27
|
|
|
28
|
-
const VERSION = "1.22.
|
|
28
|
+
const VERSION = "1.22.21";
|
|
29
29
|
const root = stableInstallPath(path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."));
|
|
30
30
|
const entry = path.join(root, "src", "mcp-supervisor.mjs");
|
|
31
31
|
const entryArgs = ["native-relay-companion.mjs"];
|
package/src/claude-bridge.mjs
CHANGED
|
@@ -30,7 +30,7 @@ import { discoverClaudeProject } from "./claude-project-discovery.mjs";
|
|
|
30
30
|
|
|
31
31
|
exitForVersionRequest(import.meta.url);
|
|
32
32
|
|
|
33
|
-
const VERSION = "1.22.
|
|
33
|
+
const VERSION = "1.22.21";
|
|
34
34
|
void import("./telemetry.mjs").then(({ startUsageReporting }) => startUsageReporting({ version: VERSION })).catch(() => {});
|
|
35
35
|
const FORWARD_MIN_INTERVAL_MS = 5000;
|
|
36
36
|
const FORWARD_MAX_PER_SESSION = 50;
|
package/src/index.mjs
CHANGED
|
@@ -41,7 +41,7 @@ import { createRelayWake, relayKeeperEnabled, relayWakeStateFile, startRelayKeep
|
|
|
41
41
|
|
|
42
42
|
exitForVersionRequest(import.meta.url);
|
|
43
43
|
|
|
44
|
-
const VERSION = "1.22.
|
|
44
|
+
const VERSION = "1.22.21";
|
|
45
45
|
void import("./telemetry.mjs").then(({ startUsageReporting }) => startUsageReporting({ version: VERSION })).catch(() => {});
|
|
46
46
|
const log = (msg) => process.stderr.write(`[codex-mcp-bridge] ${msg}\n`);
|
|
47
47
|
|
|
@@ -40,7 +40,7 @@ import { NativeCreationReceipts, creationIdValid } from "./native-creation-recei
|
|
|
40
40
|
|
|
41
41
|
exitForVersionRequest(import.meta.url);
|
|
42
42
|
|
|
43
|
-
const VERSION = "1.22.
|
|
43
|
+
const VERSION = "1.22.21";
|
|
44
44
|
const log = (msg) => process.stderr.write(`[native-relay] ${msg}\n`);
|
|
45
45
|
|
|
46
46
|
function errorResponse(code, message, sent, details) {
|
package/src/peer-protocol.mjs
CHANGED
|
@@ -367,6 +367,12 @@ function injectedMessageRoot(entry, msgId) {
|
|
|
367
367
|
return null;
|
|
368
368
|
}
|
|
369
369
|
|
|
370
|
+
function isOtherPeerInput(entry, msgId) {
|
|
371
|
+
const origin = entry.origin;
|
|
372
|
+
return origin?.kind === "peer" && typeof origin.msg_id === "string"
|
|
373
|
+
&& origin.msg_id.trim().length > 0 && origin.msg_id !== msgId;
|
|
374
|
+
}
|
|
375
|
+
|
|
370
376
|
function isInterruptedDelivery(receipt) {
|
|
371
377
|
return receipt?.status === "interrupted" && receipt.source === "transcript"
|
|
372
378
|
&& receipt.reasonCode === "CLAUDE_REPLY_INTERRUPTED"
|
|
@@ -480,6 +486,28 @@ export function readTranscriptOutcome(sessionId, cwd, msgId) {
|
|
|
480
486
|
if (roots.length !== 1) return null;
|
|
481
487
|
const { entry: root, absorbed } = roots[0];
|
|
482
488
|
const entriesById = new Map(entries.map((entry) => [entry.uuid, entry]));
|
|
489
|
+
const orderedEntries = ambiguous ? entries : [];
|
|
490
|
+
if (!ambiguous) {
|
|
491
|
+
// Claude can persist a reply before its parent attachment; visit late
|
|
492
|
+
// ancestors before their children instead of assuming append order.
|
|
493
|
+
const visited = new Set();
|
|
494
|
+
for (const entry of entries) {
|
|
495
|
+
const ancestors = [];
|
|
496
|
+
const visiting = new Set();
|
|
497
|
+
let ancestor = entry;
|
|
498
|
+
while (ancestor && !visited.has(ancestor)) {
|
|
499
|
+
if (visiting.has(ancestor)) return null;
|
|
500
|
+
visiting.add(ancestor);
|
|
501
|
+
ancestors.push(ancestor);
|
|
502
|
+
ancestor = typeof ancestor.parentUuid === "string" ? entriesById.get(ancestor.parentUuid) : null;
|
|
503
|
+
}
|
|
504
|
+
while (ancestors.length) {
|
|
505
|
+
const ancestor = ancestors.pop();
|
|
506
|
+
visited.add(ancestor);
|
|
507
|
+
orderedEntries.push(ancestor);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
}
|
|
483
511
|
const descendants = new Map();
|
|
484
512
|
const backgroundCalls = new Map();
|
|
485
513
|
const openLeaves = new Set();
|
|
@@ -491,7 +519,7 @@ export function readTranscriptOutcome(sessionId, cwd, msgId) {
|
|
|
491
519
|
openLeaves.delete(entry.parentUuid);
|
|
492
520
|
interruptions.push({ entryId: entry.uuid, backgroundTaskIds: [...outstanding.keys()] });
|
|
493
521
|
};
|
|
494
|
-
for (const entry of
|
|
522
|
+
for (const entry of orderedEntries) {
|
|
495
523
|
if (entry === root) { descendants.set(entry.uuid, new Map()); openLeaves.add(entry.uuid); continue; }
|
|
496
524
|
let parentUuid = entry.parentUuid;
|
|
497
525
|
if (!ambiguous && !interruptions.length && descendants.has(entry.logicalParentUuid)
|
|
@@ -503,6 +531,7 @@ export function readTranscriptOutcome(sessionId, cwd, msgId) {
|
|
|
503
531
|
}
|
|
504
532
|
}
|
|
505
533
|
if (!descendants.has(parentUuid)) continue;
|
|
534
|
+
let taskNotification = null;
|
|
506
535
|
if (entry.type === "attachment" && entry.attachment?.type === "queued_command") {
|
|
507
536
|
// Only a native command record is a boundary. Its text is never used as
|
|
508
537
|
// proof of authorization, cancellation, or successful task completion.
|
|
@@ -510,12 +539,12 @@ export function readTranscriptOutcome(sessionId, cwd, msgId) {
|
|
|
510
539
|
interrupt(entry, descendants.get(entry.parentUuid));
|
|
511
540
|
continue;
|
|
512
541
|
}
|
|
513
|
-
//
|
|
514
|
-
//
|
|
515
|
-
// its text alone never completes any outstanding background command.
|
|
542
|
+
// Inline native notifications preserve the chain; only matching task and
|
|
543
|
+
// tool-call identities below can complete an outstanding command.
|
|
516
544
|
if (entry.attachment.commandMode !== "task-notification"
|
|
517
545
|
|| entry.attachment.origin?.kind !== "task-notification"
|
|
518
546
|
|| entry.attachment.origin?.producer !== "session-task") continue;
|
|
547
|
+
taskNotification = entry.attachment.prompt;
|
|
519
548
|
}
|
|
520
549
|
const role = entry.message?.role;
|
|
521
550
|
const content = entry.message?.content;
|
|
@@ -532,7 +561,7 @@ export function readTranscriptOutcome(sessionId, cwd, msgId) {
|
|
|
532
561
|
&& (typeof content === "string" && content.trim()
|
|
533
562
|
|| Array.isArray(content) && content.length > 0 && content.every((part) => part?.type === "text" && typeof part.text === "string")
|
|
534
563
|
&& content.some((part) => part.text.trim()));
|
|
535
|
-
if (ordinaryInput) {
|
|
564
|
+
if (ordinaryInput || isOtherPeerInput(entry, msgId)) {
|
|
536
565
|
interrupt(entry, outstanding);
|
|
537
566
|
continue;
|
|
538
567
|
}
|
|
@@ -542,14 +571,18 @@ export function readTranscriptOutcome(sessionId, cwd, msgId) {
|
|
|
542
571
|
if (entry.origin?.kind !== "task-notification" || entry.origin.producer !== "session-task"
|
|
543
572
|
|| entry.promptSource !== "system" || entry.turnOrigin !== "task_notification"
|
|
544
573
|
|| typeof content !== "string" || !/^<task-notification>\s[\s\S]*<\/task-notification>$/.test(content.trim())) continue;
|
|
574
|
+
taskNotification = content;
|
|
575
|
+
}
|
|
576
|
+
if (typeof taskNotification === "string" && /^<task-notification>\s[\s\S]*<\/task-notification>$/.test(taskNotification.trim())) {
|
|
545
577
|
const field = (name) => {
|
|
546
|
-
const matches = [...
|
|
578
|
+
const matches = [...taskNotification.matchAll(new RegExp(`<${name}>([^<>]+)</${name}>`, "g"))];
|
|
547
579
|
return matches.length === 1 ? matches[0][1] : null;
|
|
548
580
|
};
|
|
549
581
|
const taskId = field("task-id");
|
|
550
582
|
if (!outstanding.has(taskId) || outstanding.get(taskId) !== field("tool-use-id")
|
|
551
|
-
|| !["completed", "failed", "killed"].includes(field("status")))
|
|
552
|
-
|
|
583
|
+
|| !["completed", "failed", "killed"].includes(field("status"))) {
|
|
584
|
+
if (role === "user") continue;
|
|
585
|
+
} else outstanding.delete(taskId);
|
|
553
586
|
}
|
|
554
587
|
if (typeof entry.uuid !== "string" || !entry.uuid.trim()) continue;
|
|
555
588
|
if (role === "assistant" && Array.isArray(content)) {
|