@makerbi/remodex 1.5.3 → 1.5.8
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-jsonl-diagnose.js +0 -0
- package/bin/remodex.js +42 -10
- package/package.json +1 -1
- package/src/account-status.js +7 -1
- package/src/apply-patch-changes.js +185 -0
- package/src/bootstrap-codex-cli.js +1 -1
- package/src/bridge-status.js +122 -0
- package/src/bridge.js +823 -157
- package/src/codex-transport.js +38 -11
- package/src/desktop-handler.js +14 -1
- package/src/desktop-ipc-action-follower.js +173 -1
- package/src/index.js +4 -2
- package/src/macos-launch-agent.js +87 -2
- package/src/project-handler.js +162 -1
- package/src/push-notification-service-client.js +85 -37
- package/src/push-notification-tracker.js +15 -0
- package/src/rollout-live-mirror.js +331 -20
- package/src/rollout-watch.js +5 -1
- package/src/secure-device-state.js +26 -1
- package/src/secure-transport.js +40 -12
- package/src/session-jsonl-history.js +860 -14
- package/src/voice-handler.js +129 -62
- package/src/workspace-handler.js +327 -14
- package/src/private-defaults.json +0 -4
package/src/codex-transport.js
CHANGED
|
@@ -13,6 +13,7 @@ function createCodexTransport({
|
|
|
13
13
|
endpoint = "",
|
|
14
14
|
env = process.env,
|
|
15
15
|
appPath = "",
|
|
16
|
+
platform = process.platform,
|
|
16
17
|
spawnImpl = spawn,
|
|
17
18
|
WebSocketImpl = WebSocket,
|
|
18
19
|
} = {}) {
|
|
@@ -20,11 +21,11 @@ function createCodexTransport({
|
|
|
20
21
|
return createWebSocketTransport({ endpoint, WebSocketImpl });
|
|
21
22
|
}
|
|
22
23
|
|
|
23
|
-
return createSpawnTransport({ env, appPath, spawnImpl });
|
|
24
|
+
return createSpawnTransport({ env, appPath, platform, spawnImpl });
|
|
24
25
|
}
|
|
25
26
|
|
|
26
|
-
function createSpawnTransport({ env, appPath, spawnImpl = spawn }) {
|
|
27
|
-
const launchPlans = createCodexLaunchPlans({ env, appPath });
|
|
27
|
+
function createSpawnTransport({ env, appPath, platform, spawnImpl = spawn }) {
|
|
28
|
+
const launchPlans = createCodexLaunchPlans({ env, appPath, platform });
|
|
28
29
|
let launchIndex = -1;
|
|
29
30
|
let activeLaunch = null;
|
|
30
31
|
let codex = null;
|
|
@@ -154,13 +155,12 @@ function createSpawnTransport({ env, appPath, spawnImpl = spawn }) {
|
|
|
154
155
|
return;
|
|
155
156
|
}
|
|
156
157
|
stdoutBuffer += chunk.toString("utf8");
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
listeners.emitMessage(trimmedLine);
|
|
158
|
+
let newlineIndex;
|
|
159
|
+
while ((newlineIndex = stdoutBuffer.indexOf("\n")) !== -1) {
|
|
160
|
+
const line = stdoutBuffer.substring(0, newlineIndex).trim();
|
|
161
|
+
stdoutBuffer = stdoutBuffer.substring(newlineIndex + 1);
|
|
162
|
+
if (line) {
|
|
163
|
+
listeners.emitMessage(line);
|
|
164
164
|
}
|
|
165
165
|
}
|
|
166
166
|
});
|
|
@@ -254,7 +254,32 @@ function shutdownCodexProcess(codex) {
|
|
|
254
254
|
function createCodexCloseError({ code, signal, stderrBuffer, launchDescription }) {
|
|
255
255
|
const details = stderrBuffer.trim();
|
|
256
256
|
const reason = details || `Process exited with code ${code}${signal ? ` (signal: ${signal})` : ""}.`;
|
|
257
|
-
return new Error(
|
|
257
|
+
return new Error(formatCodexLaunchFailure({
|
|
258
|
+
launchDescription,
|
|
259
|
+
reason,
|
|
260
|
+
}));
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Turns common Codex auth/config failures into recovery guidance without handling secrets in Remodex.
|
|
264
|
+
function formatCodexLaunchFailure({ launchDescription, reason }) {
|
|
265
|
+
const message = `Codex launcher ${launchDescription} failed: ${reason}`;
|
|
266
|
+
const missingEnvVar = extractMissingEnvironmentVariable(reason);
|
|
267
|
+
if (!missingEnvVar) {
|
|
268
|
+
return message;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const guidance = [
|
|
272
|
+
`Codex is asking for ${missingEnvVar}, which usually means your Codex config forces API-key auth or a custom provider env var.`,
|
|
273
|
+
"Remodex does not store or forward OpenAI API keys.",
|
|
274
|
+
"Recommended fix: run `codex login` on this Mac, then restart Remodex.",
|
|
275
|
+
"If you intentionally use API-key auth, run `printenv OPENAI_API_KEY | codex login --with-api-key` or make that env var available to the Remodex daemon yourself.",
|
|
276
|
+
];
|
|
277
|
+
return `${message}\n${guidance.join("\n")}`;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function extractMissingEnvironmentVariable(reason) {
|
|
281
|
+
const match = String(reason || "").match(/Missing environment variable:\s*`?([A-Za-z_][A-Za-z0-9_]*)`?/i);
|
|
282
|
+
return match ? match[1] : "";
|
|
258
283
|
}
|
|
259
284
|
|
|
260
285
|
function appendOutputBuffer(buffer, chunk) {
|
|
@@ -350,4 +375,6 @@ function createListenerBag() {
|
|
|
350
375
|
module.exports = {
|
|
351
376
|
createCodexLaunchPlans,
|
|
352
377
|
createCodexTransport,
|
|
378
|
+
extractMissingEnvironmentVariable,
|
|
379
|
+
formatCodexLaunchFailure,
|
|
353
380
|
};
|
package/src/desktop-handler.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// FILE: desktop-handler.js
|
|
2
|
-
// Purpose: Handles explicit desktop handoff, display wake, and
|
|
2
|
+
// Purpose: Handles explicit desktop handoff, display wake, bridge update, and preference RPCs for Codex.app.
|
|
3
3
|
// Layer: Bridge handler
|
|
4
4
|
// Exports: handleDesktopRequest
|
|
5
5
|
// Depends on: child_process, fs, os, path, ./rollout-watch
|
|
@@ -126,6 +126,8 @@ async function handleDesktopMethod(method, params, options = {}) {
|
|
|
126
126
|
return readBridgePreferences(options);
|
|
127
127
|
case "desktop/preferences/update":
|
|
128
128
|
return updateBridgePreferences(params, options);
|
|
129
|
+
case "desktop/bridge/updateAndRestart":
|
|
130
|
+
return updateBridgePackageAndRestart(options);
|
|
129
131
|
default:
|
|
130
132
|
throw desktopError("unknown_method", `Unknown desktop method: ${method}`);
|
|
131
133
|
}
|
|
@@ -360,6 +362,17 @@ async function updateBridgePreferences(params, options = {}) {
|
|
|
360
362
|
});
|
|
361
363
|
}
|
|
362
364
|
|
|
365
|
+
async function updateBridgePackageAndRestart(options = {}) {
|
|
366
|
+
if (typeof options.updateBridgePackageAndRestart !== "function") {
|
|
367
|
+
throw desktopError(
|
|
368
|
+
"unsupported_bridge_update",
|
|
369
|
+
"This bridge does not support iPhone-triggered bridge updates yet."
|
|
370
|
+
);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
return options.updateBridgePackageAndRestart();
|
|
374
|
+
}
|
|
375
|
+
|
|
363
376
|
function resolveThreadId(params) {
|
|
364
377
|
if (!params || typeof params !== "object") {
|
|
365
378
|
return "";
|
|
@@ -11,17 +11,20 @@ const path = require("path");
|
|
|
11
11
|
const FRAME_HEADER_BYTES = 4;
|
|
12
12
|
const MAX_FRAME_BYTES = 256 * 1024 * 1024;
|
|
13
13
|
const REQUEST_TIMEOUT_MS = 10_000;
|
|
14
|
+
const DESKTOP_IPC_ACTION_SOURCE = "desktop-ipc-action-follower";
|
|
14
15
|
const DESKTOP_RESUME_METHODS = new Set(["thread/read", "thread/resume"]);
|
|
15
16
|
const ACTION_METHODS = new Set([
|
|
16
17
|
"item/commandExecution/requestApproval",
|
|
17
18
|
"item/fileChange/requestApproval",
|
|
18
19
|
"item/fileRead/requestApproval",
|
|
20
|
+
"item/permissions/requestApproval",
|
|
19
21
|
"item/tool/requestUserInput",
|
|
20
22
|
]);
|
|
21
23
|
const REPLY_METHOD_BY_ACTION_METHOD = new Map([
|
|
22
24
|
["item/commandExecution/requestApproval", "thread-follower-command-approval-decision"],
|
|
23
25
|
["item/fileChange/requestApproval", "thread-follower-file-approval-decision"],
|
|
24
26
|
["item/fileRead/requestApproval", "thread-follower-file-approval-decision"],
|
|
27
|
+
["item/permissions/requestApproval", "thread-follower-file-approval-decision"],
|
|
25
28
|
["item/tool/requestUserInput", "thread-follower-submit-user-input"],
|
|
26
29
|
]);
|
|
27
30
|
const METHOD_VERSION_BY_NAME = new Map([
|
|
@@ -52,6 +55,7 @@ function createDesktopIpcActionFollower({
|
|
|
52
55
|
onDisconnect,
|
|
53
56
|
});
|
|
54
57
|
const rawStatesByThreadId = new Map();
|
|
58
|
+
const assistantMessageTextsByThreadId = new Map();
|
|
55
59
|
const pendingRoutesByRequestId = new Map();
|
|
56
60
|
const activeThreadIds = new Set();
|
|
57
61
|
const recoveringThreadIds = new Set();
|
|
@@ -82,6 +86,7 @@ function createDesktopIpcActionFollower({
|
|
|
82
86
|
|
|
83
87
|
function stopAll() {
|
|
84
88
|
rawStatesByThreadId.clear();
|
|
89
|
+
assistantMessageTextsByThreadId.clear();
|
|
85
90
|
pendingRoutesByRequestId.clear();
|
|
86
91
|
activeThreadIds.clear();
|
|
87
92
|
recoveringThreadIds.clear();
|
|
@@ -130,11 +135,13 @@ function createDesktopIpcActionFollower({
|
|
|
130
135
|
}
|
|
131
136
|
|
|
132
137
|
rawStatesByThreadId.set(threadId, nextState);
|
|
138
|
+
syncProjectedAssistantDeltas(threadId, previousState, nextState);
|
|
133
139
|
syncProjectedActions(threadId, projectPendingDesktopActions(threadId, nextState));
|
|
134
140
|
}
|
|
135
141
|
|
|
136
142
|
function onDisconnect() {
|
|
137
143
|
rawStatesByThreadId.clear();
|
|
144
|
+
assistantMessageTextsByThreadId.clear();
|
|
138
145
|
pendingRoutesByRequestId.clear();
|
|
139
146
|
recoveringThreadIds.clear();
|
|
140
147
|
queuedChangesByThreadId.clear();
|
|
@@ -271,9 +278,34 @@ function createDesktopIpcActionFollower({
|
|
|
271
278
|
}
|
|
272
279
|
|
|
273
280
|
rawStatesByThreadId.set(threadId, nextState);
|
|
281
|
+
syncProjectedAssistantDeltas(threadId, baselineState, nextState);
|
|
274
282
|
syncProjectedActions(threadId, projectPendingDesktopActions(threadId, nextState));
|
|
275
283
|
}
|
|
276
284
|
|
|
285
|
+
function syncProjectedAssistantDeltas(threadId, previousState, nextState) {
|
|
286
|
+
const previousTexts = assistantMessageTextsByThreadId.get(threadId);
|
|
287
|
+
if (!previousTexts && !previousState) {
|
|
288
|
+
assistantMessageTextsByThreadId.set(threadId, snapshotAssistantMessageTexts(nextState));
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const notifications = projectDesktopAssistantDeltaNotifications(
|
|
293
|
+
threadId,
|
|
294
|
+
previousState,
|
|
295
|
+
nextState,
|
|
296
|
+
previousTexts || snapshotAssistantMessageTexts(previousState)
|
|
297
|
+
);
|
|
298
|
+
if (notifications.length === 0) {
|
|
299
|
+
assistantMessageTextsByThreadId.set(threadId, snapshotAssistantMessageTexts(nextState));
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
for (const notification of notifications) {
|
|
304
|
+
sendApplicationResponse(JSON.stringify(notification));
|
|
305
|
+
}
|
|
306
|
+
assistantMessageTextsByThreadId.set(threadId, snapshotAssistantMessageTexts(nextState));
|
|
307
|
+
}
|
|
308
|
+
|
|
277
309
|
return {
|
|
278
310
|
observeInbound,
|
|
279
311
|
stopAll,
|
|
@@ -483,7 +515,7 @@ function desktopFollowerPayloadForResponse(route, responseMessage) {
|
|
|
483
515
|
};
|
|
484
516
|
}
|
|
485
517
|
|
|
486
|
-
const decision =
|
|
518
|
+
const decision = desktopApprovalDecisionForResponse(route.method, responseMessage?.result);
|
|
487
519
|
if (!APPROVAL_DECISIONS.has(decision)) {
|
|
488
520
|
return null;
|
|
489
521
|
}
|
|
@@ -498,6 +530,47 @@ function desktopFollowerPayloadForResponse(route, responseMessage) {
|
|
|
498
530
|
};
|
|
499
531
|
}
|
|
500
532
|
|
|
533
|
+
function desktopApprovalDecisionForResponse(method, result) {
|
|
534
|
+
const explicitDecision = readString(result?.decision);
|
|
535
|
+
if (explicitDecision) {
|
|
536
|
+
return explicitDecision;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
if (method !== "item/permissions/requestApproval") {
|
|
540
|
+
return "";
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
// Permission approvals use a grant payload on app-server, while Desktop IPC
|
|
544
|
+
// currently exposes only decision-style follower replies.
|
|
545
|
+
return hasGrantedPermission(result?.permissions) ? "accept" : "decline";
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
function hasGrantedPermission(value) {
|
|
549
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
550
|
+
return false;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
if (Object.keys(value).length === 0) {
|
|
554
|
+
return false;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
return Object.values(value).some((entry) => {
|
|
558
|
+
if (entry == null) {
|
|
559
|
+
return false;
|
|
560
|
+
}
|
|
561
|
+
if (typeof entry === "boolean") {
|
|
562
|
+
return entry;
|
|
563
|
+
}
|
|
564
|
+
if (Array.isArray(entry)) {
|
|
565
|
+
return entry.length > 0;
|
|
566
|
+
}
|
|
567
|
+
if (typeof entry === "object") {
|
|
568
|
+
return Object.keys(entry).length > 0;
|
|
569
|
+
}
|
|
570
|
+
return true;
|
|
571
|
+
});
|
|
572
|
+
}
|
|
573
|
+
|
|
501
574
|
function projectPendingDesktopActions(threadId, conversationState) {
|
|
502
575
|
const requests = Array.isArray(conversationState?.requests) ? conversationState.requests : [];
|
|
503
576
|
return requests
|
|
@@ -507,6 +580,93 @@ function projectPendingDesktopActions(threadId, conversationState) {
|
|
|
507
580
|
.filter(Boolean);
|
|
508
581
|
}
|
|
509
582
|
|
|
583
|
+
// Desktop IPC exposes full conversation snapshots/patches, not app-server assistant delta events.
|
|
584
|
+
// Mirror only suffix growth for assistant rows so phones can render the same live text progression.
|
|
585
|
+
function projectDesktopAssistantDeltaNotifications(
|
|
586
|
+
threadId,
|
|
587
|
+
previousState,
|
|
588
|
+
nextState,
|
|
589
|
+
previousTexts = snapshotAssistantMessageTexts(previousState)
|
|
590
|
+
) {
|
|
591
|
+
const nextMessages = collectAssistantMessages(nextState);
|
|
592
|
+
const notifications = [];
|
|
593
|
+
|
|
594
|
+
for (const message of nextMessages) {
|
|
595
|
+
const previousText = previousTexts.get(message.key) || "";
|
|
596
|
+
if (!message.text || !message.text.startsWith(previousText) || message.text.length <= previousText.length) {
|
|
597
|
+
continue;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
const delta = message.text.slice(previousText.length);
|
|
601
|
+
notifications.push({
|
|
602
|
+
method: "item/agentMessage/delta",
|
|
603
|
+
params: {
|
|
604
|
+
threadId,
|
|
605
|
+
turnId: message.turnId,
|
|
606
|
+
itemId: message.itemId,
|
|
607
|
+
delta,
|
|
608
|
+
},
|
|
609
|
+
});
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
return notifications;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
function snapshotAssistantMessageTexts(conversationState) {
|
|
616
|
+
return new Map(collectAssistantMessages(conversationState).map((message) => [message.key, message.text]));
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
function collectAssistantMessages(conversationState) {
|
|
620
|
+
const turns = Array.isArray(conversationState?.turns) ? conversationState.turns : [];
|
|
621
|
+
const messages = [];
|
|
622
|
+
for (const turn of turns) {
|
|
623
|
+
const turnId = readString(turn?.id) || readString(turn?.turnId) || readString(turn?.turn_id);
|
|
624
|
+
const items = Array.isArray(turn?.items) ? turn.items : [];
|
|
625
|
+
for (const item of items) {
|
|
626
|
+
if (!isAssistantMessageItem(item)) {
|
|
627
|
+
continue;
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
const itemId = readString(item?.id) || readString(item?.itemId) || readString(item?.item_id);
|
|
631
|
+
const text = assistantMessageText(item);
|
|
632
|
+
if (!turnId || !itemId) {
|
|
633
|
+
continue;
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
messages.push({
|
|
637
|
+
key: `${turnId}:${itemId}`,
|
|
638
|
+
turnId,
|
|
639
|
+
itemId,
|
|
640
|
+
text,
|
|
641
|
+
});
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
return messages;
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
function isAssistantMessageItem(item) {
|
|
648
|
+
const type = normalizeToken(item?.type);
|
|
649
|
+
if (type === "agentmessage" || type === "assistantmessage") {
|
|
650
|
+
return true;
|
|
651
|
+
}
|
|
652
|
+
return type === "message" && normalizeToken(item?.role) === "assistant";
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
function assistantMessageText(item) {
|
|
656
|
+
const directText = readString(item?.text) || readString(item?.message);
|
|
657
|
+
if (directText) {
|
|
658
|
+
return directText;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
const content = Array.isArray(item?.content) ? item.content : [];
|
|
662
|
+
return content
|
|
663
|
+
.map((entry) => entry && typeof entry === "object" ? entry : null)
|
|
664
|
+
.filter(Boolean)
|
|
665
|
+
.map((entry) => readString(entry.text) || readString(entry?.data?.text))
|
|
666
|
+
.filter(Boolean)
|
|
667
|
+
.join("");
|
|
668
|
+
}
|
|
669
|
+
|
|
510
670
|
function projectPendingDesktopAction(threadId, request) {
|
|
511
671
|
const requestId = requestIdKey(request.id);
|
|
512
672
|
const method = readString(request.method);
|
|
@@ -529,6 +689,7 @@ function projectPendingDesktopAction(threadId, request) {
|
|
|
529
689
|
method,
|
|
530
690
|
params: {
|
|
531
691
|
...params,
|
|
692
|
+
remodexActionSource: DESKTOP_IPC_ACTION_SOURCE,
|
|
532
693
|
threadId: readString(params.threadId) || readString(params.thread_id) || threadId,
|
|
533
694
|
},
|
|
534
695
|
};
|
|
@@ -631,6 +792,10 @@ function writeFrame(socket, payload, callback) {
|
|
|
631
792
|
}
|
|
632
793
|
|
|
633
794
|
function resolveDefaultIpcSocketPath() {
|
|
795
|
+
if (process.platform === "win32") {
|
|
796
|
+
return "\\\\.\\pipe\\codex-ipc";
|
|
797
|
+
}
|
|
798
|
+
|
|
634
799
|
const uid = typeof process.getuid === "function" ? process.getuid() : 0;
|
|
635
800
|
return path.join(os.tmpdir(), "codex-ipc", `ipc-${uid}.sock`);
|
|
636
801
|
}
|
|
@@ -656,6 +821,12 @@ function readString(value) {
|
|
|
656
821
|
return typeof value === "string" && value.trim() ? value.trim() : "";
|
|
657
822
|
}
|
|
658
823
|
|
|
824
|
+
function normalizeToken(value) {
|
|
825
|
+
return typeof value === "string"
|
|
826
|
+
? value.toLowerCase().replace(/[_-\s]+/g, "")
|
|
827
|
+
: "";
|
|
828
|
+
}
|
|
829
|
+
|
|
659
830
|
function cloneJSON(value) {
|
|
660
831
|
return JSON.parse(JSON.stringify(value));
|
|
661
832
|
}
|
|
@@ -672,6 +843,7 @@ module.exports = {
|
|
|
672
843
|
applyConversationStateChange,
|
|
673
844
|
createDesktopIpcActionFollower,
|
|
674
845
|
desktopFollowerPayloadForResponse,
|
|
846
|
+
projectDesktopAssistantDeltaNotifications,
|
|
675
847
|
projectPendingDesktopActions,
|
|
676
848
|
resolveDefaultIpcSocketPath,
|
|
677
849
|
seedConversationStateFromThreadRead,
|
package/src/index.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// Depends on: ./bridge, ./secure-device-state, ./session-state, ./rollout-watch, ./macos-launch-agent
|
|
6
6
|
|
|
7
7
|
const { startBridge } = require("./bridge");
|
|
8
|
-
const { readBridgeDeviceState,
|
|
8
|
+
const { readBridgeDeviceState, resetBridgeTrustState } = require("./secure-device-state");
|
|
9
9
|
const { openLastActiveThread } = require("./session-state");
|
|
10
10
|
const { watchThreadRollout } = require("./rollout-watch");
|
|
11
11
|
const { readBridgeConfig } = require("./codex-desktop-refresher");
|
|
@@ -14,6 +14,7 @@ const {
|
|
|
14
14
|
printMacOSBridgePairingQr,
|
|
15
15
|
printMacOSBridgeServiceStatus,
|
|
16
16
|
resetMacOSBridgePairing,
|
|
17
|
+
restartMacOSBridgeService,
|
|
17
18
|
runMacOSBridgeService,
|
|
18
19
|
startMacOSBridgeService,
|
|
19
20
|
stopMacOSBridgeService,
|
|
@@ -26,11 +27,12 @@ module.exports = {
|
|
|
26
27
|
readBridgeConfig,
|
|
27
28
|
readBridgeDeviceState,
|
|
28
29
|
resetMacOSBridgePairing,
|
|
30
|
+
restartMacOSBridgeService,
|
|
29
31
|
startBridge,
|
|
30
32
|
runMacOSBridgeService,
|
|
31
33
|
startMacOSBridgeService,
|
|
32
34
|
stopMacOSBridgeService,
|
|
33
|
-
resetBridgePairing:
|
|
35
|
+
resetBridgePairing: resetBridgeTrustState,
|
|
34
36
|
openLastActiveThread,
|
|
35
37
|
watchThreadRollout,
|
|
36
38
|
};
|
|
@@ -11,7 +11,7 @@ const path = require("path");
|
|
|
11
11
|
const { startBridge } = require("./bridge");
|
|
12
12
|
const { readBridgeConfig } = require("./codex-desktop-refresher");
|
|
13
13
|
const { printQR } = require("./qr");
|
|
14
|
-
const {
|
|
14
|
+
const { resetBridgeTrustState } = require("./secure-device-state");
|
|
15
15
|
const {
|
|
16
16
|
clearBridgeStatus,
|
|
17
17
|
clearPairingSession,
|
|
@@ -122,6 +122,65 @@ async function startMacOSBridgeService({
|
|
|
122
122
|
};
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
+
// Restarts the installed LaunchAgent without rewriting relay config, useful during local bridge development.
|
|
126
|
+
async function restartMacOSBridgeService({
|
|
127
|
+
env = process.env,
|
|
128
|
+
platform = process.platform,
|
|
129
|
+
fsImpl = fs,
|
|
130
|
+
execFileSyncImpl = execFileSync,
|
|
131
|
+
osImpl = os,
|
|
132
|
+
waitForPairing = false,
|
|
133
|
+
pairingTimeoutMs = DEFAULT_PAIRING_WAIT_TIMEOUT_MS,
|
|
134
|
+
pairingPollIntervalMs = DEFAULT_PAIRING_WAIT_INTERVAL_MS,
|
|
135
|
+
...startOptions
|
|
136
|
+
} = {}) {
|
|
137
|
+
assertDarwinPlatform(platform);
|
|
138
|
+
const plistPath = resolveLaunchAgentPlistPath({ env, osImpl });
|
|
139
|
+
if (!fsImpl.existsSync(plistPath)) {
|
|
140
|
+
return startMacOSBridgeService({
|
|
141
|
+
env,
|
|
142
|
+
platform,
|
|
143
|
+
fsImpl,
|
|
144
|
+
execFileSyncImpl,
|
|
145
|
+
osImpl,
|
|
146
|
+
waitForPairing,
|
|
147
|
+
pairingTimeoutMs,
|
|
148
|
+
pairingPollIntervalMs,
|
|
149
|
+
...startOptions,
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const startedAt = Date.now();
|
|
154
|
+
if (waitForPairing) {
|
|
155
|
+
clearPairingSession({ env, fsImpl });
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
kickstartLaunchAgent({
|
|
159
|
+
env,
|
|
160
|
+
execFileSyncImpl,
|
|
161
|
+
plistPath,
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
if (waitForPairing) {
|
|
165
|
+
const pairingSession = await waitForFreshPairingSession({
|
|
166
|
+
env,
|
|
167
|
+
fsImpl,
|
|
168
|
+
startedAt,
|
|
169
|
+
timeoutMs: pairingTimeoutMs,
|
|
170
|
+
intervalMs: pairingPollIntervalMs,
|
|
171
|
+
});
|
|
172
|
+
return {
|
|
173
|
+
plistPath,
|
|
174
|
+
pairingSession,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return {
|
|
179
|
+
plistPath,
|
|
180
|
+
pairingSession: null,
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
125
184
|
function stopMacOSBridgeService({
|
|
126
185
|
env = process.env,
|
|
127
186
|
platform = process.platform,
|
|
@@ -150,7 +209,7 @@ function resetMacOSBridgePairing({
|
|
|
150
209
|
platform = process.platform,
|
|
151
210
|
execFileSyncImpl = execFileSync,
|
|
152
211
|
fsImpl = fs,
|
|
153
|
-
resetBridgePairingImpl =
|
|
212
|
+
resetBridgePairingImpl = resetBridgeTrustState,
|
|
154
213
|
} = {}) {
|
|
155
214
|
assertDarwinPlatform(platform);
|
|
156
215
|
stopMacOSBridgeService({
|
|
@@ -388,6 +447,31 @@ function restartLaunchAgent({
|
|
|
388
447
|
], { stdio: ["ignore", "ignore", "pipe"] });
|
|
389
448
|
}
|
|
390
449
|
|
|
450
|
+
function kickstartLaunchAgent({
|
|
451
|
+
env = process.env,
|
|
452
|
+
execFileSyncImpl = execFileSync,
|
|
453
|
+
plistPath,
|
|
454
|
+
} = {}) {
|
|
455
|
+
try {
|
|
456
|
+
execFileSyncImpl("launchctl", [
|
|
457
|
+
"kickstart",
|
|
458
|
+
"-k",
|
|
459
|
+
launchAgentLabelDomain(env),
|
|
460
|
+
], { stdio: ["ignore", "ignore", "pipe"] });
|
|
461
|
+
} catch {
|
|
462
|
+
execFileSyncImpl("launchctl", [
|
|
463
|
+
"bootstrap",
|
|
464
|
+
launchAgentDomain(env),
|
|
465
|
+
plistPath,
|
|
466
|
+
], { stdio: ["ignore", "ignore", "pipe"] });
|
|
467
|
+
execFileSyncImpl("launchctl", [
|
|
468
|
+
"kickstart",
|
|
469
|
+
"-k",
|
|
470
|
+
launchAgentLabelDomain(env),
|
|
471
|
+
], { stdio: ["ignore", "ignore", "pipe"] });
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
391
475
|
function bootoutLaunchAgent({
|
|
392
476
|
env = process.env,
|
|
393
477
|
execFileSyncImpl = execFileSync,
|
|
@@ -552,6 +636,7 @@ module.exports = {
|
|
|
552
636
|
printMacOSBridgeServiceStatus,
|
|
553
637
|
resetMacOSBridgePairing,
|
|
554
638
|
resolveLaunchAgentPlistPath,
|
|
639
|
+
restartMacOSBridgeService,
|
|
555
640
|
runMacOSBridgeService,
|
|
556
641
|
startMacOSBridgeService,
|
|
557
642
|
stopMacOSBridgeService,
|