@makerbi/remodex 3.2.0 → 3.3.0
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/package.json +1 -1
- package/src/bridge.js +160 -30
- package/src/codex-desktop-refresher.js +8 -8
- package/src/codex-runtime-settings.js +113 -0
- package/src/desktop-ipc-action-follower.js +269 -92
- package/src/desktop-ipc-conversation-adapter.js +56 -51
- package/src/desktop-ipc-conversation-projector.js +1 -0
- package/src/desktop-ipc-live-owner.js +189 -164
- package/src/desktop-ipc-shared.js +35 -1
- package/src/git-handler.js +20 -17
- package/src/rollout-live-mirror.js +3 -4
- package/src/runtime-provider-router.js +10 -2
- package/src/thread-activity-projector.js +694 -0
- package/src/thread-activity-store.js +325 -0
- package/src/thread-runtime-settings-store.js +60 -74
package/package.json
CHANGED
package/src/bridge.js
CHANGED
|
@@ -68,6 +68,13 @@ const {
|
|
|
68
68
|
} = require("./desktop-ipc-action-follower");
|
|
69
69
|
const { createDesktopIpcLiveOwner } = require("./desktop-ipc-live-owner");
|
|
70
70
|
const { createThreadRuntimeSettingsStore } = require("./thread-runtime-settings-store");
|
|
71
|
+
const { normalizeServiceTier, hasOwn } = require("./codex-runtime-settings");
|
|
72
|
+
const {
|
|
73
|
+
APP_SERVER_SOURCE,
|
|
74
|
+
DESKTOP_IPC_SOURCE,
|
|
75
|
+
createThreadActivityProjector,
|
|
76
|
+
} = require("./thread-activity-projector");
|
|
77
|
+
const { createThreadActivityStore } = require("./thread-activity-store");
|
|
71
78
|
const { createThreadListProvenanceEnricher } = require("./thread-list-provenance");
|
|
72
79
|
const { createWorktreeOriginEnricher } = require("./worktree-origin");
|
|
73
80
|
const { forEachThreadRowInResponse } = require("./thread-row-enrichment");
|
|
@@ -600,6 +607,7 @@ function annotateTurnStateProbeWithMirrorActiveTurn(request, response, getMirror
|
|
|
600
607
|
|
|
601
608
|
function canonicalThreadTurnsListRequest(request) {
|
|
602
609
|
const params = { ...(request?.params || {}) };
|
|
610
|
+
params.itemsView ??= params.remodexTurnStateOnly === true ? "summary" : "full";
|
|
603
611
|
delete params.remodexRequireCanonical;
|
|
604
612
|
delete params.remodexTurnStateOnly;
|
|
605
613
|
if (params.cursor === JSONL_OLDER_HANDOFF_CURSOR || threadTurnsListHandoffDescriptor(params.cursor)) {
|
|
@@ -761,9 +769,8 @@ function startBridge({
|
|
|
761
769
|
const relaySessionUrl = `${relayBaseUrl}/${sessionId}`;
|
|
762
770
|
const notificationSecret = randomBytes(24).toString("hex");
|
|
763
771
|
const desktopRefresher = new CodexDesktopRefresher({
|
|
764
|
-
//
|
|
765
|
-
//
|
|
766
|
-
// activation; refreshEnabled still controls the legacy reload workaround.
|
|
772
|
+
// Opening a thread manually establishes its IPC subscription. Automatic
|
|
773
|
+
// navigation and the legacy refresh workaround both require an opt-in.
|
|
767
774
|
enabled: config.refreshEnabled || config.desktopAutoFollowEnabled === true,
|
|
768
775
|
// With IPC live sync streaming content, deep-link refreshes are only needed
|
|
769
776
|
// to navigate Desktop onto the phone-driven thread, not to reload content.
|
|
@@ -806,9 +813,23 @@ function startBridge({
|
|
|
806
813
|
const jsonlTurnsListRolloutCacheByThread = new Map();
|
|
807
814
|
const jsonlTurnsListRolloutMissCacheByThread = new Map();
|
|
808
815
|
const threadTurnsListFastPageCoordinator = createThreadTurnsListFastPageCoordinator();
|
|
809
|
-
const threadRuntimeSettingsStore = createThreadRuntimeSettingsStore(
|
|
816
|
+
const threadRuntimeSettingsStore = createThreadRuntimeSettingsStore({
|
|
817
|
+
onChange(threadId, runtimeSettings) {
|
|
818
|
+
sendApplicationResponse(JSON.stringify({
|
|
819
|
+
method: "remodex/runtimeSettings/updated",
|
|
820
|
+
params: { threadId, runtimeSettings },
|
|
821
|
+
}));
|
|
822
|
+
},
|
|
823
|
+
});
|
|
810
824
|
const threadListProvenanceEnricher = createThreadListProvenanceEnricher();
|
|
811
825
|
const worktreeOriginEnricher = createWorktreeOriginEnricher();
|
|
826
|
+
const activityProjector = createThreadActivityProjector();
|
|
827
|
+
const activityStore = createThreadActivityStore({
|
|
828
|
+
sendApplicationResponse,
|
|
829
|
+
onEvict(threadId, source) {
|
|
830
|
+
activityProjector.forget(threadId, source);
|
|
831
|
+
},
|
|
832
|
+
});
|
|
812
833
|
const trackedForwardedRequestMethods = new Set([
|
|
813
834
|
"account/login/start",
|
|
814
835
|
"account/login/cancel",
|
|
@@ -838,6 +859,7 @@ function startBridge({
|
|
|
838
859
|
sendRelayRegistrationUpdate(nextDeviceState);
|
|
839
860
|
},
|
|
840
861
|
onSecureSessionReady(session) {
|
|
862
|
+
activityStore.resetSubscriber();
|
|
841
863
|
activePhoneSummary = buildActivePhoneSummary(session, deviceState);
|
|
842
864
|
const lastPublishedBridgeStatus = bridgeStatusPublisher.latest();
|
|
843
865
|
if (lastPublishedBridgeStatus) {
|
|
@@ -864,9 +886,10 @@ function startBridge({
|
|
|
864
886
|
// authoritative, but yields an active cache that stopped broadcasting;
|
|
865
887
|
// a later Desktop snapshot is announced as a new source epoch so the
|
|
866
888
|
// phone performs canonical repair instead of mixing both mirrors.
|
|
867
|
-
shouldSuppressThread: (threadId) => shouldSuppressRolloutMirrorForThread(
|
|
889
|
+
shouldSuppressThread: (threadId, context) => shouldSuppressRolloutMirrorForThread(
|
|
868
890
|
threadId,
|
|
869
|
-
{ desktopIpcActionFollower, desktopIpcLiveOwner }
|
|
891
|
+
{ desktopIpcActionFollower, desktopIpcLiveOwner },
|
|
892
|
+
context
|
|
870
893
|
),
|
|
871
894
|
})
|
|
872
895
|
: null;
|
|
@@ -877,6 +900,7 @@ function startBridge({
|
|
|
877
900
|
await sendCodexRequest("thread/read", buildCompleteThreadReadParams(threadId))
|
|
878
901
|
),
|
|
879
902
|
forwardToLocalCodex: (rawMessage) => {
|
|
903
|
+
if (handleLocalRuntimeSettingsRequest(parseBridgeMessage(rawMessage))) return;
|
|
880
904
|
observeDesktopIpcLiveOwnerInbound(rawMessage);
|
|
881
905
|
forwardInboundRequestToCodex(rawMessage);
|
|
882
906
|
},
|
|
@@ -890,6 +914,7 @@ function startBridge({
|
|
|
890
914
|
onFollowerStateChanged(threadId, following) {
|
|
891
915
|
desktopRefresher.handleFollowerStateChanged(threadId, following);
|
|
892
916
|
},
|
|
917
|
+
onActivityObservation: handleDesktopActivityObservation,
|
|
893
918
|
})
|
|
894
919
|
: null;
|
|
895
920
|
const desktopIpcLiveOwner = !config.codexEndpoint
|
|
@@ -1003,6 +1028,7 @@ function startBridge({
|
|
|
1003
1028
|
bridgeStatusPublisher.stopHeartbeat();
|
|
1004
1029
|
runtimeProviderRouter.shutdown();
|
|
1005
1030
|
stopContextUsageWatcher();
|
|
1031
|
+
activityStore.dispose();
|
|
1006
1032
|
rolloutLiveMirror?.stopAll();
|
|
1007
1033
|
desktopIpcActionFollower?.stopAll();
|
|
1008
1034
|
desktopIpcLiveOwner?.stopAll();
|
|
@@ -1196,6 +1222,14 @@ function startBridge({
|
|
|
1196
1222
|
// Streaming deltas make this the hottest path in the bridge: parse the
|
|
1197
1223
|
// envelope once and share the read-only object with every observer.
|
|
1198
1224
|
const parsedMessage = parseBridgeMessage(message);
|
|
1225
|
+
if (parsedMessage?.result && forwardedInitializeRequestIds.has(String(parsedMessage.id))) {
|
|
1226
|
+
parsedMessage.result.remodexRuntimeSettingsVersion = 2;
|
|
1227
|
+
message = JSON.stringify(parsedMessage);
|
|
1228
|
+
}
|
|
1229
|
+
if (parsedMessage?.method === "thread/settings/updated"
|
|
1230
|
+
&& parsedMessage.params?.threadSettings) {
|
|
1231
|
+
threadRuntimeSettingsStore.observe(parsedMessage.params.threadId, parsedMessage.params.threadSettings);
|
|
1232
|
+
}
|
|
1199
1233
|
if (handleBridgeManagedCodexResponse(message, parsedMessage)) {
|
|
1200
1234
|
return;
|
|
1201
1235
|
}
|
|
@@ -1203,6 +1237,7 @@ function startBridge({
|
|
|
1203
1237
|
trackCodexHandshakeState(message, parsedMessage);
|
|
1204
1238
|
desktopRefresher.handleOutbound(message, parsedMessage);
|
|
1205
1239
|
desktopIpcLiveOwner?.observeOutbound(message, parsedMessage);
|
|
1240
|
+
observeAppServerActivity(parsedMessage);
|
|
1206
1241
|
pushNotificationTracker.handleOutbound(message, parsedMessage);
|
|
1207
1242
|
rememberThreadFromMessage("codex", message, parsedMessage);
|
|
1208
1243
|
secureTransport.queueOutboundApplicationMessage(
|
|
@@ -1212,6 +1247,7 @@ function startBridge({
|
|
|
1212
1247
|
});
|
|
1213
1248
|
|
|
1214
1249
|
codex.onClose(() => {
|
|
1250
|
+
activityStore.markSourceStale(APP_SERVER_SOURCE);
|
|
1215
1251
|
const wasShuttingDown = isShuttingDown;
|
|
1216
1252
|
clearRelayWatchdog();
|
|
1217
1253
|
bridgeStatusPublisher.stopHeartbeat();
|
|
@@ -1243,7 +1279,11 @@ function startBridge({
|
|
|
1243
1279
|
|
|
1244
1280
|
// Routes decrypted app payloads through the same bridge handlers as before.
|
|
1245
1281
|
function handleApplicationMessage(rawMessage) {
|
|
1282
|
+
rawMessage = normalizePhoneRuntimeRequest(rawMessage);
|
|
1246
1283
|
const parsedMessage = parseBridgeMessage(rawMessage);
|
|
1284
|
+
if (activityStore.handleRequest(parsedMessage)) {
|
|
1285
|
+
return;
|
|
1286
|
+
}
|
|
1247
1287
|
if (handleBridgeManagedHandshakeMessage(rawMessage, sendApplicationResponse, parsedMessage)) {
|
|
1248
1288
|
return;
|
|
1249
1289
|
}
|
|
@@ -1277,9 +1317,17 @@ function startBridge({
|
|
|
1277
1317
|
})) {
|
|
1278
1318
|
return;
|
|
1279
1319
|
}
|
|
1320
|
+
// Provider-owned renames must reach their runtime instead of the Codex-only
|
|
1321
|
+
// catalog persistence path in the local git/thread handler.
|
|
1322
|
+
if (parsedMessage?.method === "thread/name/set"
|
|
1323
|
+
&& runtimeProviderRouter.handleApplicationMessage(rawMessage, {
|
|
1324
|
+
sendResponse: sendApplicationResponse,
|
|
1325
|
+
})) {
|
|
1326
|
+
return;
|
|
1327
|
+
}
|
|
1280
1328
|
if (handleGitRequest(rawMessage, sendApplicationResponse, {
|
|
1281
1329
|
codexAppPath: config.codexAppPath,
|
|
1282
|
-
|
|
1330
|
+
sendCodexRequest,
|
|
1283
1331
|
})) {
|
|
1284
1332
|
return;
|
|
1285
1333
|
}
|
|
@@ -1298,6 +1346,7 @@ function startBridge({
|
|
|
1298
1346
|
})) {
|
|
1299
1347
|
return;
|
|
1300
1348
|
}
|
|
1349
|
+
if (handleLocalRuntimeSettingsRequest(parsedMessage)) return;
|
|
1301
1350
|
observeDesktopIpcLiveOwnerInbound(rawMessage, parsedMessage);
|
|
1302
1351
|
if (handleBridgeManagedThreadTurnsListRequest(rawMessage, sendApplicationResponse)) {
|
|
1303
1352
|
return;
|
|
@@ -1305,6 +1354,24 @@ function startBridge({
|
|
|
1305
1354
|
forwardInboundRequestToCodex(rawMessage);
|
|
1306
1355
|
}
|
|
1307
1356
|
|
|
1357
|
+
function handleLocalRuntimeSettingsRequest(parsedMessage) {
|
|
1358
|
+
if (parsedMessage?.method === "thread/settings/update" && parsedMessage.id != null) {
|
|
1359
|
+
const { threadId, ...settings } = parsedMessage.params || {};
|
|
1360
|
+
const update = desktopIpcLiveOwner?.updateThreadSettings
|
|
1361
|
+
? desktopIpcLiveOwner.updateThreadSettings(threadId, settings)
|
|
1362
|
+
: sendCodexRequest("thread/settings/update", { threadId, ...settings }).then(() => ({
|
|
1363
|
+
runtimeSettings: threadRuntimeSettingsStore.commit(threadId, settings, { source: "phone" }),
|
|
1364
|
+
}));
|
|
1365
|
+
Promise.resolve(update).then((result) => {
|
|
1366
|
+
sendApplicationResponse(JSON.stringify({ id: parsedMessage.id, result }));
|
|
1367
|
+
}).catch((error) => {
|
|
1368
|
+
sendApplicationResponse(createJsonRpcErrorResponse(parsedMessage.id, error, "runtime_settings_update_failed"));
|
|
1369
|
+
});
|
|
1370
|
+
return true;
|
|
1371
|
+
}
|
|
1372
|
+
return false;
|
|
1373
|
+
}
|
|
1374
|
+
|
|
1308
1375
|
function forwardInboundRequestToCodex(rawMessage) {
|
|
1309
1376
|
const codexRequest = stripRuntimeProviderFieldsForCodex(
|
|
1310
1377
|
normalizeTurnStartForCodex(rawMessage)
|
|
@@ -1358,6 +1425,58 @@ function startBridge({
|
|
|
1358
1425
|
}
|
|
1359
1426
|
}
|
|
1360
1427
|
|
|
1428
|
+
function observeAppServerActivity(message) {
|
|
1429
|
+
if (isShuttingDown) {
|
|
1430
|
+
return;
|
|
1431
|
+
}
|
|
1432
|
+
const projection = activityProjector.observeAppServer(message);
|
|
1433
|
+
if (projection?.entry) {
|
|
1434
|
+
const entry = projection.entry;
|
|
1435
|
+
const previous = activityStore.get(entry.threadId);
|
|
1436
|
+
if (previous?.source === DESKTOP_IPC_SOURCE
|
|
1437
|
+
&& !desktopIpcLiveOwner?.isThreadOwned(entry.threadId)) {
|
|
1438
|
+
// Catalog changes do not transfer runtime ownership to this app-server.
|
|
1439
|
+
if (message.method === "thread/name/updated" || message.method === "thread/started") {
|
|
1440
|
+
activityStore.upsert({
|
|
1441
|
+
...previous,
|
|
1442
|
+
...(entry.title ? { title: entry.title } : {}),
|
|
1443
|
+
...(entry.cwd ? { cwd: entry.cwd } : {}),
|
|
1444
|
+
});
|
|
1445
|
+
}
|
|
1446
|
+
return;
|
|
1447
|
+
}
|
|
1448
|
+
activityStore.upsert({
|
|
1449
|
+
...(previous?.title ? { title: previous.title } : {}),
|
|
1450
|
+
...(previous?.cwd ? { cwd: previous.cwd } : {}),
|
|
1451
|
+
...entry,
|
|
1452
|
+
});
|
|
1453
|
+
} else if (projection?.removedThreadId) {
|
|
1454
|
+
activityStore.remove(projection.removedThreadId);
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
function handleDesktopActivityObservation(observation) {
|
|
1459
|
+
if (isShuttingDown) {
|
|
1460
|
+
return;
|
|
1461
|
+
}
|
|
1462
|
+
if (observation?.type === "state") {
|
|
1463
|
+
const entry = activityProjector.projectDesktopState(
|
|
1464
|
+
observation.threadId,
|
|
1465
|
+
observation.state,
|
|
1466
|
+
observation.sourceGeneration
|
|
1467
|
+
);
|
|
1468
|
+
activityStore.upsert(entry);
|
|
1469
|
+
return;
|
|
1470
|
+
}
|
|
1471
|
+
if (observation?.type === "disconnected") {
|
|
1472
|
+
activityStore.markSourceStale(DESKTOP_IPC_SOURCE, observation.sourceGeneration);
|
|
1473
|
+
return;
|
|
1474
|
+
}
|
|
1475
|
+
if (observation?.type === "removed") {
|
|
1476
|
+
activityStore.remove(observation.threadId, { source: DESKTOP_IPC_SOURCE });
|
|
1477
|
+
}
|
|
1478
|
+
}
|
|
1479
|
+
|
|
1361
1480
|
// Encrypts bridge-generated responses instead of letting the relay see plaintext.
|
|
1362
1481
|
function sendApplicationResponse(rawMessage) {
|
|
1363
1482
|
secureTransport.queueOutboundApplicationMessage(
|
|
@@ -1374,25 +1493,6 @@ function startBridge({
|
|
|
1374
1493
|
sendApplicationResponse(rawMessage);
|
|
1375
1494
|
}
|
|
1376
1495
|
|
|
1377
|
-
// Mirrors accepted local renames back to the phone using the existing push-event shape.
|
|
1378
|
-
function sendThreadNameUpdatedNotification(result) {
|
|
1379
|
-
const threadId = readString(result?.threadId || result?.thread_id);
|
|
1380
|
-
const name = readString(result?.name || result?.title);
|
|
1381
|
-
if (!threadId || !name) {
|
|
1382
|
-
return;
|
|
1383
|
-
}
|
|
1384
|
-
|
|
1385
|
-
sendApplicationResponse(JSON.stringify({
|
|
1386
|
-
method: "thread/name/updated",
|
|
1387
|
-
params: {
|
|
1388
|
-
threadId,
|
|
1389
|
-
thread_id: threadId,
|
|
1390
|
-
name,
|
|
1391
|
-
title: name,
|
|
1392
|
-
},
|
|
1393
|
-
}));
|
|
1394
|
-
}
|
|
1395
|
-
|
|
1396
1496
|
function handleBridgeManagedThreadTurnsListRequest(rawMessage, sendResponse = sendApplicationResponse) {
|
|
1397
1497
|
const request = parseAdaptiveThreadTurnsListRequest(rawMessage);
|
|
1398
1498
|
if (!request) {
|
|
@@ -1713,6 +1813,10 @@ function startBridge({
|
|
|
1713
1813
|
if (relaySanitizedRequestMethods.has(method)) {
|
|
1714
1814
|
const trackedRequest = {
|
|
1715
1815
|
method,
|
|
1816
|
+
isActiveThreadCatalog: method === "thread/list"
|
|
1817
|
+
&& parsed.params?.archived !== true
|
|
1818
|
+
&& !parsed.params?.cursor,
|
|
1819
|
+
threadListLimit: method === "thread/list" ? parsed.params?.limit : undefined,
|
|
1716
1820
|
threadId: method === "thread/turns/list" || method === "thread/read" || method === "thread/resume"
|
|
1717
1821
|
? threadIdFromRequestParams(parsed.params)
|
|
1718
1822
|
: "",
|
|
@@ -1771,6 +1875,9 @@ function startBridge({
|
|
|
1771
1875
|
worktreeOriginEnricher.attachToThread(thread);
|
|
1772
1876
|
});
|
|
1773
1877
|
normalizedMessage = JSON.stringify(parsed);
|
|
1878
|
+
if (trackedRequest.isActiveThreadCatalog) {
|
|
1879
|
+
desktopIpcActionFollower?.observeThreadListResponse(parsed.result, { limit: trackedRequest.threadListLimit });
|
|
1880
|
+
}
|
|
1774
1881
|
}
|
|
1775
1882
|
|
|
1776
1883
|
return sanitizeThreadHistoryImagesForRelay(normalizedMessage, trackedRequest.method, trackedRequest);
|
|
@@ -1997,6 +2104,7 @@ function startBridge({
|
|
|
1997
2104
|
id: parsed.id,
|
|
1998
2105
|
result: {
|
|
1999
2106
|
bridgeManaged: true,
|
|
2107
|
+
remodexRuntimeSettingsVersion: 2,
|
|
2000
2108
|
},
|
|
2001
2109
|
}));
|
|
2002
2110
|
return true;
|
|
@@ -2107,8 +2215,10 @@ function startBridge({
|
|
|
2107
2215
|
|
|
2108
2216
|
// Runs bridge-private JSON-RPC calls against the local app-server so token-bearing responses
|
|
2109
2217
|
// can power bridge features like transcription without ever reaching the phone.
|
|
2110
|
-
function sendCodexRequest(method, params) {
|
|
2111
|
-
const requestId =
|
|
2218
|
+
function sendCodexRequest(method, params, callerRequestId = null) {
|
|
2219
|
+
const requestId = callerRequestId == null
|
|
2220
|
+
? `bridge-managed-${randomBytes(12).toString("hex")}`
|
|
2221
|
+
: String(callerRequestId);
|
|
2112
2222
|
const payload = JSON.stringify({
|
|
2113
2223
|
id: requestId,
|
|
2114
2224
|
method,
|
|
@@ -2510,6 +2620,25 @@ function disableUnsupportedReasoningSummaryForTurnStart(rawMessage) {
|
|
|
2510
2620
|
});
|
|
2511
2621
|
}
|
|
2512
2622
|
|
|
2623
|
+
// Upgrade the old phone wire convention once at ingress. Internally an omitted
|
|
2624
|
+
// speed always inherits; modern clients distinguish that from explicit Standard.
|
|
2625
|
+
function normalizePhoneRuntimeRequest(rawMessage) {
|
|
2626
|
+
const parsed = parseBridgeJSON(rawMessage);
|
|
2627
|
+
if (!parsed || !["turn/start", "thread/start", "thread/settings/update"].includes(parsed.method)
|
|
2628
|
+
|| !parsed.params || typeof parsed.params !== "object") return rawMessage;
|
|
2629
|
+
const params = { ...parsed.params };
|
|
2630
|
+
if (parsed.method === "turn/start" && params.remodexRuntimeSettingsVersion !== 2
|
|
2631
|
+
&& !hasOwn(params, "serviceTier") && !hasOwn(params, "service_tier")) {
|
|
2632
|
+
params.serviceTier = "default";
|
|
2633
|
+
}
|
|
2634
|
+
delete params.remodexRuntimeSettingsVersion;
|
|
2635
|
+
if (hasOwn(params, "serviceTier") && params.serviceTier != null) {
|
|
2636
|
+
params.serviceTier = normalizeServiceTier(params.serviceTier)
|
|
2637
|
+
?? (parsed.method === "thread/settings/update" ? null : "default");
|
|
2638
|
+
}
|
|
2639
|
+
return JSON.stringify({ ...parsed, params });
|
|
2640
|
+
}
|
|
2641
|
+
|
|
2513
2642
|
function normalizeTurnStartParamsForCodex(params) {
|
|
2514
2643
|
const normalizedRawMessage = normalizeTurnStartForCodex(JSON.stringify({
|
|
2515
2644
|
method: "turn/start",
|
|
@@ -5113,12 +5242,12 @@ function persistBridgePreferences(
|
|
|
5113
5242
|
function shouldSuppressRolloutMirrorForThread(
|
|
5114
5243
|
threadId,
|
|
5115
5244
|
{ desktopIpcActionFollower = null, desktopIpcLiveOwner = null } = {},
|
|
5116
|
-
|
|
5245
|
+
context = {}
|
|
5117
5246
|
) {
|
|
5118
5247
|
// Desktop ownership is an expiring live lease, not a permanent boolean. A
|
|
5119
5248
|
// stale IPC snapshot used to mute an actively growing rollout forever.
|
|
5120
5249
|
const followerIsFresh = typeof desktopIpcActionFollower?.hasFreshLiveThreadState === "function"
|
|
5121
|
-
? desktopIpcActionFollower.hasFreshLiveThreadState(threadId,
|
|
5250
|
+
? desktopIpcActionFollower.hasFreshLiveThreadState(threadId, context)
|
|
5122
5251
|
: desktopIpcActionFollower?.hasLiveThreadState(threadId);
|
|
5123
5252
|
const ownerIsFresh = typeof desktopIpcLiveOwner?.isFreshThreadOwned === "function"
|
|
5124
5253
|
? desktopIpcLiveOwner.isFreshThreadOwned(threadId)
|
|
@@ -5141,6 +5270,7 @@ module.exports = {
|
|
|
5141
5270
|
isContextualUserItemNotification,
|
|
5142
5271
|
maybeMergeLatestJsonlTurnIntoTurnsListResponse,
|
|
5143
5272
|
normalizeTurnStartForCodex,
|
|
5273
|
+
normalizePhoneRuntimeRequest,
|
|
5144
5274
|
normalizeRelayBoundJsonRpcMessage,
|
|
5145
5275
|
persistBridgePreferences,
|
|
5146
5276
|
resolveJsonlTurnsListRolloutPathForFallback,
|
|
@@ -101,6 +101,9 @@ class CodexDesktopRefresher {
|
|
|
101
101
|
}
|
|
102
102
|
|
|
103
103
|
handleInbound(rawMessage, parsedMessage = null) {
|
|
104
|
+
if (!this.canRefresh()) {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
104
107
|
const parsed = parsedMessage ?? safeParseJSON(rawMessage);
|
|
105
108
|
if (!parsed) {
|
|
106
109
|
return;
|
|
@@ -147,6 +150,9 @@ class CodexDesktopRefresher {
|
|
|
147
150
|
}
|
|
148
151
|
|
|
149
152
|
handleOutbound(rawMessage, parsedMessage = null) {
|
|
153
|
+
if (!this.canRefresh()) {
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
150
156
|
const parsed = parsedMessage ?? safeParseJSON(rawMessage);
|
|
151
157
|
if (!parsed) {
|
|
152
158
|
return;
|
|
@@ -744,11 +750,6 @@ function readBridgeConfig({
|
|
|
744
750
|
const desktopIpcLiveSyncEnabled = explicitDesktopIpcLiveSyncEnabled == null
|
|
745
751
|
? true
|
|
746
752
|
: explicitDesktopIpcLiveSyncEnabled;
|
|
747
|
-
const defaultDesktopAutoFollowEnabled = (
|
|
748
|
-
platform === "darwin"
|
|
749
|
-
&& !codexEndpoint
|
|
750
|
-
&& desktopIpcLiveSyncEnabled
|
|
751
|
-
);
|
|
752
753
|
return {
|
|
753
754
|
relayUrl,
|
|
754
755
|
relayAccessToken,
|
|
@@ -774,9 +775,8 @@ function readBridgeConfig({
|
|
|
774
775
|
codexEndpoint,
|
|
775
776
|
desktopIpcSocketPath: readFirstDefinedEnv(["REMODEX_DESKTOP_IPC_SOCKET"], "", env),
|
|
776
777
|
desktopIpcLiveSyncEnabled,
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
: explicitDesktopAutoFollowEnabled,
|
|
778
|
+
// Live sync must not navigate or activate Desktop without an explicit opt-in.
|
|
779
|
+
desktopAutoFollowEnabled: explicitDesktopAutoFollowEnabled === true,
|
|
780
780
|
desktopIpcSnapshotDebounceMs: parseIntegerEnv(
|
|
781
781
|
readFirstDefinedEnv(["REMODEX_DESKTOP_IPC_SNAPSHOT_DEBOUNCE_MS"], "75", env),
|
|
782
782
|
75
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
// Canonical runtime settings shared by the app-server and Desktop IPC adapters.
|
|
2
|
+
// Missing fields inherit; null clears speed, while null effort inherits unless
|
|
3
|
+
// supplied in collaboration settings or an authoritative snapshot. Turn-only overrides
|
|
4
|
+
// are deliberately excluded from the persistent next-turn settings.
|
|
5
|
+
const hasOwn = (value, key) => Object.prototype.hasOwnProperty.call(value || {}, key);
|
|
6
|
+
const string = (value) => typeof value === "string" ? value.trim() : "";
|
|
7
|
+
const THREAD_SETTINGS_UPDATE_KEYS = [
|
|
8
|
+
"approvalPolicy", "approvalsReviewer", "collaborationMode", "cwd", "effort", "model",
|
|
9
|
+
"multiAgentMode", "permissions", "personality", "sandboxPolicy", "serviceTier", "summary",
|
|
10
|
+
];
|
|
11
|
+
|
|
12
|
+
function normalizeThreadSettingsUpdate(params, options = {}) {
|
|
13
|
+
const settings = Object.fromEntries(THREAD_SETTINGS_UPDATE_KEYS.filter((key) => hasOwn(params, key))
|
|
14
|
+
.map((key) => [key, structuredClone(params[key])]));
|
|
15
|
+
const patch = runtimeSettingsPatch(params, options);
|
|
16
|
+
if (settings.effort == null && !options.authoritative) delete settings.effort;
|
|
17
|
+
if (hasOwn(patch, "serviceTier")) settings.serviceTier = patch.serviceTier;
|
|
18
|
+
if (settings.collaborationMode?.settings) {
|
|
19
|
+
if (patch.model) settings.collaborationMode.settings.model = patch.model;
|
|
20
|
+
if (hasOwn(patch, "reasoningEffort")) settings.collaborationMode.settings.reasoning_effort = patch.reasoningEffort;
|
|
21
|
+
}
|
|
22
|
+
return settings;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Confirmations may be partial (for example the first edit only selects Fast).
|
|
26
|
+
// Preserve omission instead of manufacturing resets for unknown fields.
|
|
27
|
+
function threadSettingsFromRuntimeSettings(settings) {
|
|
28
|
+
const patch = runtimeSettingsPatch(settings);
|
|
29
|
+
const result = { ...patch };
|
|
30
|
+
if (hasOwn(patch, "reasoningEffort")) {
|
|
31
|
+
result.effort = patch.reasoningEffort;
|
|
32
|
+
delete result.reasoningEffort;
|
|
33
|
+
}
|
|
34
|
+
return result;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function normalizeServiceTier(value) {
|
|
38
|
+
const tier = string(value);
|
|
39
|
+
if (!tier || tier === "default") return null;
|
|
40
|
+
return tier === "fast" ? "priority" : tier;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function runtimeSettingsPatch(params = {}, { authoritative = false } = {}) {
|
|
44
|
+
const nested = params.collaborationMode?.settings || params.collaboration_mode?.settings || {};
|
|
45
|
+
const patch = {};
|
|
46
|
+
if (string(params.model) || string(nested.model)) patch.model = string(params.model) || string(nested.model);
|
|
47
|
+
for (const [object, key] of [[params, "effort"], [params, "reasoningEffort"], [nested, "reasoning_effort"], [nested, "reasoningEffort"]]) {
|
|
48
|
+
if (hasOwn(object, key) && (key !== "effort" || object[key] != null || authoritative)) {
|
|
49
|
+
patch.reasoningEffort = string(object[key]) || null;
|
|
50
|
+
break;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
if (hasOwn(params, "serviceTier") || hasOwn(params, "service_tier")) {
|
|
54
|
+
patch.serviceTier = normalizeServiceTier(hasOwn(params, "serviceTier") ? params.serviceTier : params.service_tier);
|
|
55
|
+
}
|
|
56
|
+
return patch;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function runtimeSettingsFromConversation(conversation = {}) {
|
|
60
|
+
const settings = conversation.latestThreadSettings || {};
|
|
61
|
+
const fallback = {};
|
|
62
|
+
if (string(conversation.latestModel)) fallback.model = conversation.latestModel;
|
|
63
|
+
if (hasOwn(conversation, "latestReasoningEffort")) fallback.reasoningEffort = conversation.latestReasoningEffort;
|
|
64
|
+
if (hasOwn(conversation, "latestServiceTier")) fallback.serviceTier = conversation.latestServiceTier;
|
|
65
|
+
return { ...runtimeSettingsPatch(fallback), ...runtimeSettingsPatch(settings, { authoritative: true }) };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function applyRuntimeSettingsToConversation(conversation, params, options = {}) {
|
|
69
|
+
if (!conversation) return;
|
|
70
|
+
const patch = runtimeSettingsPatch(params, options);
|
|
71
|
+
const settings = { ...(conversation.latestThreadSettings || {}), ...normalizeThreadSettingsUpdate(params, options) };
|
|
72
|
+
if (hasOwn(patch, "model")) conversation.latestModel = settings.model = patch.model;
|
|
73
|
+
if (hasOwn(patch, "reasoningEffort")) conversation.latestReasoningEffort = settings.effort = patch.reasoningEffort;
|
|
74
|
+
if (hasOwn(patch, "serviceTier")) conversation.latestServiceTier = settings.serviceTier = patch.serviceTier;
|
|
75
|
+
const mode = params.collaborationMode || conversation.latestCollaborationMode;
|
|
76
|
+
if (mode || patch.model) {
|
|
77
|
+
conversation.latestCollaborationMode = {
|
|
78
|
+
...(mode || { mode: "default" }),
|
|
79
|
+
settings: {
|
|
80
|
+
developer_instructions: null,
|
|
81
|
+
...(mode?.settings || {}),
|
|
82
|
+
...(patch.model ? { model: patch.model } : {}),
|
|
83
|
+
...(hasOwn(patch, "reasoningEffort") ? { reasoning_effort: patch.reasoningEffort } : {}),
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
settings.collaborationMode = conversation.latestCollaborationMode;
|
|
87
|
+
}
|
|
88
|
+
conversation.latestThreadSettings = settings;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Ordering is scoped to one runtime owner and task. Failures don't poison the
|
|
92
|
+
// queue, and the next mutation always waits for the preceding acknowledgement.
|
|
93
|
+
function createThreadMutationQueue() {
|
|
94
|
+
const pending = new Map();
|
|
95
|
+
return function enqueue(threadId, operation) {
|
|
96
|
+
const result = (pending.get(threadId) || Promise.resolve()).catch(() => {}).then(operation);
|
|
97
|
+
pending.set(threadId, result);
|
|
98
|
+
result.finally(() => { if (pending.get(threadId) === result) pending.delete(threadId); }).catch(() => {});
|
|
99
|
+
return result;
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
module.exports = {
|
|
104
|
+
applyRuntimeSettingsToConversation,
|
|
105
|
+
createThreadMutationQueue,
|
|
106
|
+
hasOwn,
|
|
107
|
+
normalizeServiceTier,
|
|
108
|
+
normalizeThreadSettingsUpdate,
|
|
109
|
+
THREAD_SETTINGS_UPDATE_KEYS,
|
|
110
|
+
threadSettingsFromRuntimeSettings,
|
|
111
|
+
runtimeSettingsFromConversation,
|
|
112
|
+
runtimeSettingsPatch,
|
|
113
|
+
};
|