clawdex-mobile 5.0.8 → 5.1.2
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 +19 -0
- package/docs/setup-and-operations.md +44 -5
- package/docs/troubleshooting.md +12 -0
- package/package.json +1 -1
- package/scripts/bridge-self-update.js +246 -65
- package/scripts/start-bridge-secure.js +3 -0
- package/services/rust-bridge/Cargo.lock +55 -3
- package/services/rust-bridge/Cargo.toml +3 -2
- package/services/rust-bridge/src/main.rs +8127 -3889
- package/services/rust-bridge/src/services/git.rs +211 -5
- package/services/rust-bridge/src/services/update.rs +130 -16
- package/vendor/bridge-binaries/darwin-arm64/codex-rust-bridge +0 -0
- package/vendor/bridge-binaries/darwin-x64/codex-rust-bridge +0 -0
- package/vendor/bridge-binaries/linux-arm64/codex-rust-bridge +0 -0
- package/vendor/bridge-binaries/linux-armv7l/codex-rust-bridge +0 -0
- package/vendor/bridge-binaries/linux-x64/codex-rust-bridge +0 -0
- package/vendor/bridge-binaries/win32-x64/codex-rust-bridge.exe +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,25 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project are documented in this file.
|
|
4
4
|
|
|
5
|
+
## 5.1.2 - 2026-04-07
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- Local preview browser workflow with desktop and overview shells for mobile web inspection.
|
|
9
|
+
- App-wide font preference support in the mobile client.
|
|
10
|
+
|
|
11
|
+
### Improved
|
|
12
|
+
- Chat transcript tool-call UX now supports grouped tool-call inspection, per-call output expansion, and file-change labels that include changed filenames.
|
|
13
|
+
- Drawer, sheet, and chat header interactions feel smoother and more consistent across open/close, reconnect, and navigation flows.
|
|
14
|
+
- Composer and transcript responsiveness were tightened with lower rerender churn, more stable activity indicators, and cleaner compaction presentation.
|
|
15
|
+
- Browser preview controls, address handling, and preview session stability were refined for everyday use.
|
|
16
|
+
- Manual npm release runs can now build every packaged bridge target without publishing to npm.
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
- Bridge restart cleanup and maintenance behavior are more reliable during repeated local development cycles.
|
|
20
|
+
- Queued thread messages now use guaranteed-unique bridge queue item IDs during blocked-turn queuing.
|
|
21
|
+
- Bridge self-update now reports shutdown failures cleanly while still preserving status updates and `.env.secure` backup cleanup.
|
|
22
|
+
- Browser preview and mobile UI regressions caught during review and CI were resolved before release.
|
|
23
|
+
|
|
5
24
|
## 1.1.0 - 2026-02-23
|
|
6
25
|
|
|
7
26
|
### Added
|
|
@@ -80,16 +80,20 @@ When both CLIs are selected, the bridge starts both backends and merges chat lis
|
|
|
80
80
|
|
|
81
81
|
Open the installed mobile app on your phone, then scan the bridge QR. If needed, enter the bridge URL manually (for example `http://100.x.y.z:8787` or `http://192.168.x.y:8787`). The chosen bridge URL is stored on-device and can be changed later in Settings.
|
|
82
82
|
|
|
83
|
-
### In-app Bridge
|
|
83
|
+
### In-app Bridge Maintenance
|
|
84
84
|
|
|
85
|
-
For
|
|
85
|
+
For secure-launcher installs, the mobile Settings screen can trigger bridge maintenance safely.
|
|
86
86
|
|
|
87
87
|
- Open `Settings > Bridge Maintenance`
|
|
88
|
-
- Tap `
|
|
89
|
-
- The app will disconnect briefly while
|
|
88
|
+
- Tap `Restart bridge safely` to stop the current bridge and relaunch it through `scripts/start-bridge-secure.js`
|
|
89
|
+
- The app will disconnect briefly while the detached helper waits for bridge health to recover
|
|
90
|
+
|
|
91
|
+
Published `clawdex-mobile` CLI installs also expose `Update bridge`.
|
|
92
|
+
|
|
93
|
+
- `Update bridge` stops the current bridge, runs `npm install -g clawdex-mobile@latest`, and starts the bridge again
|
|
90
94
|
- If the upgrade step fails, the helper attempts to restart the previous bridge automatically
|
|
91
95
|
|
|
92
|
-
Source checkouts
|
|
96
|
+
Source checkouts expose only the restart action because repo-specific update logic is not safe to automate generically from mobile.
|
|
93
97
|
|
|
94
98
|
## Local Mobile Development Only
|
|
95
99
|
|
|
@@ -110,6 +114,39 @@ Optional environment variables:
|
|
|
110
114
|
- `EXPO_AUTO_REPAIR=true` — auto-repair React Native runtime on `npm run mobile`
|
|
111
115
|
- `EXPO_CLEAR_CACHE=true` — force `expo start --clear` via `npm run mobile`
|
|
112
116
|
|
|
117
|
+
## Local Browser Preview
|
|
118
|
+
|
|
119
|
+
The mobile app includes a `Browser` screen that can open loopback-only web apps from the bridge
|
|
120
|
+
machine inside the app itself.
|
|
121
|
+
|
|
122
|
+
Typical examples:
|
|
123
|
+
|
|
124
|
+
- `localhost:3000`
|
|
125
|
+
- `127.0.0.1:5173`
|
|
126
|
+
- `3000`
|
|
127
|
+
|
|
128
|
+
How it works:
|
|
129
|
+
|
|
130
|
+
- The app creates a short-lived preview session through the bridge RPC API
|
|
131
|
+
- The bridge serves a dedicated preview origin on a separate port
|
|
132
|
+
- HTTP requests, subresources, cookies, and WebSocket/HMR traffic are proxied from the phone to
|
|
133
|
+
the bridge host's loopback target
|
|
134
|
+
- Browser runtime calls to other loopback origins on the host are also rewritten through the
|
|
135
|
+
preview origin for `fetch`, XHR, `EventSource`, `WebSocket`, and form submissions
|
|
136
|
+
|
|
137
|
+
Current scope:
|
|
138
|
+
|
|
139
|
+
- Supports `http://` and `https://` loopback targets only
|
|
140
|
+
- Intended for local web dev servers such as Next.js, Vite, CRA, or simple static servers
|
|
141
|
+
- Separate local frontend/backend ports can work together inside the preview as long as the app
|
|
142
|
+
reaches the backend through normal browser APIs or form posts
|
|
143
|
+
- Hard-coded absolute localhost asset URLs outside those browser APIs may still need a same-origin
|
|
144
|
+
dev proxy in the app itself
|
|
145
|
+
- Does not preview native React Native simulator/device UI directly
|
|
146
|
+
|
|
147
|
+
For a concise list of supported cases and known limitations, see
|
|
148
|
+
`docs/browser-preview-limitations.md`.
|
|
149
|
+
|
|
113
150
|
## Teardown / Cleanup
|
|
114
151
|
|
|
115
152
|
```bash
|
|
@@ -138,6 +175,7 @@ npm run teardown -- --yes
|
|
|
138
175
|
|---|---|
|
|
139
176
|
| `BRIDGE_HOST` | bind host for rust bridge |
|
|
140
177
|
| `BRIDGE_PORT` | bridge port (default `8787`) |
|
|
178
|
+
| `BRIDGE_PREVIEW_PORT` | browser preview port for proxied localhost web apps (default `BRIDGE_PORT + 1`) |
|
|
141
179
|
| `BRIDGE_AUTH_TOKEN` | required auth token |
|
|
142
180
|
| `BRIDGE_ALLOW_QUERY_TOKEN_AUTH` | query-token auth fallback |
|
|
143
181
|
| `CODEX_CLI_BIN` | codex executable |
|
|
@@ -207,6 +245,7 @@ Expected response contains `"status":"ok"`.
|
|
|
207
245
|
6. Open Git from header and verify status/diff/commit/push behavior
|
|
208
246
|
7. Test attachment menu (`+`) with workspace path + phone file/image
|
|
209
247
|
8. Run long task and verify stop button interrupts run and transcript logs stop
|
|
248
|
+
9. Open `Browser`, enter `localhost:3000` or another active loopback dev port, and verify the page loads inside the app
|
|
210
249
|
|
|
211
250
|
## Chat Controls (Workspace, Model, Mode, Approvals)
|
|
212
251
|
|
package/docs/troubleshooting.md
CHANGED
|
@@ -36,8 +36,20 @@ npm run stop:services
|
|
|
36
36
|
- For the shipped mobile app, rescan the bridge QR or update the stored token in Settings.
|
|
37
37
|
- For a local dev build, also ensure `BRIDGE_AUTH_TOKEN` in `.env.secure` matches `EXPO_PUBLIC_HOST_BRIDGE_TOKEN` in `apps/mobile/.env`.
|
|
38
38
|
- Restart the bridge after token changes.
|
|
39
|
+
- On secure-launcher installs, `Settings > Bridge Maintenance > Restart bridge safely` can do that from the phone.
|
|
39
40
|
- If an in-app bridge update fails, inspect `.bridge-updater.log` and `.bridge-update-status.json` in the bridge install root.
|
|
40
41
|
|
|
42
|
+
## Local browser preview does not open
|
|
43
|
+
|
|
44
|
+
- The in-app browser only supports loopback targets from the bridge host: `localhost`, `127.0.0.1`, or `::1`.
|
|
45
|
+
- Use entries like `localhost:3000`, `127.0.0.1:5173`, or just a port number.
|
|
46
|
+
- If a separate local API runs on another port, make sure the app reaches it through `fetch`, XHR, `EventSource`, `WebSocket`, or a normal form post so the preview runtime can rewrite it through the bridge.
|
|
47
|
+
- See `docs/browser-preview-limitations.md` for the current support boundaries and known caveats.
|
|
48
|
+
- If Browser reports preview is unavailable, check whether `BRIDGE_PREVIEW_PORT` is already in use on the host.
|
|
49
|
+
- By default the preview server binds to `BRIDGE_PORT + 1`.
|
|
50
|
+
- Restart the bridge after changing `BRIDGE_PREVIEW_PORT`.
|
|
51
|
+
- If the page shell loads but live reload does not, verify the target dev server is still serving its WebSocket/HMR endpoint locally.
|
|
52
|
+
|
|
41
53
|
## Tailscale issues
|
|
42
54
|
|
|
43
55
|
- Verify host and phone are on the same Tailscale network
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
"use strict";
|
|
3
3
|
|
|
4
|
-
const { spawn } = require("node:child_process");
|
|
4
|
+
const { spawn, spawnSync } = require("node:child_process");
|
|
5
5
|
const fs = require("node:fs");
|
|
6
6
|
const os = require("node:os");
|
|
7
7
|
const path = require("node:path");
|
|
@@ -10,6 +10,7 @@ const https = require("node:https");
|
|
|
10
10
|
|
|
11
11
|
function parseArgs(argv) {
|
|
12
12
|
const parsed = {
|
|
13
|
+
action: "update",
|
|
13
14
|
jobId: "",
|
|
14
15
|
bridgePid: 0,
|
|
15
16
|
version: "latest",
|
|
@@ -26,6 +27,9 @@ function parseArgs(argv) {
|
|
|
26
27
|
}
|
|
27
28
|
|
|
28
29
|
switch (flag) {
|
|
30
|
+
case "--action":
|
|
31
|
+
parsed.action = value;
|
|
32
|
+
break;
|
|
29
33
|
case "--job-id":
|
|
30
34
|
parsed.jobId = value;
|
|
31
35
|
break;
|
|
@@ -53,6 +57,10 @@ function parseArgs(argv) {
|
|
|
53
57
|
throw new Error("missing updater arguments");
|
|
54
58
|
}
|
|
55
59
|
|
|
60
|
+
if (parsed.action !== "update" && parsed.action !== "restart") {
|
|
61
|
+
throw new Error(`unsupported bridge maintenance action: ${parsed.action}`);
|
|
62
|
+
}
|
|
63
|
+
|
|
56
64
|
return parsed;
|
|
57
65
|
}
|
|
58
66
|
|
|
@@ -94,6 +102,18 @@ function writeStatus(statusPath, payload) {
|
|
|
94
102
|
fs.writeFileSync(statusPath, `${JSON.stringify(nextPayload, null, 2)}\n`);
|
|
95
103
|
}
|
|
96
104
|
|
|
105
|
+
function readNonEmptyEnv(env, key) {
|
|
106
|
+
const value = env?.[key];
|
|
107
|
+
return typeof value === "string" && value.trim() ? value.trim() : "";
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function formatHostForUrl(host) {
|
|
111
|
+
if (host.includes(":") && !host.startsWith("[")) {
|
|
112
|
+
return `[${host}]`;
|
|
113
|
+
}
|
|
114
|
+
return host;
|
|
115
|
+
}
|
|
116
|
+
|
|
97
117
|
function backupSecureEnv(packageRoot, jobId) {
|
|
98
118
|
const secureEnvPath = path.join(packageRoot, ".env.secure");
|
|
99
119
|
if (!fs.existsSync(secureEnvPath)) {
|
|
@@ -166,23 +186,135 @@ function killBridgeProcess(pid) {
|
|
|
166
186
|
}
|
|
167
187
|
}
|
|
168
188
|
|
|
169
|
-
|
|
170
|
-
|
|
189
|
+
function isProcessAlive(pid) {
|
|
190
|
+
try {
|
|
191
|
+
process.kill(pid, 0);
|
|
192
|
+
return true;
|
|
193
|
+
} catch {
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function forceKillBridgeProcess(pid) {
|
|
199
|
+
if (process.platform === "win32") {
|
|
200
|
+
const result = spawnSync("taskkill", ["/PID", String(pid), "/T", "/F"], {
|
|
201
|
+
stdio: "ignore",
|
|
202
|
+
});
|
|
203
|
+
if (result.error && result.error.code !== "ENOENT") {
|
|
204
|
+
throw result.error;
|
|
205
|
+
}
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
process.kill(pid, "SIGKILL");
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function listMatchingPids(pattern) {
|
|
213
|
+
const result = spawnSync("ps", ["-ax", "-o", "pid=", "-o", "command="], {
|
|
214
|
+
encoding: "utf8",
|
|
215
|
+
});
|
|
216
|
+
if (result.error || result.status !== 0) {
|
|
217
|
+
return [];
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return result.stdout
|
|
221
|
+
.split(/\r?\n/)
|
|
222
|
+
.map((line) => line.trim())
|
|
223
|
+
.filter(Boolean)
|
|
224
|
+
.map((line) => {
|
|
225
|
+
const match = line.match(/^(\d+)\s+(.*)$/);
|
|
226
|
+
if (!match) {
|
|
227
|
+
return null;
|
|
228
|
+
}
|
|
229
|
+
return {
|
|
230
|
+
pid: Number.parseInt(match[1], 10),
|
|
231
|
+
command: match[2],
|
|
232
|
+
};
|
|
233
|
+
})
|
|
234
|
+
.filter((entry) => entry && Number.isFinite(entry.pid) && pattern.test(entry.command))
|
|
235
|
+
.map((entry) => entry.pid);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function isOpencodeEnabled(env) {
|
|
239
|
+
const activeEngine = readNonEmptyEnv(env, "BRIDGE_ACTIVE_ENGINE");
|
|
240
|
+
const enabledEngines = readNonEmptyEnv(env, "BRIDGE_ENABLED_ENGINES");
|
|
241
|
+
return [activeEngine, enabledEngines].some((value) =>
|
|
242
|
+
value
|
|
243
|
+
.split(",")
|
|
244
|
+
.map((entry) => entry.trim())
|
|
245
|
+
.filter(Boolean)
|
|
246
|
+
.includes("opencode")
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function buildOpencodeServePattern(port) {
|
|
251
|
+
const normalizedPort = String(port).trim().replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
252
|
+
return new RegExp(`\\bopencode(?:\\.cmd|\\.exe)?\\b.*\\bserve\\b.*--port\\s+${normalizedPort}\\b`);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
async function stopLingeringOpencodeServer(packageRoot) {
|
|
256
|
+
const secureEnvPath = path.join(packageRoot, ".env.secure");
|
|
257
|
+
if (!fs.existsSync(secureEnvPath)) {
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const secureEnv = readEnvFile(secureEnvPath);
|
|
262
|
+
if (!isOpencodeEnabled(secureEnv)) {
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const opencodePort = readNonEmptyEnv(secureEnv, "BRIDGE_OPENCODE_PORT") || "4090";
|
|
267
|
+
const pids = listMatchingPids(buildOpencodeServePattern(opencodePort));
|
|
268
|
+
if (pids.length === 0) {
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
for (const pid of pids) {
|
|
171
273
|
try {
|
|
172
|
-
process.kill(pid,
|
|
173
|
-
} catch {
|
|
274
|
+
process.kill(pid, "SIGTERM");
|
|
275
|
+
} catch {}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
for (let attempt = 0; attempt < 20; attempt += 1) {
|
|
279
|
+
const stillRunning = pids.filter((pid) => isProcessAlive(pid));
|
|
280
|
+
if (stillRunning.length === 0) {
|
|
174
281
|
return;
|
|
175
282
|
}
|
|
176
283
|
await sleep(250);
|
|
177
284
|
}
|
|
178
285
|
|
|
179
|
-
|
|
286
|
+
for (const pid of pids) {
|
|
287
|
+
if (!isProcessAlive(pid)) {
|
|
288
|
+
continue;
|
|
289
|
+
}
|
|
180
290
|
try {
|
|
181
|
-
|
|
182
|
-
} catch {
|
|
291
|
+
forceKillBridgeProcess(pid);
|
|
292
|
+
} catch {}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
async function waitForBridgeExit(pid) {
|
|
297
|
+
for (let attempt = 0; attempt < 40; attempt += 1) {
|
|
298
|
+
if (!isProcessAlive(pid)) {
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
await sleep(250);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
try {
|
|
305
|
+
forceKillBridgeProcess(pid);
|
|
306
|
+
} catch {
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
for (let attempt = 0; attempt < 20; attempt += 1) {
|
|
311
|
+
if (!isProcessAlive(pid)) {
|
|
183
312
|
return;
|
|
184
313
|
}
|
|
314
|
+
await sleep(250);
|
|
185
315
|
}
|
|
316
|
+
|
|
317
|
+
throw new Error("bridge process did not exit in time");
|
|
186
318
|
}
|
|
187
319
|
|
|
188
320
|
function startBridge(packageRoot, logPath) {
|
|
@@ -209,7 +341,7 @@ async function waitForHealth(envFilePath, timeoutMs) {
|
|
|
209
341
|
const secureEnv = readEnvFile(envFilePath);
|
|
210
342
|
const host = secureEnv.BRIDGE_HOST || "127.0.0.1";
|
|
211
343
|
const port = secureEnv.BRIDGE_PORT || "8787";
|
|
212
|
-
const url = new URL(`http://${host}:${port}/health`);
|
|
344
|
+
const url = new URL(`http://${formatHostForUrl(host)}:${port}/health`);
|
|
213
345
|
const client = url.protocol === "https:" ? https : http;
|
|
214
346
|
const startedAt = Date.now();
|
|
215
347
|
|
|
@@ -242,6 +374,7 @@ async function waitForHealth(envFilePath, timeoutMs) {
|
|
|
242
374
|
}
|
|
243
375
|
|
|
244
376
|
async function restartBridge(packageRoot, statusPath, payload, message) {
|
|
377
|
+
await stopLingeringOpencodeServer(packageRoot);
|
|
245
378
|
writeStatus(statusPath, {
|
|
246
379
|
...payload,
|
|
247
380
|
state: "starting",
|
|
@@ -256,13 +389,23 @@ async function restartBridge(packageRoot, statusPath, payload, message) {
|
|
|
256
389
|
await waitForHealth(path.join(packageRoot, ".env.secure"), 45_000);
|
|
257
390
|
}
|
|
258
391
|
|
|
392
|
+
async function stopCurrentBridge(statusPath, payload, bridgePid) {
|
|
393
|
+
writeStatus(statusPath, {
|
|
394
|
+
...payload,
|
|
395
|
+
state: "stopping",
|
|
396
|
+
message: "Stopping the current bridge process.",
|
|
397
|
+
});
|
|
398
|
+
killBridgeProcess(bridgePid);
|
|
399
|
+
await waitForBridgeExit(bridgePid);
|
|
400
|
+
}
|
|
401
|
+
|
|
259
402
|
async function main() {
|
|
260
403
|
const args = parseArgs(process.argv.slice(2));
|
|
261
404
|
const packageRoot = path.resolve(__dirname, "..");
|
|
262
405
|
const secureEnvBackup = backupSecureEnv(packageRoot, args.jobId);
|
|
263
406
|
const baseStatus = {
|
|
264
407
|
jobId: args.jobId,
|
|
265
|
-
targetVersion: args.version,
|
|
408
|
+
targetVersion: args.action === "restart" ? args.version || "current" : args.version,
|
|
266
409
|
startedAt: args.startedAt,
|
|
267
410
|
logPath: args.logPath || null,
|
|
268
411
|
};
|
|
@@ -270,75 +413,113 @@ async function main() {
|
|
|
270
413
|
writeStatus(args.statusPath, {
|
|
271
414
|
...baseStatus,
|
|
272
415
|
state: "scheduled",
|
|
273
|
-
message:
|
|
416
|
+
message:
|
|
417
|
+
args.action === "restart"
|
|
418
|
+
? "Bridge restart scheduled."
|
|
419
|
+
: `Bridge update scheduled for ${args.version}.`,
|
|
274
420
|
});
|
|
275
|
-
await sleep(800);
|
|
276
|
-
|
|
277
|
-
writeStatus(args.statusPath, {
|
|
278
|
-
...baseStatus,
|
|
279
|
-
state: "stopping",
|
|
280
|
-
message: "Stopping the current bridge process.",
|
|
281
|
-
});
|
|
282
|
-
killBridgeProcess(args.bridgePid);
|
|
283
|
-
await waitForBridgeExit(args.bridgePid);
|
|
284
|
-
|
|
285
|
-
let upgraded = false;
|
|
286
421
|
try {
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
422
|
+
await sleep(800);
|
|
423
|
+
await stopCurrentBridge(args.statusPath, baseStatus, args.bridgePid);
|
|
424
|
+
|
|
425
|
+
if (args.action === "restart") {
|
|
426
|
+
try {
|
|
427
|
+
await restartBridge(
|
|
428
|
+
packageRoot,
|
|
429
|
+
args.statusPath,
|
|
430
|
+
baseStatus,
|
|
431
|
+
"Starting the bridge process."
|
|
432
|
+
);
|
|
433
|
+
writeStatus(args.statusPath, {
|
|
434
|
+
...baseStatus,
|
|
435
|
+
state: "completed",
|
|
436
|
+
message: "Bridge restarted successfully.",
|
|
437
|
+
completedAt: new Date().toISOString(),
|
|
438
|
+
});
|
|
439
|
+
} catch (error) {
|
|
440
|
+
writeStatus(args.statusPath, {
|
|
441
|
+
...baseStatus,
|
|
442
|
+
state: "failed",
|
|
443
|
+
message:
|
|
444
|
+
error instanceof Error
|
|
445
|
+
? error.message
|
|
446
|
+
: "Bridge restart failed and the bridge did not recover automatically.",
|
|
447
|
+
completedAt: new Date().toISOString(),
|
|
448
|
+
});
|
|
449
|
+
process.exitCode = 1;
|
|
450
|
+
}
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
let upgraded = false;
|
|
312
455
|
try {
|
|
456
|
+
writeStatus(args.statusPath, {
|
|
457
|
+
...baseStatus,
|
|
458
|
+
state: "upgrading",
|
|
459
|
+
message: `Installing clawdex-mobile@${args.version}.`,
|
|
460
|
+
});
|
|
461
|
+
await runCommand(npmCommand(), ["install", "-g", `clawdex-mobile@${args.version}`], {
|
|
462
|
+
cwd: packageRoot,
|
|
463
|
+
env: process.env,
|
|
464
|
+
});
|
|
313
465
|
restoreSecureEnv(secureEnvBackup);
|
|
466
|
+
upgraded = true;
|
|
314
467
|
await restartBridge(
|
|
315
468
|
packageRoot,
|
|
316
469
|
args.statusPath,
|
|
317
470
|
baseStatus,
|
|
318
|
-
|
|
319
|
-
? "Updated bridge failed to come up; restarting the previous bridge."
|
|
320
|
-
: "Upgrade failed; restarting the previous bridge."
|
|
471
|
+
"Starting the updated bridge process."
|
|
321
472
|
);
|
|
322
473
|
writeStatus(args.statusPath, {
|
|
323
474
|
...baseStatus,
|
|
324
|
-
state: "
|
|
325
|
-
message:
|
|
326
|
-
? "Bridge update failed after install. The previous bridge was restarted."
|
|
327
|
-
: "Bridge upgrade failed. The previous bridge was restarted.",
|
|
328
|
-
completedAt: new Date().toISOString(),
|
|
329
|
-
});
|
|
330
|
-
} catch (restartError) {
|
|
331
|
-
writeStatus(args.statusPath, {
|
|
332
|
-
...baseStatus,
|
|
333
|
-
state: "failed",
|
|
334
|
-
message:
|
|
335
|
-
restartError instanceof Error
|
|
336
|
-
? restartError.message
|
|
337
|
-
: "Bridge update failed and automatic recovery did not complete.",
|
|
475
|
+
state: "completed",
|
|
476
|
+
message: `Bridge updated to ${args.version} and restarted successfully.`,
|
|
338
477
|
completedAt: new Date().toISOString(),
|
|
339
478
|
});
|
|
340
|
-
|
|
479
|
+
} catch (error) {
|
|
480
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
481
|
+
try {
|
|
482
|
+
restoreSecureEnv(secureEnvBackup);
|
|
483
|
+
await restartBridge(
|
|
484
|
+
packageRoot,
|
|
485
|
+
args.statusPath,
|
|
486
|
+
baseStatus,
|
|
487
|
+
upgraded
|
|
488
|
+
? "Updated bridge failed to come up; restarting the previous bridge."
|
|
489
|
+
: "Upgrade failed; restarting the previous bridge."
|
|
490
|
+
);
|
|
491
|
+
writeStatus(args.statusPath, {
|
|
492
|
+
...baseStatus,
|
|
493
|
+
state: "recovered",
|
|
494
|
+
message: upgraded
|
|
495
|
+
? "Bridge update failed after install. The previous bridge was restarted."
|
|
496
|
+
: "Bridge upgrade failed. The previous bridge was restarted.",
|
|
497
|
+
completedAt: new Date().toISOString(),
|
|
498
|
+
});
|
|
499
|
+
} catch (restartError) {
|
|
500
|
+
writeStatus(args.statusPath, {
|
|
501
|
+
...baseStatus,
|
|
502
|
+
state: "failed",
|
|
503
|
+
message:
|
|
504
|
+
restartError instanceof Error
|
|
505
|
+
? restartError.message
|
|
506
|
+
: "Bridge update failed and automatic recovery did not complete.",
|
|
507
|
+
completedAt: new Date().toISOString(),
|
|
508
|
+
});
|
|
509
|
+
process.exitCode = 1;
|
|
510
|
+
}
|
|
341
511
|
}
|
|
512
|
+
} catch (error) {
|
|
513
|
+
writeStatus(args.statusPath, {
|
|
514
|
+
...baseStatus,
|
|
515
|
+
state: "failed",
|
|
516
|
+
message:
|
|
517
|
+
error instanceof Error
|
|
518
|
+
? error.message
|
|
519
|
+
: "Bridge maintenance failed before restart or recovery could begin.",
|
|
520
|
+
completedAt: new Date().toISOString(),
|
|
521
|
+
});
|
|
522
|
+
process.exitCode = 1;
|
|
342
523
|
} finally {
|
|
343
524
|
cleanupSecureEnvBackup(secureEnvBackup);
|
|
344
525
|
}
|
|
@@ -579,6 +579,9 @@ async function start() {
|
|
|
579
579
|
const fileEnv = readEnvFile(secureEnvFile);
|
|
580
580
|
const env = { ...fileEnv, ...process.env };
|
|
581
581
|
const devMode = process.argv.includes("--dev") || env.BRIDGE_RUN_MODE === "dev";
|
|
582
|
+
if (devMode) {
|
|
583
|
+
env.BRIDGE_RUN_MODE = "dev";
|
|
584
|
+
}
|
|
582
585
|
const backgroundMode = process.argv.includes("--background");
|
|
583
586
|
const forceSourceBuild = env.CLAWDEX_BRIDGE_FORCE_SOURCE_BUILD === "true";
|
|
584
587
|
const launch = resolveLaunch(rootDir, env, { devMode, forceSourceBuild });
|
|
@@ -149,7 +149,7 @@ dependencies = [
|
|
|
149
149
|
|
|
150
150
|
[[package]]
|
|
151
151
|
name = "codex-rust-bridge"
|
|
152
|
-
version = "5.
|
|
152
|
+
version = "5.1.2"
|
|
153
153
|
dependencies = [
|
|
154
154
|
"axum",
|
|
155
155
|
"base64",
|
|
@@ -162,6 +162,7 @@ dependencies = [
|
|
|
162
162
|
"serde_json",
|
|
163
163
|
"shlex",
|
|
164
164
|
"tokio",
|
|
165
|
+
"tokio-tungstenite",
|
|
165
166
|
]
|
|
166
167
|
|
|
167
168
|
[[package]]
|
|
@@ -278,6 +279,12 @@ version = "0.3.32"
|
|
|
278
279
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
279
280
|
checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d"
|
|
280
281
|
|
|
282
|
+
[[package]]
|
|
283
|
+
name = "futures-io"
|
|
284
|
+
version = "0.3.32"
|
|
285
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
286
|
+
checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718"
|
|
287
|
+
|
|
281
288
|
[[package]]
|
|
282
289
|
name = "futures-macro"
|
|
283
290
|
version = "0.3.32"
|
|
@@ -308,9 +315,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
|
308
315
|
checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6"
|
|
309
316
|
dependencies = [
|
|
310
317
|
"futures-core",
|
|
318
|
+
"futures-io",
|
|
311
319
|
"futures-macro",
|
|
312
320
|
"futures-sink",
|
|
313
321
|
"futures-task",
|
|
322
|
+
"memchr",
|
|
314
323
|
"pin-project-lite",
|
|
315
324
|
"slab",
|
|
316
325
|
]
|
|
@@ -433,7 +442,7 @@ dependencies = [
|
|
|
433
442
|
"tokio",
|
|
434
443
|
"tokio-rustls",
|
|
435
444
|
"tower-service",
|
|
436
|
-
"webpki-roots",
|
|
445
|
+
"webpki-roots 1.0.6",
|
|
437
446
|
]
|
|
438
447
|
|
|
439
448
|
[[package]]
|
|
@@ -932,14 +941,16 @@ dependencies = [
|
|
|
932
941
|
"sync_wrapper",
|
|
933
942
|
"tokio",
|
|
934
943
|
"tokio-rustls",
|
|
944
|
+
"tokio-util",
|
|
935
945
|
"tower",
|
|
936
946
|
"tower-http",
|
|
937
947
|
"tower-service",
|
|
938
948
|
"url",
|
|
939
949
|
"wasm-bindgen",
|
|
940
950
|
"wasm-bindgen-futures",
|
|
951
|
+
"wasm-streams",
|
|
941
952
|
"web-sys",
|
|
942
|
-
"webpki-roots",
|
|
953
|
+
"webpki-roots 1.0.6",
|
|
943
954
|
]
|
|
944
955
|
|
|
945
956
|
[[package]]
|
|
@@ -1277,8 +1288,25 @@ checksum = "d25a406cddcc431a75d3d9afc6a7c0f7428d4891dd973e4d54c56b46127bf857"
|
|
|
1277
1288
|
dependencies = [
|
|
1278
1289
|
"futures-util",
|
|
1279
1290
|
"log",
|
|
1291
|
+
"rustls",
|
|
1292
|
+
"rustls-pki-types",
|
|
1280
1293
|
"tokio",
|
|
1294
|
+
"tokio-rustls",
|
|
1281
1295
|
"tungstenite",
|
|
1296
|
+
"webpki-roots 0.26.11",
|
|
1297
|
+
]
|
|
1298
|
+
|
|
1299
|
+
[[package]]
|
|
1300
|
+
name = "tokio-util"
|
|
1301
|
+
version = "0.7.18"
|
|
1302
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1303
|
+
checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098"
|
|
1304
|
+
dependencies = [
|
|
1305
|
+
"bytes",
|
|
1306
|
+
"futures-core",
|
|
1307
|
+
"futures-sink",
|
|
1308
|
+
"pin-project-lite",
|
|
1309
|
+
"tokio",
|
|
1282
1310
|
]
|
|
1283
1311
|
|
|
1284
1312
|
[[package]]
|
|
@@ -1365,6 +1393,8 @@ dependencies = [
|
|
|
1365
1393
|
"httparse",
|
|
1366
1394
|
"log",
|
|
1367
1395
|
"rand",
|
|
1396
|
+
"rustls",
|
|
1397
|
+
"rustls-pki-types",
|
|
1368
1398
|
"sha1",
|
|
1369
1399
|
"thiserror",
|
|
1370
1400
|
"utf-8",
|
|
@@ -1507,6 +1537,19 @@ dependencies = [
|
|
|
1507
1537
|
"unicode-ident",
|
|
1508
1538
|
]
|
|
1509
1539
|
|
|
1540
|
+
[[package]]
|
|
1541
|
+
name = "wasm-streams"
|
|
1542
|
+
version = "0.4.2"
|
|
1543
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1544
|
+
checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65"
|
|
1545
|
+
dependencies = [
|
|
1546
|
+
"futures-util",
|
|
1547
|
+
"js-sys",
|
|
1548
|
+
"wasm-bindgen",
|
|
1549
|
+
"wasm-bindgen-futures",
|
|
1550
|
+
"web-sys",
|
|
1551
|
+
]
|
|
1552
|
+
|
|
1510
1553
|
[[package]]
|
|
1511
1554
|
name = "web-sys"
|
|
1512
1555
|
version = "0.3.91"
|
|
@@ -1527,6 +1570,15 @@ dependencies = [
|
|
|
1527
1570
|
"wasm-bindgen",
|
|
1528
1571
|
]
|
|
1529
1572
|
|
|
1573
|
+
[[package]]
|
|
1574
|
+
name = "webpki-roots"
|
|
1575
|
+
version = "0.26.11"
|
|
1576
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1577
|
+
checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9"
|
|
1578
|
+
dependencies = [
|
|
1579
|
+
"webpki-roots 1.0.6",
|
|
1580
|
+
]
|
|
1581
|
+
|
|
1530
1582
|
[[package]]
|
|
1531
1583
|
name = "webpki-roots"
|
|
1532
1584
|
version = "1.0.6"
|