codex-relay 1.4.1 → 1.4.3
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/README.md +28 -18
- package/dist/cli.js +60 -2
- package/dist/src.js +198 -104
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -30,14 +30,18 @@ After approval, the phone can list Codex threads, start new work, stream message
|
|
|
30
30
|
|
|
31
31
|
## Shared Terminal and Mobile Sessions
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
On macOS, Codex Relay prefers Codex's shared Unix socket so terminal and mobile clients can follow the same live sessions. If the shared app-server cannot start or initialize, the relay prints a warning and continues with a private app-server.
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
Linux, WSL, and native Windows keep using a private app-server by default. A terminal TUI that was started separately can resume the same saved thread, but it does not receive the relay process's live events.
|
|
36
|
+
|
|
37
|
+
Require the shared app-server on any platform:
|
|
36
38
|
|
|
37
39
|
```sh
|
|
38
40
|
npx codex-relay@latest --shared-app-server
|
|
39
41
|
```
|
|
40
42
|
|
|
43
|
+
This explicit mode does not fall back to a private app-server when shared startup fails.
|
|
44
|
+
|
|
41
45
|
When a shared app-server is already running, the relay attaches to it instead of starting another one. If the relay's own socket connection resets, it reconnects without deliberately stopping the shared app-server.
|
|
42
46
|
|
|
43
47
|
Then connect a new terminal TUI to the shared app-server. On macOS, Linux, or WSL:
|
|
@@ -56,7 +60,7 @@ Pass a thread ID after the remote endpoint to open a specific thread. The relay
|
|
|
56
60
|
|
|
57
61
|
Shared mode uses Codex's experimental app-server transport. A directly connected terminal TUI has its own WebSocket connection, which the relay cannot observe or reconnect. If that terminal reports a socket reset while the thread continues on mobile, reconnect it with the matching remote endpoint above and append the thread ID if needed.
|
|
58
62
|
|
|
59
|
-
Shared mode requires a recent Codex CLI with app-server and `resume --remote` support. It uses a Unix socket on macOS, Linux, and WSL, or a loopback-only WebSocket on Windows. If
|
|
63
|
+
Shared mode requires a recent Codex CLI with app-server and `resume --remote` support. It uses a Unix socket on macOS, Linux, and WSL, or a loopback-only WebSocket on Windows. If explicit shared mode is unavailable, update Codex or omit `--shared-app-server`. On macOS, set `CODEX_RELAY_APP_SERVER_MODE=stdio` to force private mode instead of using the shared-first default.
|
|
60
64
|
|
|
61
65
|
## Background Mode
|
|
62
66
|
|
|
@@ -79,10 +83,10 @@ Print the current pairing QR again:
|
|
|
79
83
|
npx codex-relay@latest qr
|
|
80
84
|
```
|
|
81
85
|
|
|
82
|
-
Stop a background server
|
|
86
|
+
Stop a background server:
|
|
83
87
|
|
|
84
88
|
```sh
|
|
85
|
-
|
|
89
|
+
npx codex-relay@latest stop
|
|
86
90
|
```
|
|
87
91
|
|
|
88
92
|
## Commands
|
|
@@ -99,11 +103,17 @@ npx codex-relay@latest --bg
|
|
|
99
103
|
|
|
100
104
|
Start the relay in the background.
|
|
101
105
|
|
|
106
|
+
```sh
|
|
107
|
+
npx codex-relay@latest stop
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Stop the background relay. Repeating this command is safe when no background server is running.
|
|
111
|
+
|
|
102
112
|
```sh
|
|
103
113
|
npx codex-relay@latest --shared-app-server
|
|
104
114
|
```
|
|
105
115
|
|
|
106
|
-
|
|
116
|
+
Require the relay to start through Codex's shared app-server socket.
|
|
107
117
|
|
|
108
118
|
```sh
|
|
109
119
|
npx codex-relay@latest qr
|
|
@@ -127,17 +137,17 @@ Start the relay and automatically approve mobile pairing requests. Use this only
|
|
|
127
137
|
|
|
128
138
|
The relay listens on `0.0.0.0:8787` by default. Configure it with environment variables:
|
|
129
139
|
|
|
130
|
-
| Variable | Purpose
|
|
131
|
-
| -------------------------------------- |
|
|
132
|
-
| `PORT` | Server port. Defaults to `8787`.
|
|
133
|
-
| `HOST` | Listen host. Defaults to `0.0.0.0`.
|
|
134
|
-
| `CODEX_RELAY_WORKSPACE_PATH` | Workspace path Codex should use. Defaults to the directory where you run `npx codex-relay@latest`.
|
|
135
|
-
| `CODEX_RELAY_AUTH_DB_PATH` | Pairing and session database path. Defaults to `.codex-relay/auth.db`.
|
|
136
|
-
| `CODEX_RELAY_APPROVAL_SECRET` | Secret used by the local approve command. Usually generated automatically.
|
|
137
|
-
| `CODEX_RELAY_DANGEROUSLY_AUTO_APPROVE` | Set to `1` to auto-approve mobile pairing requests. Prefer the CLI flag for local use.
|
|
138
|
-
| `CODEX_RELAY_APP_SERVER_MODE` | Set to `socket`
|
|
139
|
-
| `CODEX_HOME` | Codex home directory, used when reading Codex session metadata.
|
|
140
|
-
| `CODEX_BIN` | Codex CLI executable path.
|
|
140
|
+
| Variable | Purpose |
|
|
141
|
+
| -------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
142
|
+
| `PORT` | Server port. Defaults to `8787`. |
|
|
143
|
+
| `HOST` | Listen host. Defaults to `0.0.0.0`. |
|
|
144
|
+
| `CODEX_RELAY_WORKSPACE_PATH` | Workspace path Codex should use. Defaults to the directory where you run `npx codex-relay@latest`. |
|
|
145
|
+
| `CODEX_RELAY_AUTH_DB_PATH` | Pairing and session database path. Defaults to `.codex-relay/auth.db`. |
|
|
146
|
+
| `CODEX_RELAY_APPROVAL_SECRET` | Secret used by the local approve command. Usually generated automatically. |
|
|
147
|
+
| `CODEX_RELAY_DANGEROUSLY_AUTO_APPROVE` | Set to `1` to auto-approve mobile pairing requests. Prefer the CLI flag for local use. |
|
|
148
|
+
| `CODEX_RELAY_APP_SERVER_MODE` | Set to `socket` to require shared mode or `stdio` to require private mode. Unset prefers shared mode with startup fallback on macOS and private mode elsewhere. |
|
|
149
|
+
| `CODEX_HOME` | Codex home directory, used when reading Codex session metadata. |
|
|
150
|
+
| `CODEX_BIN` | Codex CLI executable path. |
|
|
141
151
|
|
|
142
152
|
Examples:
|
|
143
153
|
|
|
@@ -174,7 +184,7 @@ npx codex-relay@latest qr
|
|
|
174
184
|
Or stop the background process shown by the CLI:
|
|
175
185
|
|
|
176
186
|
```sh
|
|
177
|
-
|
|
187
|
+
npx codex-relay@latest stop
|
|
178
188
|
```
|
|
179
189
|
|
|
180
190
|
If the mobile app cannot connect, confirm that the phone can reach the printed `Mobile:` URL and that the chosen port is not blocked by a firewall.
|
package/dist/cli.js
CHANGED
|
@@ -20,6 +20,23 @@ async function readRunningRelayPid(pidPath, options = {}) {
|
|
|
20
20
|
const command = await (options.commandReader ?? readProcessCommand)(pid);
|
|
21
21
|
return command && isRelayProcessCommand(command) ? pid : void 0;
|
|
22
22
|
}
|
|
23
|
+
async function stopRunningRelay(pidPath, options = {}) {
|
|
24
|
+
const pid = await readRunningRelayPid(pidPath, options);
|
|
25
|
+
if (!pid) {
|
|
26
|
+
await rm(pidPath, { force: true });
|
|
27
|
+
return { kind: "not-running" };
|
|
28
|
+
}
|
|
29
|
+
(options.signalProcess ?? signalRelayProcess)(pid);
|
|
30
|
+
if (!await (options.waitForProcessExit ?? waitForRelayProcessExit)(pid)) return {
|
|
31
|
+
kind: "timed-out",
|
|
32
|
+
pid
|
|
33
|
+
};
|
|
34
|
+
await rm(pidPath, { force: true });
|
|
35
|
+
return {
|
|
36
|
+
kind: "stopped",
|
|
37
|
+
pid
|
|
38
|
+
};
|
|
39
|
+
}
|
|
23
40
|
function defaultIsProcessAlive(pid) {
|
|
24
41
|
try {
|
|
25
42
|
process.kill(pid, 0);
|
|
@@ -28,6 +45,21 @@ function defaultIsProcessAlive(pid) {
|
|
|
28
45
|
return false;
|
|
29
46
|
}
|
|
30
47
|
}
|
|
48
|
+
function signalRelayProcess(pid) {
|
|
49
|
+
try {
|
|
50
|
+
process.kill(pid, "SIGTERM");
|
|
51
|
+
} catch (error) {
|
|
52
|
+
if (error instanceof Error && "code" in error && error.code === "ESRCH") return;
|
|
53
|
+
throw error;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
async function waitForRelayProcessExit(pid) {
|
|
57
|
+
for (let attempt = 0; attempt < 100; attempt += 1) {
|
|
58
|
+
if (!defaultIsProcessAlive(pid)) return true;
|
|
59
|
+
await setTimeout(50);
|
|
60
|
+
}
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
31
63
|
async function readProcessCommand(pid) {
|
|
32
64
|
try {
|
|
33
65
|
const { stdout } = await execFileAsync("ps", [
|
|
@@ -48,12 +80,13 @@ function isRelayProcessCommand(command) {
|
|
|
48
80
|
//#endregion
|
|
49
81
|
//#region src/cli.ts
|
|
50
82
|
const npxCommand = "npx codex-relay@latest";
|
|
51
|
-
const program = new Command().name("codex-relay").description("Run and approve the codex-relay local CLI bridge.").option("--bg", "run the Codex Relay server in the background").option("--debug", "write verbose relay diagnostics to debug.log").option("--shared-app-server", "
|
|
83
|
+
const program = new Command().name("codex-relay").description("Run and approve the codex-relay local CLI bridge.").option("--bg", "run the Codex Relay server in the background").option("--debug", "write verbose relay diagnostics to debug.log").option("--shared-app-server", "require a shared Codex app-server so terminal and mobile can share live sessions").option("--dangerously-auto-approve", "automatically approve mobile pairing requests without a local approval command").addHelpText("after", `
|
|
52
84
|
|
|
53
85
|
Examples:
|
|
54
86
|
${npxCommand} Start the relay and print a pairing QR
|
|
55
87
|
${npxCommand} --shared-app-server Share live sessions with a connected terminal
|
|
56
88
|
${npxCommand} --bg Start the relay in the background
|
|
89
|
+
${npxCommand} stop Stop the background relay
|
|
57
90
|
${npxCommand} qr Print the current pairing QR
|
|
58
91
|
${npxCommand} clear Sign out every paired mobile app
|
|
59
92
|
${npxCommand} approve CODE Approve a pending mobile pairing request`).action(async (options) => {
|
|
@@ -66,6 +99,9 @@ Examples:
|
|
|
66
99
|
}
|
|
67
100
|
await import("./src.js").catch(handleServerStartError);
|
|
68
101
|
});
|
|
102
|
+
program.command("stop").description("Stop the background Codex Relay server.").action(async () => {
|
|
103
|
+
await stopBackgroundServer();
|
|
104
|
+
});
|
|
69
105
|
program.command("qr").description("Print the current pairing QR for an already running server.").action(async () => {
|
|
70
106
|
await printPairingQr();
|
|
71
107
|
});
|
|
@@ -124,6 +160,7 @@ async function startBackgroundServer() {
|
|
|
124
160
|
console.log(`Logs: ${logPath}`);
|
|
125
161
|
if (process.env.CODEX_RELAY_DEBUG === "1") console.log(`Debug logs: ${debugLogPath}`);
|
|
126
162
|
console.log(`Print the pairing QR later with: ${npxCommand} qr`);
|
|
163
|
+
console.log(`Stop the background relay with: ${npxCommand} stop`);
|
|
127
164
|
}
|
|
128
165
|
function backgroundArgs() {
|
|
129
166
|
return process.argv.slice(2).filter((arg) => arg !== "--bg");
|
|
@@ -140,6 +177,27 @@ async function waitForBackgroundPid(child, pidPath) {
|
|
|
140
177
|
await setTimeout(100);
|
|
141
178
|
}
|
|
142
179
|
}
|
|
180
|
+
async function stopBackgroundServer() {
|
|
181
|
+
printStopResult(await stopRunningRelay(codexRelayDataPath("server.pid")));
|
|
182
|
+
}
|
|
183
|
+
function printStopResult(result) {
|
|
184
|
+
switch (result.kind) {
|
|
185
|
+
case "not-running":
|
|
186
|
+
console.log("No background Codex Relay server is running.");
|
|
187
|
+
return;
|
|
188
|
+
case "stopped":
|
|
189
|
+
console.log(`Stopped codex-relay background server (pid ${result.pid}).`);
|
|
190
|
+
return;
|
|
191
|
+
case "timed-out":
|
|
192
|
+
console.error(`Timed out stopping codex-relay background server (pid ${result.pid}).`);
|
|
193
|
+
process.exitCode = 1;
|
|
194
|
+
return;
|
|
195
|
+
default: return assertNever(result);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
function assertNever(value) {
|
|
199
|
+
throw new TypeError(`Unexpected background stop result: ${JSON.stringify(value)}`);
|
|
200
|
+
}
|
|
143
201
|
async function approvePairing(rawCode) {
|
|
144
202
|
const approvalCode = normalizeApprovalCode(rawCode ?? "");
|
|
145
203
|
if (!approvalCode) {
|
|
@@ -277,7 +335,7 @@ async function handleServerStartError(error) {
|
|
|
277
335
|
console.error(` ${npxCommand} qr`);
|
|
278
336
|
console.error("");
|
|
279
337
|
console.error("To stop the background server:");
|
|
280
|
-
console.error(`
|
|
338
|
+
console.error(` ${npxCommand} stop`);
|
|
281
339
|
console.error("");
|
|
282
340
|
console.error(`Logs: ${logPath}`);
|
|
283
341
|
} else {
|
package/dist/src.js
CHANGED
|
@@ -36,16 +36,31 @@ const stdioAppServerArgs = [
|
|
|
36
36
|
];
|
|
37
37
|
const sharedWindowsServerUrl = "ws://127.0.0.1:8788";
|
|
38
38
|
const windowsShellExtensions = new Set([".bat", ".cmd"]);
|
|
39
|
-
function resolveCodexAppServerMode(
|
|
39
|
+
function resolveCodexAppServerMode(input = {}) {
|
|
40
|
+
const env = input.env ?? process.env;
|
|
41
|
+
const platform$2 = input.platform ?? platform();
|
|
40
42
|
const configuredMode = env.CODEX_RELAY_APP_SERVER_MODE?.trim().toLowerCase();
|
|
41
|
-
if (!configuredMode
|
|
42
|
-
|
|
43
|
+
if (!configuredMode) return platform$2 === "darwin" ? {
|
|
44
|
+
fallbackToStdio: true,
|
|
45
|
+
mode: "socket"
|
|
46
|
+
} : {
|
|
47
|
+
fallbackToStdio: false,
|
|
48
|
+
mode: "stdio"
|
|
49
|
+
};
|
|
50
|
+
if (configuredMode === "stdio") return {
|
|
51
|
+
fallbackToStdio: false,
|
|
52
|
+
mode: "stdio"
|
|
53
|
+
};
|
|
54
|
+
if (configuredMode === "socket") return {
|
|
55
|
+
fallbackToStdio: false,
|
|
56
|
+
mode: "socket"
|
|
57
|
+
};
|
|
43
58
|
throw new Error(`Unsupported CODEX_RELAY_APP_SERVER_MODE ${JSON.stringify(configuredMode)}. Expected "stdio" or "socket".`);
|
|
44
59
|
}
|
|
45
60
|
function resolveCodexAppServerSpawn(input = {}) {
|
|
46
|
-
const platform$
|
|
61
|
+
const platform$3 = input.platform ?? platform();
|
|
47
62
|
const command = resolveCodexBinary(input.env ?? process.env);
|
|
48
|
-
const isWindows = platform$
|
|
63
|
+
const isWindows = platform$3 === "win32";
|
|
49
64
|
return {
|
|
50
65
|
command,
|
|
51
66
|
args: [...stdioAppServerArgs],
|
|
@@ -54,15 +69,15 @@ function resolveCodexAppServerSpawn(input = {}) {
|
|
|
54
69
|
};
|
|
55
70
|
}
|
|
56
71
|
function resolveCodexSharedAppServerSpawn(input = {}) {
|
|
57
|
-
const platform$
|
|
72
|
+
const platform$4 = input.platform ?? platform();
|
|
58
73
|
const command = resolveCodexBinary(input.env ?? process.env);
|
|
59
|
-
const isWindows = platform$
|
|
74
|
+
const isWindows = platform$4 === "win32";
|
|
60
75
|
return {
|
|
61
76
|
command,
|
|
62
77
|
args: [
|
|
63
78
|
"app-server",
|
|
64
79
|
"--listen",
|
|
65
|
-
resolveCodexSharedAppServerRemoteAddress(platform$
|
|
80
|
+
resolveCodexSharedAppServerRemoteAddress(platform$4)
|
|
66
81
|
],
|
|
67
82
|
shell: isWindows && shouldUseWindowsShell(command),
|
|
68
83
|
windowsHide: isWindows
|
|
@@ -126,8 +141,10 @@ const sharedSocketReconnectDelaysMs = [
|
|
|
126
141
|
2e3
|
|
127
142
|
];
|
|
128
143
|
var CodexAppServerClient = class {
|
|
144
|
+
activeMode;
|
|
129
145
|
child;
|
|
130
146
|
closed = false;
|
|
147
|
+
fallbackToStdio;
|
|
131
148
|
initialized;
|
|
132
149
|
notificationHandlers = /* @__PURE__ */ new Set();
|
|
133
150
|
nextId = 1;
|
|
@@ -135,12 +152,21 @@ var CodexAppServerClient = class {
|
|
|
135
152
|
reconnecting;
|
|
136
153
|
requestHandlers = /* @__PURE__ */ new Set();
|
|
137
154
|
readline;
|
|
155
|
+
sharedReconnectEnabled = false;
|
|
138
156
|
sharedServer;
|
|
139
157
|
socket;
|
|
158
|
+
onStartupFallback;
|
|
140
159
|
startSharedServer;
|
|
141
160
|
constructor(options = {}) {
|
|
161
|
+
const mode = options.mode ?? resolveCodexAppServerMode();
|
|
162
|
+
this.activeMode = mode.mode;
|
|
163
|
+
this.fallbackToStdio = mode.fallbackToStdio;
|
|
164
|
+
this.onStartupFallback = options.onStartupFallback;
|
|
142
165
|
this.startSharedServer = options.startSharedServer ?? startSharedCodexAppServer;
|
|
143
166
|
}
|
|
167
|
+
get appServerMode() {
|
|
168
|
+
return this.activeMode;
|
|
169
|
+
}
|
|
144
170
|
initialize() {
|
|
145
171
|
return this.ensureInitialized();
|
|
146
172
|
}
|
|
@@ -220,15 +246,8 @@ var CodexAppServerClient = class {
|
|
|
220
246
|
this.closed = true;
|
|
221
247
|
for (const pending of this.pending.values()) pending.reject(/* @__PURE__ */ new Error("Codex app-server was closed."));
|
|
222
248
|
this.pending.clear();
|
|
223
|
-
this.
|
|
224
|
-
this.
|
|
225
|
-
const socket = this.socket;
|
|
226
|
-
this.socket = void 0;
|
|
227
|
-
socket?.close();
|
|
228
|
-
this.sharedServer?.kill();
|
|
229
|
-
this.readline = void 0;
|
|
230
|
-
this.child = void 0;
|
|
231
|
-
this.sharedServer = void 0;
|
|
249
|
+
this.stopStdioCodexAppServer();
|
|
250
|
+
this.stopSharedCodexAppServer();
|
|
232
251
|
this.initialized = void 0;
|
|
233
252
|
}
|
|
234
253
|
async request(method, params) {
|
|
@@ -262,13 +281,31 @@ var CodexAppServerClient = class {
|
|
|
262
281
|
return this.initialized;
|
|
263
282
|
}
|
|
264
283
|
async start() {
|
|
265
|
-
|
|
284
|
+
if (this.activeMode === "stdio") {
|
|
285
|
+
await this.startAndInitializeStdioCodexAppServer();
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
try {
|
|
289
|
+
await this.startOrAttachSharedCodexAppServer();
|
|
290
|
+
await this.initializeAppServer();
|
|
291
|
+
this.fallbackToStdio = false;
|
|
292
|
+
this.sharedReconnectEnabled = true;
|
|
293
|
+
} catch (error) {
|
|
294
|
+
const sharedError = error instanceof Error ? error : new Error(String(error));
|
|
295
|
+
this.stopSharedCodexAppServer();
|
|
296
|
+
if (!this.fallbackToStdio) throw sharedError;
|
|
297
|
+
this.activeMode = "stdio";
|
|
298
|
+
relayDebugLog("app_server.shared_socket.startup_fallback", { message: sharedError.message });
|
|
299
|
+
this.onStartupFallback?.(sharedError);
|
|
300
|
+
await this.startAndInitializeStdioCodexAppServer();
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
async startAndInitializeStdioCodexAppServer() {
|
|
266
304
|
try {
|
|
267
|
-
|
|
268
|
-
else this.startStdioCodexAppServer();
|
|
305
|
+
this.startStdioCodexAppServer();
|
|
269
306
|
await this.initializeAppServer();
|
|
270
307
|
} catch (error) {
|
|
271
|
-
|
|
308
|
+
this.stopStdioCodexAppServer();
|
|
272
309
|
throw error;
|
|
273
310
|
}
|
|
274
311
|
}
|
|
@@ -310,6 +347,14 @@ var CodexAppServerClient = class {
|
|
|
310
347
|
this.child?.kill();
|
|
311
348
|
this.child = void 0;
|
|
312
349
|
}
|
|
350
|
+
stopSharedCodexAppServer() {
|
|
351
|
+
this.sharedReconnectEnabled = false;
|
|
352
|
+
const socket = this.socket;
|
|
353
|
+
this.socket = void 0;
|
|
354
|
+
socket?.close();
|
|
355
|
+
this.sharedServer?.kill();
|
|
356
|
+
this.sharedServer = void 0;
|
|
357
|
+
}
|
|
313
358
|
async startOrAttachSharedCodexAppServer() {
|
|
314
359
|
try {
|
|
315
360
|
await this.connectSharedCodexAppServer();
|
|
@@ -319,7 +364,7 @@ var CodexAppServerClient = class {
|
|
|
319
364
|
});
|
|
320
365
|
return;
|
|
321
366
|
} catch (error) {
|
|
322
|
-
const attachError =
|
|
367
|
+
const attachError = error instanceof Error ? error : new Error(String(error));
|
|
323
368
|
relayDebugLog("app_server.shared_socket.attach_failed", {
|
|
324
369
|
message: attachError.message,
|
|
325
370
|
socketPath: sharedCodexAppServerSocketPath()
|
|
@@ -409,7 +454,7 @@ var CodexAppServerClient = class {
|
|
|
409
454
|
return this.sharedServer ? "relay-owned" : "attached";
|
|
410
455
|
}
|
|
411
456
|
scheduleSharedSocketReconnect() {
|
|
412
|
-
if (this.closed || this.reconnecting) return;
|
|
457
|
+
if (this.closed || !this.sharedReconnectEnabled || this.reconnecting) return;
|
|
413
458
|
const reconnecting = this.reconnectSharedCodexAppServer();
|
|
414
459
|
this.reconnecting = reconnecting;
|
|
415
460
|
this.initialized = reconnecting;
|
|
@@ -428,7 +473,7 @@ var CodexAppServerClient = class {
|
|
|
428
473
|
let lastError;
|
|
429
474
|
for (const delayMs of sharedSocketReconnectDelaysMs) {
|
|
430
475
|
await setTimeout$1(delayMs);
|
|
431
|
-
if (this.closed) return;
|
|
476
|
+
if (this.closed || !this.sharedReconnectEnabled) return;
|
|
432
477
|
try {
|
|
433
478
|
await this.connectSharedCodexAppServer();
|
|
434
479
|
await this.initializeAppServer();
|
|
@@ -438,7 +483,7 @@ var CodexAppServerClient = class {
|
|
|
438
483
|
});
|
|
439
484
|
return;
|
|
440
485
|
} catch (error) {
|
|
441
|
-
lastError =
|
|
486
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
442
487
|
const socket = this.socket;
|
|
443
488
|
this.socket = void 0;
|
|
444
489
|
socket?.close();
|
|
@@ -555,7 +600,7 @@ async function startSharedCodexAppServer() {
|
|
|
555
600
|
exitReason = signal ?? code ?? 1;
|
|
556
601
|
});
|
|
557
602
|
const remoteAddress = resolveCodexSharedAppServerRemoteAddress();
|
|
558
|
-
for (let attempt = 0; attempt <
|
|
603
|
+
for (let attempt = 0; attempt < 600; attempt += 1) {
|
|
559
604
|
if (spawnError) throw new Error(`Failed to start the shared Codex app-server: ${spawnError.message}`);
|
|
560
605
|
try {
|
|
561
606
|
await waitForSharedCodexAppServer(remoteAddress);
|
|
@@ -572,16 +617,8 @@ async function startSharedCodexAppServer() {
|
|
|
572
617
|
throw new Error(`Timed out waiting for the shared Codex app-server at ${remoteAddress}.`);
|
|
573
618
|
}
|
|
574
619
|
async function waitForSharedCodexAppServer(remoteAddress) {
|
|
575
|
-
if (remoteAddress === "unix://") {
|
|
576
|
-
await access(sharedCodexAppServerSocketPath());
|
|
577
|
-
return;
|
|
578
|
-
}
|
|
579
|
-
const url = new URL(remoteAddress);
|
|
580
620
|
await new Promise((resolve, reject) => {
|
|
581
|
-
const socket = connect({
|
|
582
|
-
host: url.hostname,
|
|
583
|
-
port: Number(url.port)
|
|
584
|
-
});
|
|
621
|
+
const socket = remoteAddress === "unix://" ? connect({ path: sharedCodexAppServerSocketPath() }) : connectRemoteAddress(remoteAddress);
|
|
585
622
|
const onError = (error) => reject(error);
|
|
586
623
|
socket.once("error", onError);
|
|
587
624
|
socket.once("connect", () => {
|
|
@@ -591,6 +628,13 @@ async function waitForSharedCodexAppServer(remoteAddress) {
|
|
|
591
628
|
});
|
|
592
629
|
});
|
|
593
630
|
}
|
|
631
|
+
function connectRemoteAddress(remoteAddress) {
|
|
632
|
+
const url = new URL(remoteAddress);
|
|
633
|
+
return connect({
|
|
634
|
+
host: url.hostname,
|
|
635
|
+
port: Number(url.port)
|
|
636
|
+
});
|
|
637
|
+
}
|
|
594
638
|
function sharedCodexAppServerSocketPath() {
|
|
595
639
|
return join(process.env.CODEX_HOME?.trim() || join(homedir(), ".codex"), "app-server-control", "app-server-control.sock");
|
|
596
640
|
}
|
|
@@ -1424,7 +1468,7 @@ function createApp(options = {}) {
|
|
|
1424
1468
|
const appServerHistoryLoadsByThreadId = /* @__PURE__ */ new Map();
|
|
1425
1469
|
const steeringThreads = /* @__PURE__ */ new Set();
|
|
1426
1470
|
const secureSessionsByTokenHash = /* @__PURE__ */ new Map();
|
|
1427
|
-
const activeStreamControllers = /* @__PURE__ */ new
|
|
1471
|
+
const activeStreamControllers = /* @__PURE__ */ new Map();
|
|
1428
1472
|
const threadOptions = { workingDirectory: workspacePath };
|
|
1429
1473
|
function runThreadOperation(threadId, operation) {
|
|
1430
1474
|
const result = (threadOperationTails.get(threadId) ?? Promise.resolve()).then(operation, operation);
|
|
@@ -1871,7 +1915,7 @@ function createApp(options = {}) {
|
|
|
1871
1915
|
const stream = new ReadableStream({
|
|
1872
1916
|
start(controller) {
|
|
1873
1917
|
streamController = controller;
|
|
1874
|
-
activeStreamControllers.
|
|
1918
|
+
activeStreamControllers.set(controller, closeStream);
|
|
1875
1919
|
send(workspaceTerminalOutputResponse(session, since));
|
|
1876
1920
|
if (session.exitedAt) return;
|
|
1877
1921
|
unsubscribe = subscribeWorkspaceTerminalOutput(session, send);
|
|
@@ -2593,20 +2637,30 @@ function createApp(options = {}) {
|
|
|
2593
2637
|
});
|
|
2594
2638
|
const encoder = new TextEncoder();
|
|
2595
2639
|
const secureSession = getSecureSessionForRequest(c, options.pairing, secureSessionsByTokenHash);
|
|
2596
|
-
let streamController;
|
|
2597
2640
|
let streamSettled = false;
|
|
2641
|
+
let closeStream = () => {};
|
|
2598
2642
|
const stream = new ReadableStream({
|
|
2599
2643
|
start(controller) {
|
|
2600
|
-
|
|
2601
|
-
|
|
2644
|
+
const attachmentAbortController = new AbortController();
|
|
2645
|
+
let closed = false;
|
|
2646
|
+
let stopPreviewMonitor = () => {};
|
|
2647
|
+
closeStream = () => {
|
|
2648
|
+
if (closed) return;
|
|
2649
|
+
closed = true;
|
|
2650
|
+
stopPreviewMonitor();
|
|
2651
|
+
attachmentAbortController.abort();
|
|
2652
|
+
activeStreamControllers.delete(controller);
|
|
2653
|
+
closeSseController(controller);
|
|
2654
|
+
};
|
|
2655
|
+
activeStreamControllers.set(controller, closeStream);
|
|
2602
2656
|
relayDebugLog("thread.stream.started", {
|
|
2603
2657
|
mode: !runOptions.prompt && appServer ? "attach" : "run",
|
|
2604
2658
|
threadId
|
|
2605
2659
|
});
|
|
2606
|
-
|
|
2660
|
+
stopPreviewMonitor = startWebPreviewTargetMonitor({
|
|
2607
2661
|
bridgeUrl: c.req.url,
|
|
2608
2662
|
send(target) {
|
|
2609
|
-
sendSse(controller, encoder, secureSession, {
|
|
2663
|
+
return sendSse(controller, encoder, secureSession, {
|
|
2610
2664
|
type: "thread.preview_target.detected",
|
|
2611
2665
|
threadId,
|
|
2612
2666
|
target
|
|
@@ -2621,6 +2675,7 @@ function createApp(options = {}) {
|
|
|
2621
2675
|
messagesByThreadId,
|
|
2622
2676
|
pendingApprovals,
|
|
2623
2677
|
secureSession,
|
|
2678
|
+
signal: attachmentAbortController.signal,
|
|
2624
2679
|
threadId,
|
|
2625
2680
|
threads
|
|
2626
2681
|
}).finally(() => {
|
|
@@ -2629,8 +2684,7 @@ function createApp(options = {}) {
|
|
|
2629
2684
|
mode: "attach",
|
|
2630
2685
|
threadId
|
|
2631
2686
|
});
|
|
2632
|
-
|
|
2633
|
-
stopPreviewMonitor();
|
|
2687
|
+
closeStream();
|
|
2634
2688
|
});
|
|
2635
2689
|
return;
|
|
2636
2690
|
}
|
|
@@ -2641,14 +2695,12 @@ function createApp(options = {}) {
|
|
|
2641
2695
|
thread: knownThread,
|
|
2642
2696
|
error: apiError("unsupported", "Running-thread stream attachment requires the Codex app-server.").error
|
|
2643
2697
|
});
|
|
2644
|
-
closeSseController(controller);
|
|
2645
2698
|
streamSettled = true;
|
|
2646
2699
|
relayDebugLog("thread.stream.finished", {
|
|
2647
2700
|
mode: "unsupported",
|
|
2648
2701
|
threadId
|
|
2649
2702
|
});
|
|
2650
|
-
|
|
2651
|
-
stopPreviewMonitor();
|
|
2703
|
+
closeStream();
|
|
2652
2704
|
return;
|
|
2653
2705
|
}
|
|
2654
2706
|
const skills = runOptions.skills ?? [];
|
|
@@ -2681,12 +2733,11 @@ function createApp(options = {}) {
|
|
|
2681
2733
|
mode: "run",
|
|
2682
2734
|
threadId
|
|
2683
2735
|
});
|
|
2684
|
-
|
|
2685
|
-
stopPreviewMonitor();
|
|
2736
|
+
closeStream();
|
|
2686
2737
|
});
|
|
2687
2738
|
},
|
|
2688
2739
|
cancel(reason) {
|
|
2689
|
-
|
|
2740
|
+
closeStream();
|
|
2690
2741
|
relayDebugLog("thread.stream.cancelled_by_client", {
|
|
2691
2742
|
reason: debugReason(reason),
|
|
2692
2743
|
settled: streamSettled,
|
|
@@ -3030,8 +3081,6 @@ async function runPromptStreamed(input) {
|
|
|
3030
3081
|
thread: threadSummary,
|
|
3031
3082
|
error: errorBody.error
|
|
3032
3083
|
});
|
|
3033
|
-
} finally {
|
|
3034
|
-
closeSseController(input.controller);
|
|
3035
3084
|
}
|
|
3036
3085
|
}
|
|
3037
3086
|
async function startAppServerTurn(appServer, threadId, input) {
|
|
@@ -3122,7 +3171,16 @@ async function streamRunningAppServerThread(input) {
|
|
|
3122
3171
|
let observedTurnActivity = false;
|
|
3123
3172
|
let producedTurnOutput = false;
|
|
3124
3173
|
let threadSummary = input.threads.get(input.threadId);
|
|
3125
|
-
|
|
3174
|
+
let cleanupNotificationHandler = () => void 0;
|
|
3175
|
+
let cleanupRequestHandler = () => void 0;
|
|
3176
|
+
let handlersCleaned = false;
|
|
3177
|
+
const cleanupHandlers = () => {
|
|
3178
|
+
if (handlersCleaned) return;
|
|
3179
|
+
handlersCleaned = true;
|
|
3180
|
+
cleanupRequestHandler();
|
|
3181
|
+
cleanupNotificationHandler();
|
|
3182
|
+
};
|
|
3183
|
+
cleanupRequestHandler = input.appServer.onRequest((request) => {
|
|
3126
3184
|
if (!isApprovalServerRequest(request.method)) {
|
|
3127
3185
|
input.appServer.rejectRequest(request.id, -32601, `${request.method} is not supported by Codex Relay mobile yet.`);
|
|
3128
3186
|
return;
|
|
@@ -3175,8 +3233,15 @@ async function streamRunningAppServerThread(input) {
|
|
|
3175
3233
|
message
|
|
3176
3234
|
});
|
|
3177
3235
|
});
|
|
3178
|
-
let cleanupNotificationHandler = () => void 0;
|
|
3179
3236
|
const completed = new Promise((resolve, reject) => {
|
|
3237
|
+
let settled = false;
|
|
3238
|
+
const finish = (error) => {
|
|
3239
|
+
if (settled) return;
|
|
3240
|
+
settled = true;
|
|
3241
|
+
cleanupHandlers();
|
|
3242
|
+
if (error === void 0) resolve();
|
|
3243
|
+
else reject(error);
|
|
3244
|
+
};
|
|
3180
3245
|
cleanupNotificationHandler = input.appServer.onNotification((notification) => {
|
|
3181
3246
|
const params = recordParams(notification);
|
|
3182
3247
|
const notificationThreadId = firstString(params, ["threadId"]);
|
|
@@ -3191,10 +3256,7 @@ async function streamRunningAppServerThread(input) {
|
|
|
3191
3256
|
type: "thread.state.changed",
|
|
3192
3257
|
thread: threadSummary
|
|
3193
3258
|
});
|
|
3194
|
-
if (state !== "running")
|
|
3195
|
-
cleanupNotificationHandler();
|
|
3196
|
-
resolve();
|
|
3197
|
-
}
|
|
3259
|
+
if (state !== "running") finish();
|
|
3198
3260
|
return;
|
|
3199
3261
|
}
|
|
3200
3262
|
case "thread/goal/updated":
|
|
@@ -3303,8 +3365,7 @@ async function streamRunningAppServerThread(input) {
|
|
|
3303
3365
|
threadId: input.threadId,
|
|
3304
3366
|
threads: input.threads
|
|
3305
3367
|
});
|
|
3306
|
-
|
|
3307
|
-
resolve();
|
|
3368
|
+
finish();
|
|
3308
3369
|
return;
|
|
3309
3370
|
}
|
|
3310
3371
|
if (assistantMessageId) {
|
|
@@ -3342,27 +3403,40 @@ async function streamRunningAppServerThread(input) {
|
|
|
3342
3403
|
error: errorBody.error
|
|
3343
3404
|
});
|
|
3344
3405
|
}
|
|
3345
|
-
|
|
3346
|
-
resolve();
|
|
3406
|
+
finish();
|
|
3347
3407
|
return;
|
|
3348
3408
|
}
|
|
3349
3409
|
}
|
|
3350
3410
|
} catch (error) {
|
|
3351
|
-
|
|
3352
|
-
reject(error);
|
|
3411
|
+
finish(error);
|
|
3353
3412
|
}
|
|
3354
3413
|
});
|
|
3355
3414
|
});
|
|
3415
|
+
let removeAbortListener = () => {};
|
|
3416
|
+
const aborted = new Promise((resolve) => {
|
|
3417
|
+
const onAbort = () => {
|
|
3418
|
+
cleanupHandlers();
|
|
3419
|
+
resolve();
|
|
3420
|
+
};
|
|
3421
|
+
if (input.signal.aborted) {
|
|
3422
|
+
onAbort();
|
|
3423
|
+
return;
|
|
3424
|
+
}
|
|
3425
|
+
input.signal.addEventListener("abort", onAbort, { once: true });
|
|
3426
|
+
removeAbortListener = () => input.signal.removeEventListener("abort", onAbort);
|
|
3427
|
+
});
|
|
3356
3428
|
try {
|
|
3357
|
-
const appServerThread = await input.appServer.readThread(input.threadId, { includeTurns: false });
|
|
3429
|
+
const appServerThread = await Promise.race([input.appServer.readThread(input.threadId, { includeTurns: false }), aborted.then(() => void 0)]);
|
|
3430
|
+
if (!appServerThread || input.signal.aborted) return;
|
|
3358
3431
|
threadSummary = rememberAppServerThread(input.threads, appServerThread);
|
|
3359
3432
|
sendSse(input.controller, input.encoder, input.secureSession, {
|
|
3360
3433
|
type: "thread.state.changed",
|
|
3361
3434
|
thread: threadSummary
|
|
3362
3435
|
});
|
|
3363
3436
|
if (threadSummary.state !== "running") return;
|
|
3364
|
-
await completed;
|
|
3437
|
+
await Promise.race([completed, aborted]);
|
|
3365
3438
|
} catch (error) {
|
|
3439
|
+
if (input.signal.aborted) return;
|
|
3366
3440
|
threadSummary = updateThread(input.threads, input.messagesByThreadId, input.threadId, {
|
|
3367
3441
|
state: "failed",
|
|
3368
3442
|
lastError: errorMessage(error)
|
|
@@ -3373,9 +3447,8 @@ async function streamRunningAppServerThread(input) {
|
|
|
3373
3447
|
error: apiError("codex_run_failed", threadSummary.lastError ?? "Codex run failed.").error
|
|
3374
3448
|
});
|
|
3375
3449
|
} finally {
|
|
3376
|
-
|
|
3377
|
-
|
|
3378
|
-
closeSseController(input.controller);
|
|
3450
|
+
removeAbortListener();
|
|
3451
|
+
cleanupHandlers();
|
|
3379
3452
|
}
|
|
3380
3453
|
}
|
|
3381
3454
|
async function runAppServerPromptStreamed(input) {
|
|
@@ -3843,7 +3916,6 @@ async function runAppServerPromptStreamed(input) {
|
|
|
3843
3916
|
input.activeAppServerTurnIdsByThreadId.delete(activeThreadId);
|
|
3844
3917
|
input.steeringThreads.delete(activeThreadId);
|
|
3845
3918
|
}
|
|
3846
|
-
closeSseController(input.controller);
|
|
3847
3919
|
}
|
|
3848
3920
|
}
|
|
3849
3921
|
function sendEmptyAppServerTurnError(input) {
|
|
@@ -4464,13 +4536,17 @@ function sendSse(controller, encoder, secureSession, event) {
|
|
|
4464
4536
|
stage: "event",
|
|
4465
4537
|
threadId
|
|
4466
4538
|
});
|
|
4467
|
-
return;
|
|
4539
|
+
return false;
|
|
4468
4540
|
}
|
|
4469
|
-
if (!enqueueSseChunk(controller, encoder.encode(`data: ${JSON.stringify(data)}\n\n`)))
|
|
4470
|
-
|
|
4471
|
-
|
|
4472
|
-
|
|
4473
|
-
|
|
4541
|
+
if (!enqueueSseChunk(controller, encoder.encode(`data: ${JSON.stringify(data)}\n\n`))) {
|
|
4542
|
+
relayDebugLog("thread.stream.sse.enqueue_failed", {
|
|
4543
|
+
eventType: parsed.type,
|
|
4544
|
+
stage: "data",
|
|
4545
|
+
threadId
|
|
4546
|
+
});
|
|
4547
|
+
return false;
|
|
4548
|
+
}
|
|
4549
|
+
return true;
|
|
4474
4550
|
}
|
|
4475
4551
|
function sendTerminalOutputSse(controller, encoder, secureSession, response) {
|
|
4476
4552
|
const parsed = WorkspaceTerminalOutputResponseSchema.parse(response);
|
|
@@ -4502,7 +4578,7 @@ function closeSseController(controller) {
|
|
|
4502
4578
|
}
|
|
4503
4579
|
}
|
|
4504
4580
|
function closeActiveStreamControllers(controllers) {
|
|
4505
|
-
for (const
|
|
4581
|
+
for (const closeStream of controllers.values()) closeStream();
|
|
4506
4582
|
controllers.clear();
|
|
4507
4583
|
}
|
|
4508
4584
|
function isClosedStreamControllerError(error) {
|
|
@@ -4510,28 +4586,36 @@ function isClosedStreamControllerError(error) {
|
|
|
4510
4586
|
}
|
|
4511
4587
|
function startWebPreviewTargetMonitor({ bridgeUrl, send }) {
|
|
4512
4588
|
const urls = webPreviewCandidateUrls(bridgeUrl);
|
|
4513
|
-
const
|
|
4589
|
+
const abortController = new AbortController();
|
|
4514
4590
|
let stopped = false;
|
|
4591
|
+
let nextScan;
|
|
4592
|
+
const stop = () => {
|
|
4593
|
+
if (stopped) return;
|
|
4594
|
+
stopped = true;
|
|
4595
|
+
abortController.abort();
|
|
4596
|
+
if (nextScan) {
|
|
4597
|
+
clearTimeout(nextScan);
|
|
4598
|
+
nextScan = void 0;
|
|
4599
|
+
}
|
|
4600
|
+
};
|
|
4515
4601
|
async function scan() {
|
|
4516
4602
|
if (stopped) return;
|
|
4517
|
-
const
|
|
4518
|
-
|
|
4519
|
-
|
|
4520
|
-
|
|
4603
|
+
const target = await detectWebPreviewTarget(urls, abortController.signal);
|
|
4604
|
+
if (stopped) return;
|
|
4605
|
+
if (target) {
|
|
4606
|
+
stop();
|
|
4521
4607
|
try {
|
|
4522
4608
|
send(target);
|
|
4523
|
-
} catch {
|
|
4524
|
-
|
|
4525
|
-
return;
|
|
4526
|
-
}
|
|
4609
|
+
} catch {}
|
|
4610
|
+
return;
|
|
4527
4611
|
}
|
|
4612
|
+
nextScan = setTimeout(() => {
|
|
4613
|
+
nextScan = void 0;
|
|
4614
|
+
scan();
|
|
4615
|
+
}, 1500);
|
|
4528
4616
|
}
|
|
4529
4617
|
scan();
|
|
4530
|
-
|
|
4531
|
-
return () => {
|
|
4532
|
-
stopped = true;
|
|
4533
|
-
clearInterval(interval);
|
|
4534
|
-
};
|
|
4618
|
+
return stop;
|
|
4535
4619
|
}
|
|
4536
4620
|
function webPreviewCandidateUrls(bridgeUrl) {
|
|
4537
4621
|
const bridge = new URL(bridgeUrl);
|
|
@@ -4554,11 +4638,14 @@ function webPreviewCandidatePorts() {
|
|
|
4554
4638
|
function readCollaborationModeTemplate(name) {
|
|
4555
4639
|
return readFileSync(new URL(`./collaboration-mode-templates/${name}.md`, import.meta.url), "utf8").replaceAll("{{KNOWN_MODE_NAMES}}", knownCollaborationModeNames).trim();
|
|
4556
4640
|
}
|
|
4557
|
-
async function
|
|
4558
|
-
return (await Promise.all(urls.map((url) => probeWebPreviewTarget(url)))).
|
|
4641
|
+
async function detectWebPreviewTarget(urls, signal) {
|
|
4642
|
+
return (await Promise.all(urls.map((url) => probeWebPreviewTarget(url, signal)))).find((target) => Boolean(target));
|
|
4559
4643
|
}
|
|
4560
|
-
async function probeWebPreviewTarget(url) {
|
|
4644
|
+
async function probeWebPreviewTarget(url, signal) {
|
|
4561
4645
|
const controller = new AbortController();
|
|
4646
|
+
const abort = () => controller.abort();
|
|
4647
|
+
if (signal.aborted) abort();
|
|
4648
|
+
else signal.addEventListener("abort", abort, { once: true });
|
|
4562
4649
|
const timeout = setTimeout(() => controller.abort(), 700);
|
|
4563
4650
|
try {
|
|
4564
4651
|
const response = await fetch(url, { signal: controller.signal });
|
|
@@ -4579,6 +4666,7 @@ async function probeWebPreviewTarget(url) {
|
|
|
4579
4666
|
return;
|
|
4580
4667
|
} finally {
|
|
4581
4668
|
clearTimeout(timeout);
|
|
4669
|
+
signal.removeEventListener("abort", abort);
|
|
4582
4670
|
}
|
|
4583
4671
|
}
|
|
4584
4672
|
function looksLikeHtml(value) {
|
|
@@ -6784,16 +6872,21 @@ const npxCommand = "npx codex-relay@latest";
|
|
|
6784
6872
|
const sessionStore = await createTursoPairingSessionStore(process.env.CODEX_RELAY_AUTH_DB_PATH ?? await prepareCodexRelayDataPath("auth.db", ["auth.db-shm", "auth.db-wal"]));
|
|
6785
6873
|
const preferencesStore = createFileRuntimePreferencesStore(process.env.CODEX_RELAY_PREFERENCES_PATH ?? await prepareCodexRelayDataPath("preferences.json"));
|
|
6786
6874
|
const appServerMode = resolveCodexAppServerMode();
|
|
6787
|
-
const
|
|
6788
|
-
|
|
6789
|
-
|
|
6790
|
-
|
|
6791
|
-
|
|
6792
|
-
|
|
6875
|
+
const relayAppServer = appServerMode.mode === "socket" ? new CodexAppServerClient({
|
|
6876
|
+
mode: appServerMode,
|
|
6877
|
+
onStartupFallback: (error) => {
|
|
6878
|
+
logRuntimeEvent("Fallback", `Shared app-server unavailable; continuing with a private app-server (${error.message}).`);
|
|
6879
|
+
}
|
|
6880
|
+
}) : void 0;
|
|
6881
|
+
if (relayAppServer) {
|
|
6882
|
+
await relayAppServer.initialize();
|
|
6883
|
+
process.once("SIGINT", () => stopRelayAppServer(130));
|
|
6884
|
+
process.once("SIGTERM", () => stopRelayAppServer(143));
|
|
6885
|
+
process.once("exit", () => relayAppServer.close());
|
|
6793
6886
|
}
|
|
6794
6887
|
serve({
|
|
6795
6888
|
fetch: createApp({
|
|
6796
|
-
appServer:
|
|
6889
|
+
appServer: relayAppServer,
|
|
6797
6890
|
pairing: {
|
|
6798
6891
|
approvalSecret,
|
|
6799
6892
|
dangerouslyAutoApprove,
|
|
@@ -6865,11 +6958,11 @@ serve({
|
|
|
6865
6958
|
listenUrl,
|
|
6866
6959
|
pairingPayload,
|
|
6867
6960
|
port: info.port,
|
|
6868
|
-
sharedAppServerRemoteAddress: appServerMode === "socket" ? resolveCodexSharedAppServerRemoteAddress() : void 0
|
|
6961
|
+
sharedAppServerRemoteAddress: relayAppServer?.appServerMode === "socket" ? resolveCodexSharedAppServerRemoteAddress() : void 0
|
|
6869
6962
|
}));
|
|
6870
6963
|
});
|
|
6871
|
-
function
|
|
6872
|
-
|
|
6964
|
+
function stopRelayAppServer(exitCode) {
|
|
6965
|
+
relayAppServer?.close();
|
|
6873
6966
|
process.exit(exitCode);
|
|
6874
6967
|
}
|
|
6875
6968
|
function formatStartupInstructions(details) {
|
|
@@ -6893,6 +6986,7 @@ function formatStartupInstructions(details) {
|
|
|
6893
6986
|
`${color.prompt("›")} Commands`,
|
|
6894
6987
|
` ${color.command(npxCommand)} Start and print a pairing QR`,
|
|
6895
6988
|
` ${color.command(`${npxCommand} --bg`)} Start in the background`,
|
|
6989
|
+
` ${color.command(`${npxCommand} stop`)} Stop the background relay`,
|
|
6896
6990
|
` ${color.command(`${npxCommand} qr`)} Print this QR again`,
|
|
6897
6991
|
` ${color.command(`${npxCommand} approve <code>`)} Approve a device`,
|
|
6898
6992
|
"",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codex-relay",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.3",
|
|
4
4
|
"description": "Local Codex Relay CLI bridge for the Codex Relay mobile app.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"@noble/ciphers": "2.2.0",
|
|
42
42
|
"@noble/curves": "2.2.0",
|
|
43
43
|
"@noble/hashes": "2.2.0",
|
|
44
|
-
"@openai/codex-sdk": "0.
|
|
44
|
+
"@openai/codex-sdk": "0.145.0",
|
|
45
45
|
"base64-js": "1.5.1",
|
|
46
46
|
"commander": "^14.0.3",
|
|
47
47
|
"es-git": "^0.6.0",
|