@minhspark/codex-mcp-bridge 1.22.20 → 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 +16 -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 +34 -7
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,22 @@ 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
|
+
|
|
7
23
|
## [1.22.20] - 2026-10-24
|
|
8
24
|
|
|
9
25
|
### 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
|
@@ -486,6 +486,28 @@ export function readTranscriptOutcome(sessionId, cwd, msgId) {
|
|
|
486
486
|
if (roots.length !== 1) return null;
|
|
487
487
|
const { entry: root, absorbed } = roots[0];
|
|
488
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
|
+
}
|
|
489
511
|
const descendants = new Map();
|
|
490
512
|
const backgroundCalls = new Map();
|
|
491
513
|
const openLeaves = new Set();
|
|
@@ -497,7 +519,7 @@ export function readTranscriptOutcome(sessionId, cwd, msgId) {
|
|
|
497
519
|
openLeaves.delete(entry.parentUuid);
|
|
498
520
|
interruptions.push({ entryId: entry.uuid, backgroundTaskIds: [...outstanding.keys()] });
|
|
499
521
|
};
|
|
500
|
-
for (const entry of
|
|
522
|
+
for (const entry of orderedEntries) {
|
|
501
523
|
if (entry === root) { descendants.set(entry.uuid, new Map()); openLeaves.add(entry.uuid); continue; }
|
|
502
524
|
let parentUuid = entry.parentUuid;
|
|
503
525
|
if (!ambiguous && !interruptions.length && descendants.has(entry.logicalParentUuid)
|
|
@@ -509,6 +531,7 @@ export function readTranscriptOutcome(sessionId, cwd, msgId) {
|
|
|
509
531
|
}
|
|
510
532
|
}
|
|
511
533
|
if (!descendants.has(parentUuid)) continue;
|
|
534
|
+
let taskNotification = null;
|
|
512
535
|
if (entry.type === "attachment" && entry.attachment?.type === "queued_command") {
|
|
513
536
|
// Only a native command record is a boundary. Its text is never used as
|
|
514
537
|
// proof of authorization, cancellation, or successful task completion.
|
|
@@ -516,12 +539,12 @@ export function readTranscriptOutcome(sessionId, cwd, msgId) {
|
|
|
516
539
|
interrupt(entry, descendants.get(entry.parentUuid));
|
|
517
540
|
continue;
|
|
518
541
|
}
|
|
519
|
-
//
|
|
520
|
-
//
|
|
521
|
-
// 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.
|
|
522
544
|
if (entry.attachment.commandMode !== "task-notification"
|
|
523
545
|
|| entry.attachment.origin?.kind !== "task-notification"
|
|
524
546
|
|| entry.attachment.origin?.producer !== "session-task") continue;
|
|
547
|
+
taskNotification = entry.attachment.prompt;
|
|
525
548
|
}
|
|
526
549
|
const role = entry.message?.role;
|
|
527
550
|
const content = entry.message?.content;
|
|
@@ -548,14 +571,18 @@ export function readTranscriptOutcome(sessionId, cwd, msgId) {
|
|
|
548
571
|
if (entry.origin?.kind !== "task-notification" || entry.origin.producer !== "session-task"
|
|
549
572
|
|| entry.promptSource !== "system" || entry.turnOrigin !== "task_notification"
|
|
550
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())) {
|
|
551
577
|
const field = (name) => {
|
|
552
|
-
const matches = [...
|
|
578
|
+
const matches = [...taskNotification.matchAll(new RegExp(`<${name}>([^<>]+)</${name}>`, "g"))];
|
|
553
579
|
return matches.length === 1 ? matches[0][1] : null;
|
|
554
580
|
};
|
|
555
581
|
const taskId = field("task-id");
|
|
556
582
|
if (!outstanding.has(taskId) || outstanding.get(taskId) !== field("tool-use-id")
|
|
557
|
-
|| !["completed", "failed", "killed"].includes(field("status")))
|
|
558
|
-
|
|
583
|
+
|| !["completed", "failed", "killed"].includes(field("status"))) {
|
|
584
|
+
if (role === "user") continue;
|
|
585
|
+
} else outstanding.delete(taskId);
|
|
559
586
|
}
|
|
560
587
|
if (typeof entry.uuid !== "string" || !entry.uuid.trim()) continue;
|
|
561
588
|
if (role === "assistant" && Array.isArray(content)) {
|