@minhspark/codex-mcp-bridge 1.13.0 → 1.13.1
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 +13 -0
- package/README.md +8 -4
- package/package.json +1 -1
- package/scripts/install-claude-desktop.mjs +3 -1
- package/scripts/install-native-relay.mjs +1 -1
- package/src/claude-bridge.mjs +1 -1
- package/src/index.mjs +28 -6
- package/src/native-relay-companion.mjs +1 -1
- package/src/native-relay.mjs +3 -0
- package/src/thread-delivery.mjs +55 -4
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
Follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and [SemVer](https://semver.org/).
|
|
4
4
|
|
|
5
|
+
## [1.13.1] - 2026-09-05
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- Make Desktop task mode independent of the external app-server for status, thread discovery, diagnostics, and fallback delivery. A missing native relay reports an actionable failure without starting another process or taking a task's writer lock.
|
|
10
|
+
- Verify native connectivity through Desktop projects and list its authorized local recent/pinned Codex tasks. Explain the snapshot coverage and reject unsupported `loadedOnly` queries instead of returning external-server state.
|
|
11
|
+
- Persist external autostart off when installing Claude Desktop in Desktop task mode, while preserving explicit legacy mode and existing permission settings. Reconnect the native companion after upgrading to enable the new allowlisted thread-list operation.
|
|
12
|
+
|
|
13
|
+
### Verification
|
|
14
|
+
|
|
15
|
+
- Windows suite: 300 passed, 0 failed, 8 platform-specific skips. Added absent-relay, zero-external-connection, native-list authorization, and overlapping-send regressions.
|
|
16
|
+
- Live Windows checks returned bridge 1.13.1, eight native local projects, recent Desktop tasks, and an existing task's history while port 8791 remained stopped.
|
|
17
|
+
|
|
5
18
|
## [1.13.0] - 2026-09-05
|
|
6
19
|
|
|
7
20
|
### Added
|
package/README.md
CHANGED
|
@@ -24,7 +24,11 @@ Call `delegate_to_codex` with `cwd`, `prompt`, and optionally `name`. The bridge
|
|
|
24
24
|
|
|
25
25
|
`start_codex_thread` requires `prompt` in Desktop mode and returns after acceptance. Use `delegate_to_codex` to also wait for the reply. `send_to_codex_thread` continues the existing Desktop task without attaching another writer; its cwd cannot be changed. On timeout, the task continues. Inspect it in Desktop and use its Stop button to interrupt it. Quota failures and approval/input requests remain visible; delivery does not bypass them. An uncertain creation or send is never automatically repeated through another backend.
|
|
26
26
|
|
|
27
|
-
The updated companion exposes a separate `-desktop-tasks` endpoint so a previous companion holding the legacy reply socket need not be killed during an upgrade. `codex_bridge_status`
|
|
27
|
+
The updated companion exposes a separate `-desktop-tasks` endpoint so a previous companion holding the legacy reply socket need not be killed during an upgrade. `codex_bridge_status` verifies the native connection through Desktop's local project list; `native_relay_status` reports both endpoints. Reconnect the companion after upgrading so it accepts the current native operations.
|
|
28
|
+
|
|
29
|
+
Desktop mode never contacts, starts, or falls back to an external app-server, including during status checks and thread discovery. The Claude Desktop installer records `CODEX_BRIDGE_AUTOSTART=0` in this mode. If Desktop or its companion is unavailable, the bridge reports the failure and leaves existing tasks in Desktop. `stop_codex_app_server` is a no-op in Desktop mode; stop an obsolete external service through its own launcher after confirming it has no active work.
|
|
30
|
+
|
|
31
|
+
`list_codex_threads` lists authorized local Codex tasks from Desktop's latest 50 non-pinned tasks and all pinned tasks, then applies workspace/title filters and the requested result limit. This snapshot does not cover the complete archive. Desktop does not expose the external server's `loadedOnly` state; requesting it returns an explanation without contacting that server.
|
|
28
32
|
|
|
29
33
|
## Architecture
|
|
30
34
|
|
|
@@ -46,7 +50,7 @@ both bridges ───────┤ named pipe / unix socket
|
|
|
46
50
|
└────────────────────────────────────────────────────────────────────┘
|
|
47
51
|
```
|
|
48
52
|
|
|
49
|
-
-
|
|
53
|
+
- In app-server mode, the app-server is a **singleton per port**. The bridge probes `http://127.0.0.1:8791/readyz`; with autostart enabled, if nothing answers it spawns a detached `codex app-server --listen ws://127.0.0.1:8791`, which keeps running after the bridge exits. Desktop mode does not use this path.
|
|
50
54
|
- Every client pointed at the same URL shares **one app-server**, so `thread/resume` with a `threadId` rejoins the running thread instead of opening a new session.
|
|
51
55
|
- The bridge keeps exactly one WebSocket, calls `initialize` once, and routes notifications by `threadId`, so parallel threads never bleed into each other.
|
|
52
56
|
- In app-server mode, `delegate_to_codex` starts the thread at the supplied `cwd`, names it, sends the prompt, and unsubscribes that thread after a terminal turn. It opens `codex://threads/<id>` only after unload is confirmed; other threads on the shared app-server keep running. Desktop mode instead creates and assigns the task through the app immediately.
|
|
@@ -314,7 +318,7 @@ On macOS and Linux the `codex` launcher is a Node script with a `#!/usr/bin/env
|
|
|
314
318
|
|---|---|---|
|
|
315
319
|
| `delegate_to_codex` | Creates a named Codex thread at the requested project `cwd`, sends Claude's prompt, returns the reply, releases the bridge writer lock, and opens the exact session in Codex Desktop when enabled. | destructive |
|
|
316
320
|
| `send_to_codex_thread` | Sends a prompt as a user turn into `threadId`, waits for `turn/completed`, returns Codex's reply plus an activity trail (commands run, files changed). | destructive |
|
|
317
|
-
| `list_codex_threads` | Lists threads (id, title, cwd, last update, status) so you can pick the **exact** `threadId`.
|
|
321
|
+
| `list_codex_threads` | Lists threads (id, title, cwd, last update, status) so you can pick the **exact** `threadId`. Desktop mode uses recent/pinned authorized local tasks; app-server mode supports `loadedOnly: true`. Windows and macOS rows carry a `codex://threads/<id>` deep link. | read-only |
|
|
318
322
|
| `start_codex_thread` | Opens a new Codex thread at a permitted `cwd`, optionally names it, and returns its `threadId`; the bridge applies its configured safe sandbox and approval policy. | writes |
|
|
319
323
|
| `read_codex_thread` | Reads the recent conversation without sending anything. | read-only |
|
|
320
324
|
| `interrupt_codex_turn` | Stops a turn that is still running. | destructive |
|
|
@@ -520,7 +524,7 @@ The bridge reads these from the environment its MCP client hands it — there is
|
|
|
520
524
|
|---|---|---|
|
|
521
525
|
| `CODEX_APP_SERVER_URL` | `ws://127.0.0.1:8791` | Shared **loopback-only** app-server endpoint. Non-loopback endpoints are rejected because this bridge does not implement remote WebSocket authentication. |
|
|
522
526
|
| `CODEX_BIN` | auto-detected | Path to `codex` used for autostart. |
|
|
523
|
-
| `CODEX_BRIDGE_AUTOSTART` | `1` | `0` = never spawn an app-server
|
|
527
|
+
| `CODEX_BRIDGE_AUTOSTART` | `1` in app-server mode | `0` = never spawn an external app-server. Always off in Desktop mode, which does not need one. |
|
|
524
528
|
| `CODEX_BRIDGE_THREAD_POLICY` | `owned` in the direct server; installer writes `roots` for new v1.11.2 entries | What authorizes a thread: `owned` (created by this bridge, or listed in `CODEX_BRIDGE_ALLOWED_THREADS`) or `roots` (working inside `CODEX_BRIDGE_ALLOWED_ROOTS`). Existing config values are preserved on upgrade. |
|
|
525
529
|
| `CODEX_BRIDGE_ALLOWED_THREADS` | empty | Exact comma-separated thread IDs permitted for read/send/interrupt/open/list; `*` explicitly permits every thread ID. Under `roots`, the workspace check still runs. |
|
|
526
530
|
| `CODEX_BRIDGE_ALLOWED_ROOTS` | `*` in the v1.11.2 installer | Absolute project directories permitted for `cwd`, separated by `:` (`;` on Windows); `*` means every usable workspace. |
|
package/package.json
CHANGED
|
@@ -50,6 +50,7 @@ cfg.mcpServers = cfg.mcpServers ?? {};
|
|
|
50
50
|
*/
|
|
51
51
|
const previousEnv = (reset ? {} : cfg.mcpServers["codex-bridge"]?.env) ?? {};
|
|
52
52
|
const settled = (name, fallback) => process.env[name] ?? previousEnv[name] ?? fallback;
|
|
53
|
+
const desktopMode = settled("CODEX_BRIDGE_DESKTOP_TASKS", desktopTasksConfigured() ? "1" : "0");
|
|
53
54
|
|
|
54
55
|
const kept = Object.keys(previousEnv).filter(
|
|
55
56
|
(name) => process.env[name] === undefined && name !== "CODEX_BIN",
|
|
@@ -86,7 +87,8 @@ cfg.mcpServers["codex-bridge"] = {
|
|
|
86
87
|
CODEX_BRIDGE_SANDBOX: settled("CODEX_BRIDGE_SANDBOX", "workspace-write"),
|
|
87
88
|
CODEX_BRIDGE_OPEN_IN_APP: settled("CODEX_BRIDGE_OPEN_IN_APP", IS_WINDOWS ? "1" : "0"),
|
|
88
89
|
CODEX_BRIDGE_RELEASE_AFTER_TURN: settled("CODEX_BRIDGE_RELEASE_AFTER_TURN", IS_WINDOWS ? "1" : "0"),
|
|
89
|
-
CODEX_BRIDGE_DESKTOP_TASKS:
|
|
90
|
+
CODEX_BRIDGE_DESKTOP_TASKS: desktopMode,
|
|
91
|
+
CODEX_BRIDGE_AUTOSTART: desktopMode === "1" ? "0" : settled("CODEX_BRIDGE_AUTOSTART", "1"),
|
|
90
92
|
...(process.env.CODEX_BRIDGE_ALLOWED_THREADS !== undefined
|
|
91
93
|
? { CODEX_BRIDGE_ALLOWED_THREADS: process.env.CODEX_BRIDGE_ALLOWED_THREADS }
|
|
92
94
|
: {}),
|
|
@@ -8,7 +8,7 @@ import { CodexAppServerClient } from "../src/app-server-client.mjs";
|
|
|
8
8
|
import { bootstrapRelayThread, readRelayConfig, relayConfigPath, relaySocketPath, writeRelayConfig } from "../src/native-relay.mjs";
|
|
9
9
|
import { IS_MACOS, IS_WINDOWS, PLATFORM_LABEL, homeDir, resolveCodexBin, spawnEnv } from "../src/platform.mjs";
|
|
10
10
|
|
|
11
|
-
const VERSION = "1.13.
|
|
11
|
+
const VERSION = "1.13.1";
|
|
12
12
|
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
13
13
|
const entry = path.join(root, "src", "native-relay-companion.mjs");
|
|
14
14
|
const serverName = process.env.CODEX_NATIVE_RELAY_NAME ?? "codex-native-relay";
|
package/src/claude-bridge.mjs
CHANGED
|
@@ -8,7 +8,7 @@ import { PLATFORM_LABEL } from "./platform.mjs";
|
|
|
8
8
|
import { PeerEndpoint, findClaudeSession, listClaudeSessions, readTranscript } from "./peer-protocol.mjs";
|
|
9
9
|
import { createThreadDelivery } from "./thread-delivery.mjs";
|
|
10
10
|
|
|
11
|
-
const VERSION = "1.13.
|
|
11
|
+
const VERSION = "1.13.1";
|
|
12
12
|
const FORWARD_MIN_INTERVAL_MS = 5000;
|
|
13
13
|
const FORWARD_MAX_PER_SESSION = 50;
|
|
14
14
|
|
package/src/index.mjs
CHANGED
|
@@ -25,7 +25,7 @@ import { BridgeSecurityPolicy } from "./security-policy.mjs";
|
|
|
25
25
|
import { DesktopTaskDelivery } from "./thread-delivery.mjs";
|
|
26
26
|
import { desktopTasksConfigured } from "./native-relay.mjs";
|
|
27
27
|
|
|
28
|
-
const VERSION = "1.13.
|
|
28
|
+
const VERSION = "1.13.1";
|
|
29
29
|
const log = (msg) => process.stderr.write(`[codex-mcp-bridge] ${msg}\n`);
|
|
30
30
|
|
|
31
31
|
/**
|
|
@@ -47,7 +47,7 @@ const security = new BridgeSecurityPolicy();
|
|
|
47
47
|
const desktopTasksEnabled = desktopTasksConfigured();
|
|
48
48
|
const desktopTasks = new DesktopTaskDelivery({ security });
|
|
49
49
|
|
|
50
|
-
const client = new CodexAppServerClient({
|
|
50
|
+
const client = desktopTasksEnabled ? null : new CodexAppServerClient({
|
|
51
51
|
clientInfo: { name: "codex-mcp-bridge", title: "Codex MCP Bridge", version: VERSION },
|
|
52
52
|
log,
|
|
53
53
|
});
|
|
@@ -212,9 +212,9 @@ async function finishDesktopHandoff({ threadId, result, openInApp, releaseAfterT
|
|
|
212
212
|
}
|
|
213
213
|
|
|
214
214
|
function formatThreadRow(t) {
|
|
215
|
-
const title = t.name || (t.preview ?? "").replace(/\s+/g, " ").slice(0, 70) || "(no title)";
|
|
215
|
+
const title = t.title || t.name || (t.preview ?? "").replace(/\s+/g, " ").slice(0, 70) || "(no title)";
|
|
216
216
|
const updated = t.updatedAt ? new Date(t.updatedAt * 1000).toISOString().replace("T", " ").slice(0, 16) : "?";
|
|
217
|
-
const status = t.status?.type ?? "?";
|
|
217
|
+
const status = typeof t.status === "string" ? t.status : t.status?.type ?? "?";
|
|
218
218
|
const deepLink = supportsCodexThreadLinks() ? `\n open: ${codexThreadUrl(t.id)}` : "";
|
|
219
219
|
const authorized = security.isThreadAuthorized(t.id, t.cwd)
|
|
220
220
|
? ""
|
|
@@ -408,7 +408,7 @@ server.registerTool(
|
|
|
408
408
|
},
|
|
409
409
|
},
|
|
410
410
|
async ({ threadId, prompt, timeoutSec, cwd, model, effort, name, openInApp, releaseAfterTurn }) => {
|
|
411
|
-
return client.withThread(threadId, async () => {
|
|
411
|
+
return (desktopTasksEnabled ? desktopTasks : client).withThread(threadId, async () => {
|
|
412
412
|
const notes = [];
|
|
413
413
|
const shouldOpen = openInApp ?? DEFAULT_OPEN_IN_APP;
|
|
414
414
|
const shouldRelease = releaseAfterTurn ?? DEFAULT_RELEASE_AFTER_TURN;
|
|
@@ -513,6 +513,11 @@ server.registerTool(
|
|
|
513
513
|
},
|
|
514
514
|
async ({ limit, cwd, searchTerm, loadedOnly }) => {
|
|
515
515
|
try {
|
|
516
|
+
if (desktopTasksEnabled) {
|
|
517
|
+
const workspace = cwd ? resolveWorkspacePath(cwd) : null;
|
|
518
|
+
const { rows, coverage } = await desktopTasks.list({ limit, cwd: workspace?.path, searchTerm, loadedOnly });
|
|
519
|
+
return textResult(`${rows.length} Codex thread(s) via Codex Desktop:\n${coverage}\n\n${rows.length ? rows.map(formatThreadRow).join("\n") : "No matching authorized local Codex tasks in this snapshot."}`);
|
|
520
|
+
}
|
|
516
521
|
const params = { limit: limit ?? 15 };
|
|
517
522
|
if (cwd) {
|
|
518
523
|
const workspace = resolveWorkspacePath(cwd);
|
|
@@ -754,6 +759,7 @@ server.registerTool(
|
|
|
754
759
|
},
|
|
755
760
|
async () => {
|
|
756
761
|
try {
|
|
762
|
+
if (desktopTasksEnabled) return textResult("Desktop-only mode does not manage an external app-server. Codex Desktop and its running tasks were left unchanged.");
|
|
757
763
|
const result = await client.stopServer();
|
|
758
764
|
if (result.stillListening) {
|
|
759
765
|
return textResult("The app-server is still listening after the stop request; its thread writer locks are not confirmed released.", true);
|
|
@@ -784,6 +790,22 @@ server.registerTool(
|
|
|
784
790
|
},
|
|
785
791
|
async () => {
|
|
786
792
|
const summary = security.summary();
|
|
793
|
+
if (desktopTasksEnabled) {
|
|
794
|
+
const native = await desktopTasks.status();
|
|
795
|
+
return textResult([
|
|
796
|
+
`platform: ${PLATFORM_LABEL} (${process.platform}/${process.arch})`,
|
|
797
|
+
`bridge version: ${VERSION}`,
|
|
798
|
+
`node: ${process.version} at ${process.execPath}`,
|
|
799
|
+
"desktop tasks: enabled; Desktop permissions, exact saved project, immediate visibility",
|
|
800
|
+
`native relay: ${native.available ? "available; verified through Codex Desktop" : "unavailable"}`,
|
|
801
|
+
`native endpoint: ${native.socketPath}`,
|
|
802
|
+
...(native.available ? [`local projects: ${native.localProjects}`] : [`reason: ${native.reason}`]),
|
|
803
|
+
"app-server: disabled in Desktop-only mode; no external endpoint is contacted",
|
|
804
|
+
"autostart: off; external app-server fallback is disabled",
|
|
805
|
+
`security: thread policy ${security.threadPolicy}, ${summary.allowAllRoots ? "all directories" : `${summary.allowedRoots.length} allowed root(s)`}; task permissions belong to Codex Desktop`,
|
|
806
|
+
`claude desktop config: ${claudeDesktopConfigPath()}`,
|
|
807
|
+
].join("\n"), !native.available);
|
|
808
|
+
}
|
|
787
809
|
const up = await client.isServerUp();
|
|
788
810
|
let liveThreads = null;
|
|
789
811
|
if (up) {
|
|
@@ -836,4 +858,4 @@ server.registerTool(
|
|
|
836
858
|
|
|
837
859
|
const transport = new StdioServerTransport();
|
|
838
860
|
await server.connect(transport);
|
|
839
|
-
log(`ready on ${PLATFORM_LABEL} (app-server endpoint: ${client.url}, codex: ${client.codexBin})`);
|
|
861
|
+
log(desktopTasksEnabled ? `ready on ${PLATFORM_LABEL} (Codex Desktop only; external app-server disabled)` : `ready on ${PLATFORM_LABEL} (app-server endpoint: ${client.url}, codex: ${client.codexBin})`);
|
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
} from "./native-relay.mjs";
|
|
21
21
|
import { IS_WINDOWS, PLATFORM_LABEL } from "./platform.mjs";
|
|
22
22
|
|
|
23
|
-
const VERSION = "1.13.
|
|
23
|
+
const VERSION = "1.13.1";
|
|
24
24
|
const log = (msg) => process.stderr.write(`[native-relay] ${msg}\n`);
|
|
25
25
|
|
|
26
26
|
function errorResponse(code, message) {
|
package/src/native-relay.mjs
CHANGED
|
@@ -178,6 +178,9 @@ export function validateDesktopOperation(operation, args) {
|
|
|
178
178
|
case "list_projects":
|
|
179
179
|
valid = exactObject(args, []);
|
|
180
180
|
break;
|
|
181
|
+
case "list_threads":
|
|
182
|
+
valid = exactObject(args, ["limit"]) && optionalInteger(args.limit, 1, 50);
|
|
183
|
+
break;
|
|
181
184
|
case "create_thread":
|
|
182
185
|
valid = exactObject(args, ["prompt", "target", "model", "thinking", "title"]) &&
|
|
183
186
|
nonempty(args.prompt) && optionalText(args.title) && modelSettings() &&
|
package/src/thread-delivery.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { NativeDesktopRelay, desktopTaskSocketPath } from "./native-relay.mjs";
|
|
1
|
+
import { NativeDesktopRelay, desktopTaskSocketPath, desktopTasksConfigured } from "./native-relay.mjs";
|
|
2
2
|
import { runTurn } from "./turn.mjs";
|
|
3
3
|
import { realpathSync } from "node:fs";
|
|
4
4
|
import path from "node:path";
|
|
@@ -45,11 +45,59 @@ export class DesktopTaskDelivery {
|
|
|
45
45
|
this.security = security;
|
|
46
46
|
this.sleep = sleep;
|
|
47
47
|
this.now = now;
|
|
48
|
+
this.threadOperations = new Map();
|
|
48
49
|
}
|
|
49
50
|
|
|
50
51
|
async request(operation, args) {
|
|
51
|
-
|
|
52
|
-
|
|
52
|
+
try {
|
|
53
|
+
const response = await this.relay.requestDesktop(operation, args);
|
|
54
|
+
return response.result;
|
|
55
|
+
} catch (err) {
|
|
56
|
+
throw new Error(`Codex Desktop operation ${operation} failed: ${err.message}. Desktop-only mode will not start or use an external app-server. Open Codex Desktop and reconnect its native relay, then inspect the existing task before retrying a send.`, { cause: err });
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async withThread(threadId, operation) {
|
|
61
|
+
const previous = this.threadOperations.get(threadId) ?? Promise.resolve();
|
|
62
|
+
const current = previous.catch(() => {}).then(operation);
|
|
63
|
+
this.threadOperations.set(threadId, current);
|
|
64
|
+
try {
|
|
65
|
+
return await current;
|
|
66
|
+
} finally {
|
|
67
|
+
if (this.threadOperations.get(threadId) === current) this.threadOperations.delete(threadId);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async status() {
|
|
72
|
+
try {
|
|
73
|
+
const response = await this.request("list_projects", {});
|
|
74
|
+
if (!Array.isArray(response?.projects)) throw new Error("Desktop returned no project list");
|
|
75
|
+
return { available: true, socketPath: this.relay.socketPath, localProjects: response.projects.filter((project) => project.projectKind === "local" && project.hostId === "local").length };
|
|
76
|
+
} catch (err) {
|
|
77
|
+
return { available: false, socketPath: this.relay.socketPath, reason: err.message };
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async list({ limit = 15, cwd, searchTerm, loadedOnly = false } = {}) {
|
|
82
|
+
if (loadedOnly) throw new Error("Codex Desktop does not expose a loaded-only thread list. Omit loadedOnly to list its recent and pinned local tasks; no external app-server was contacted.");
|
|
83
|
+
if (cwd) this.security.assertCwd(cwd);
|
|
84
|
+
const response = await this.request("list_threads", { limit: 50 });
|
|
85
|
+
if (!Array.isArray(response?.threads) || !Array.isArray(response?.pinnedThreads)) throw new Error("Codex Desktop returned an invalid thread list");
|
|
86
|
+
if (response.unavailableHosts?.some((host) => host === "local" || host?.hostId === "local")) throw new Error("The local Codex Desktop host is unavailable; its thread list could not be confirmed");
|
|
87
|
+
const seen = new Set();
|
|
88
|
+
const rows = [...response.pinnedThreads, ...response.threads].filter((thread) => {
|
|
89
|
+
if (thread.kind !== "codex" || thread.hostId !== "local" || !thread.id || !thread.cwd || seen.has(thread.id)) return false;
|
|
90
|
+
seen.add(thread.id);
|
|
91
|
+
if (!this.security.isThreadAuthorized(thread.id, thread.cwd)) return false;
|
|
92
|
+
try {
|
|
93
|
+
this.security.assertCwd(thread.cwd);
|
|
94
|
+
if (cwd && path.relative(realpathSync.native(cwd), realpathSync.native(thread.cwd))) return false;
|
|
95
|
+
} catch {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
return !searchTerm || String(thread.title ?? "").toLowerCase().includes(searchTerm.toLowerCase());
|
|
99
|
+
}).slice(0, limit);
|
|
100
|
+
return { rows, coverage: "Codex Desktop's latest 50 non-pinned tasks and all pinned tasks; local Codex workspaces only" };
|
|
53
101
|
}
|
|
54
102
|
|
|
55
103
|
async create({ cwd, prompt, name, model, effort }) {
|
|
@@ -144,6 +192,7 @@ export function createThreadDelivery({
|
|
|
144
192
|
relay = new NativeDesktopRelay(),
|
|
145
193
|
log = () => {},
|
|
146
194
|
timeoutMs = 240000,
|
|
195
|
+
desktopOnly = desktopTasksConfigured(),
|
|
147
196
|
releaseAfterTurn =
|
|
148
197
|
process.env.CODEX_BRIDGE_RELEASE_AFTER_TURN !== undefined
|
|
149
198
|
? process.env.CODEX_BRIDGE_RELEASE_AFTER_TURN === "1"
|
|
@@ -168,6 +217,7 @@ export function createThreadDelivery({
|
|
|
168
217
|
return { backend: NATIVE_BACKEND, threadId, ack };
|
|
169
218
|
} catch (err) {
|
|
170
219
|
if (err.reachedCompanion || err.code !== "RELAY_UNREACHABLE") throw err;
|
|
220
|
+
if (desktopOnly) throw new Error(`Codex Desktop relay is unavailable: ${err.message}. Desktop-only mode will not start or use an external app-server.`);
|
|
171
221
|
log(`native relay unreachable (${err.message}); falling back to the app-server path`);
|
|
172
222
|
}
|
|
173
223
|
} else if (status.reason !== reportedUnavailable) {
|
|
@@ -175,6 +225,7 @@ export function createThreadDelivery({
|
|
|
175
225
|
log(`native relay not in use: ${status.reason}`);
|
|
176
226
|
}
|
|
177
227
|
|
|
228
|
+
if (desktopOnly) throw new Error(`Codex Desktop relay is unavailable: ${status.reason ?? "no native acknowledgement"}. Desktop-only mode will not start or use an external app-server.`);
|
|
178
229
|
if (!codex) throw new Error("No Codex app-server client is configured to deliver this message");
|
|
179
230
|
const send = async () => {
|
|
180
231
|
await codex.ensureThreadAttached(threadId);
|
|
@@ -200,7 +251,7 @@ export function createThreadDelivery({
|
|
|
200
251
|
const status = relay.status();
|
|
201
252
|
return status.enabled
|
|
202
253
|
? `${NATIVE_BACKEND} via ${status.socketPath}`
|
|
203
|
-
: `${APP_SERVER_BACKEND} (${status.reason})`;
|
|
254
|
+
: desktopOnly ? `${NATIVE_BACKEND} unavailable (${status.reason}); external app-server disabled` : `${APP_SERVER_BACKEND} (${status.reason})`;
|
|
204
255
|
}
|
|
205
256
|
|
|
206
257
|
return { deliver, describe };
|