@makerbi/remodex 1.3.10 → 1.4.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/bin/remodex.js +1 -0
- package/package.json +2 -2
- package/src/bridge.js +29 -0
- package/src/codex-desktop-refresher.js +1 -0
- package/src/desktop-handler.js +100 -11
- package/src/desktop-ipc-action-follower.js +648 -0
- package/src/git-handler.js +322 -9
- package/src/pet-handler.js +537 -0
- package/src/qr.js +21 -2
- package/src/workspace-handler.js +31 -4
- package/src/private-defaults.json +0 -4
package/bin/remodex.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@makerbi/remodex",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.1",
|
|
4
4
|
"description": "Local bridge between Codex and the Remodex mobile app. Run `remodex up` to start.",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"cli"
|
|
25
25
|
],
|
|
26
26
|
"author": "Emanuele Di Pietro",
|
|
27
|
-
"license": "
|
|
27
|
+
"license": "Apache-2.0",
|
|
28
28
|
"type": "commonjs",
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"qrcode-terminal": "^0.12.0",
|
package/src/bridge.js
CHANGED
|
@@ -24,6 +24,7 @@ const { handleGitRequest } = require("./git-handler");
|
|
|
24
24
|
const { handleThreadContextRequest } = require("./thread-context-handler");
|
|
25
25
|
const { handleWorkspaceRequest } = require("./workspace-handler");
|
|
26
26
|
const { handleProjectRequest } = require("./project-handler");
|
|
27
|
+
const { handlePetRequest } = require("./pet-handler");
|
|
27
28
|
const { createNotificationsHandler } = require("./notifications-handler");
|
|
28
29
|
const { createVoiceHandler, resolveVoiceAuth } = require("./voice-handler");
|
|
29
30
|
const {
|
|
@@ -40,6 +41,10 @@ const {
|
|
|
40
41
|
} = require("./secure-device-state");
|
|
41
42
|
const { createBridgeSecureTransport } = require("./secure-transport");
|
|
42
43
|
const { createRolloutLiveMirrorController } = require("./rollout-live-mirror");
|
|
44
|
+
const {
|
|
45
|
+
createDesktopIpcActionFollower,
|
|
46
|
+
seedConversationStateFromThreadRead,
|
|
47
|
+
} = require("./desktop-ipc-action-follower");
|
|
43
48
|
const { version: bridgePackageVersion = "" } = require("../package.json");
|
|
44
49
|
const {
|
|
45
50
|
MINIMUM_SUPPORTED_IOS_APP_VERSION,
|
|
@@ -198,6 +203,13 @@ function startBridge({
|
|
|
198
203
|
sendApplicationResponse,
|
|
199
204
|
})
|
|
200
205
|
: null;
|
|
206
|
+
const desktopIpcActionFollower = !config.codexEndpoint
|
|
207
|
+
? createDesktopIpcActionFollower({
|
|
208
|
+
sendApplicationResponse,
|
|
209
|
+
readConversationState: readDesktopConversationState,
|
|
210
|
+
socketPath: config.desktopIpcSocketPath || undefined,
|
|
211
|
+
})
|
|
212
|
+
: null;
|
|
201
213
|
let contextUsageWatcher = null;
|
|
202
214
|
let watchedContextUsageKey = null;
|
|
203
215
|
|
|
@@ -443,6 +455,7 @@ function startBridge({
|
|
|
443
455
|
}
|
|
444
456
|
stopContextUsageWatcher();
|
|
445
457
|
rolloutLiveMirror?.stopAll();
|
|
458
|
+
desktopIpcActionFollower?.stopAll();
|
|
446
459
|
desktopRefresher.handleTransportReset();
|
|
447
460
|
scheduleRelayReconnect(code, closeReason);
|
|
448
461
|
});
|
|
@@ -498,6 +511,7 @@ function startBridge({
|
|
|
498
511
|
clearReconnectTimer();
|
|
499
512
|
stopContextUsageWatcher();
|
|
500
513
|
rolloutLiveMirror?.stopAll();
|
|
514
|
+
desktopIpcActionFollower?.stopAll();
|
|
501
515
|
desktopRefresher.handleTransportReset();
|
|
502
516
|
failBridgeManagedCodexRequests(new Error("Codex transport closed before the bridge request completed."));
|
|
503
517
|
forwardedRequestMethodsById.clear();
|
|
@@ -541,6 +555,9 @@ function startBridge({
|
|
|
541
555
|
if (handleProjectRequest(rawMessage, sendApplicationResponse)) {
|
|
542
556
|
return;
|
|
543
557
|
}
|
|
558
|
+
if (handlePetRequest(rawMessage, sendApplicationResponse)) {
|
|
559
|
+
return;
|
|
560
|
+
}
|
|
544
561
|
if (notificationsHandler.handleNotificationsRequest(rawMessage, sendApplicationResponse)) {
|
|
545
562
|
return;
|
|
546
563
|
}
|
|
@@ -560,6 +577,9 @@ function startBridge({
|
|
|
560
577
|
}
|
|
561
578
|
desktopRefresher.handleInbound(rawMessage);
|
|
562
579
|
rolloutLiveMirror?.observeInbound(rawMessage);
|
|
580
|
+
if (desktopIpcActionFollower?.observeInbound(rawMessage)) {
|
|
581
|
+
return;
|
|
582
|
+
}
|
|
563
583
|
rememberForwardedRequestMethod(rawMessage);
|
|
564
584
|
rememberThreadFromMessage("phone", rawMessage);
|
|
565
585
|
codex.send(rawMessage);
|
|
@@ -592,6 +612,15 @@ function startBridge({
|
|
|
592
612
|
}));
|
|
593
613
|
}
|
|
594
614
|
|
|
615
|
+
// Seeds the desktop IPC follower when it receives patches before a full snapshot.
|
|
616
|
+
async function readDesktopConversationState(threadId) {
|
|
617
|
+
const result = await sendCodexRequest("thread/read", {
|
|
618
|
+
threadId,
|
|
619
|
+
includeTurns: true,
|
|
620
|
+
});
|
|
621
|
+
return seedConversationStateFromThreadRead(result);
|
|
622
|
+
}
|
|
623
|
+
|
|
595
624
|
// ─── Bridge-owned auth snapshot ─────────────────────────────
|
|
596
625
|
|
|
597
626
|
// Handles the bridge-owned auth status wrappers without exposing tokens to the phone.
|
|
@@ -587,6 +587,7 @@ function readBridgeConfig({
|
|
|
587
587
|
? (persistedKeepMacAwakeEnabled == null ? false : persistedKeepMacAwakeEnabled)
|
|
588
588
|
: explicitKeepMacAwakeEnabled,
|
|
589
589
|
codexEndpoint,
|
|
590
|
+
desktopIpcSocketPath: readFirstDefinedEnv(["REMODEX_DESKTOP_IPC_SOCKET"], "", env),
|
|
590
591
|
refreshCommand,
|
|
591
592
|
codexBundleId: readFirstDefinedEnv(["REMODEX_CODEX_BUNDLE_ID"], DEFAULT_BUNDLE_ID, env),
|
|
592
593
|
codexAppPath: DEFAULT_APP_PATH,
|
package/src/desktop-handler.js
CHANGED
|
@@ -20,6 +20,8 @@ const DEFAULT_APP_BOOT_WAIT_MS = 1_200;
|
|
|
20
20
|
const DEFAULT_THREAD_MATERIALIZE_WAIT_MS = 4_000;
|
|
21
21
|
const DEFAULT_THREAD_MATERIALIZE_POLL_MS = 250;
|
|
22
22
|
const DEFAULT_WAKE_DISPLAY_DURATION_SECONDS = 30;
|
|
23
|
+
const WINDOWS_BOUNCE_URL = "codex://settings";
|
|
24
|
+
const DESKTOP_THREAD_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,255}$/;
|
|
23
25
|
|
|
24
26
|
function handleDesktopRequest(rawMessage, sendResponse, options = {}) {
|
|
25
27
|
let parsed;
|
|
@@ -71,16 +73,39 @@ async function handleDesktopMethod(method, params, options = {}) {
|
|
|
71
73
|
const threadMaterializeWaitMs = options.threadMaterializeWaitMs ?? DEFAULT_THREAD_MATERIALIZE_WAIT_MS;
|
|
72
74
|
const threadMaterializePollMs = options.threadMaterializePollMs ?? DEFAULT_THREAD_MATERIALIZE_POLL_MS;
|
|
73
75
|
|
|
74
|
-
if (platform !== "darwin") {
|
|
75
|
-
throw desktopError(
|
|
76
|
-
"unsupported_platform",
|
|
77
|
-
"Mac handoff is only available when the bridge is running on macOS."
|
|
78
|
-
);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
76
|
switch (method) {
|
|
77
|
+
case "desktop/continueOnDesktop":
|
|
78
|
+
if (platform !== "darwin" && platform !== "win32") {
|
|
79
|
+
throw desktopError(
|
|
80
|
+
"unsupported_platform",
|
|
81
|
+
"Desktop handoff is only available when the bridge is running on macOS or Windows."
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return continueOnDesktop(params, {
|
|
86
|
+
platform,
|
|
87
|
+
bundleId,
|
|
88
|
+
appPath,
|
|
89
|
+
executor,
|
|
90
|
+
env,
|
|
91
|
+
fsModule,
|
|
92
|
+
isAppRunning,
|
|
93
|
+
sleepFn,
|
|
94
|
+
appBootWaitMs,
|
|
95
|
+
relaunchWaitMs,
|
|
96
|
+
threadMaterializeWaitMs,
|
|
97
|
+
threadMaterializePollMs,
|
|
98
|
+
});
|
|
82
99
|
case "desktop/continueOnMac":
|
|
83
|
-
|
|
100
|
+
if (platform !== "darwin") {
|
|
101
|
+
throw desktopError(
|
|
102
|
+
"unsupported_platform",
|
|
103
|
+
"Mac handoff is only available when the bridge is running on macOS."
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return continueOnDesktop(params, {
|
|
108
|
+
platform,
|
|
84
109
|
bundleId,
|
|
85
110
|
appPath,
|
|
86
111
|
executor,
|
|
@@ -106,10 +131,11 @@ async function handleDesktopMethod(method, params, options = {}) {
|
|
|
106
131
|
}
|
|
107
132
|
}
|
|
108
133
|
|
|
109
|
-
// Waits for fresh phone-authored chats to materialize locally before deep-linking them on
|
|
110
|
-
async function
|
|
134
|
+
// Waits for fresh phone-authored chats to materialize locally before deep-linking them on desktop.
|
|
135
|
+
async function continueOnDesktop(
|
|
111
136
|
params,
|
|
112
137
|
{
|
|
138
|
+
platform,
|
|
113
139
|
bundleId,
|
|
114
140
|
appPath,
|
|
115
141
|
executor,
|
|
@@ -125,11 +151,53 @@ async function continueOnMac(
|
|
|
125
151
|
) {
|
|
126
152
|
const threadId = resolveThreadId(params);
|
|
127
153
|
if (!threadId) {
|
|
128
|
-
throw desktopError("missing_thread_id", "A thread id is required to continue on
|
|
154
|
+
throw desktopError("missing_thread_id", "A thread id is required to continue on desktop.");
|
|
155
|
+
}
|
|
156
|
+
if (!isValidDesktopThreadId(threadId)) {
|
|
157
|
+
throw desktopError("invalid_thread_id", "The requested desktop thread id is not valid.");
|
|
129
158
|
}
|
|
130
159
|
|
|
131
160
|
const targetUrl = `codex://threads/${threadId}`;
|
|
132
161
|
const desktopKnown = isThreadLikelyKnownOnDesktop(threadId, { env, fsModule });
|
|
162
|
+
|
|
163
|
+
if (platform === "win32") {
|
|
164
|
+
try {
|
|
165
|
+
if (desktopKnown) {
|
|
166
|
+
await refreshWindowsCodex(targetUrl, {
|
|
167
|
+
executor,
|
|
168
|
+
env,
|
|
169
|
+
sleepFn,
|
|
170
|
+
settleMs: relaunchWaitMs,
|
|
171
|
+
});
|
|
172
|
+
} else {
|
|
173
|
+
await openWindowsDeepLink(WINDOWS_BOUNCE_URL, { executor, env });
|
|
174
|
+
await sleepFn(appBootWaitMs);
|
|
175
|
+
await waitForThreadMaterialization(threadId, {
|
|
176
|
+
env,
|
|
177
|
+
fsModule,
|
|
178
|
+
sleepFn,
|
|
179
|
+
timeoutMs: threadMaterializeWaitMs,
|
|
180
|
+
pollMs: threadMaterializePollMs,
|
|
181
|
+
});
|
|
182
|
+
await openWindowsDeepLink(targetUrl, { executor, env });
|
|
183
|
+
}
|
|
184
|
+
} catch (error) {
|
|
185
|
+
throw desktopError(
|
|
186
|
+
"handoff_failed",
|
|
187
|
+
"Could not open Codex on this PC.",
|
|
188
|
+
error
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return {
|
|
193
|
+
success: true,
|
|
194
|
+
relaunched: false,
|
|
195
|
+
targetUrl,
|
|
196
|
+
threadId,
|
|
197
|
+
desktopKnown,
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
|
|
133
201
|
const appRunning = typeof isAppRunning === "function"
|
|
134
202
|
? await isAppRunning(appPath)
|
|
135
203
|
: await detectRunningCodexApp(appPath, executor);
|
|
@@ -311,6 +379,11 @@ function resolveThreadId(params) {
|
|
|
311
379
|
return "";
|
|
312
380
|
}
|
|
313
381
|
|
|
382
|
+
// Keeps desktop deep links to a single safe route segment before handing them to OS launchers.
|
|
383
|
+
function isValidDesktopThreadId(threadId) {
|
|
384
|
+
return typeof threadId === "string" && DESKTOP_THREAD_ID_PATTERN.test(threadId);
|
|
385
|
+
}
|
|
386
|
+
|
|
314
387
|
function desktopError(errorCode, userMessage, cause = null) {
|
|
315
388
|
const error = new Error(userMessage);
|
|
316
389
|
error.errorCode = errorCode;
|
|
@@ -372,6 +445,22 @@ async function openCodexApp({ bundleId, appPath, executor }) {
|
|
|
372
445
|
}
|
|
373
446
|
}
|
|
374
447
|
|
|
448
|
+
async function openWindowsDeepLink(targetUrl, { executor, env }) {
|
|
449
|
+
await executor(env?.SystemRoot ? path.join(env.SystemRoot, "System32", "rundll32.exe") : "rundll32.exe", [
|
|
450
|
+
"url.dll,FileProtocolHandler",
|
|
451
|
+
targetUrl,
|
|
452
|
+
], {
|
|
453
|
+
timeout: HANDOFF_TIMEOUT_MS,
|
|
454
|
+
windowsHide: true,
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
async function refreshWindowsCodex(targetUrl, { executor, env, sleepFn, settleMs }) {
|
|
459
|
+
await openWindowsDeepLink(WINDOWS_BOUNCE_URL, { executor, env });
|
|
460
|
+
await sleepFn(settleMs);
|
|
461
|
+
await openWindowsDeepLink(targetUrl, { executor, env });
|
|
462
|
+
}
|
|
463
|
+
|
|
375
464
|
// Gives the desktop a short window to materialize the requested thread before the final deep link.
|
|
376
465
|
async function openWhenThreadReady(
|
|
377
466
|
threadId,
|