@makerbi/remodex 1.5.8 → 2.0.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.
@@ -7,10 +7,12 @@
7
7
  const net = require("net");
8
8
  const os = require("os");
9
9
  const path = require("path");
10
+ const { buildApplyPatchFileChangeItem } = require("./apply-patch-changes");
10
11
 
11
12
  const FRAME_HEADER_BYTES = 4;
12
13
  const MAX_FRAME_BYTES = 256 * 1024 * 1024;
13
14
  const REQUEST_TIMEOUT_MS = 10_000;
15
+ const TURN_COMPLETION_IDLE_MS = 3_500;
14
16
  const DESKTOP_IPC_ACTION_SOURCE = "desktop-ipc-action-follower";
15
17
  const DESKTOP_RESUME_METHODS = new Set(["thread/read", "thread/resume"]);
16
18
  const ACTION_METHODS = new Set([
@@ -44,6 +46,9 @@ function createDesktopIpcActionFollower({
44
46
  netModule = net,
45
47
  now = () => Date.now(),
46
48
  requestTimeoutMs = REQUEST_TIMEOUT_MS,
49
+ turnCompletionIdleMs = TURN_COMPLETION_IDLE_MS,
50
+ setTimeoutFn = setTimeout,
51
+ clearTimeoutFn = clearTimeout,
47
52
  } = {}) {
48
53
  const ipc = createDesktopIpcClient({
49
54
  socketPath,
@@ -56,6 +61,9 @@ function createDesktopIpcActionFollower({
56
61
  });
57
62
  const rawStatesByThreadId = new Map();
58
63
  const assistantMessageTextsByThreadId = new Map();
64
+ const mirroredActivityKeysByThreadId = new Map();
65
+ const mirroredUserMessageKeysByThreadId = new Map();
66
+ const activeDesktopTurnsByThreadId = new Map();
59
67
  const pendingRoutesByRequestId = new Map();
60
68
  const activeThreadIds = new Set();
61
69
  const recoveringThreadIds = new Set();
@@ -87,6 +95,9 @@ function createDesktopIpcActionFollower({
87
95
  function stopAll() {
88
96
  rawStatesByThreadId.clear();
89
97
  assistantMessageTextsByThreadId.clear();
98
+ mirroredActivityKeysByThreadId.clear();
99
+ mirroredUserMessageKeysByThreadId.clear();
100
+ clearAllDesktopTurnCompletionTimers();
90
101
  pendingRoutesByRequestId.clear();
91
102
  activeThreadIds.clear();
92
103
  recoveringThreadIds.clear();
@@ -135,13 +146,16 @@ function createDesktopIpcActionFollower({
135
146
  }
136
147
 
137
148
  rawStatesByThreadId.set(threadId, nextState);
138
- syncProjectedAssistantDeltas(threadId, previousState, nextState);
149
+ syncProjectedLiveState(threadId, previousState, nextState);
139
150
  syncProjectedActions(threadId, projectPendingDesktopActions(threadId, nextState));
140
151
  }
141
152
 
142
153
  function onDisconnect() {
143
154
  rawStatesByThreadId.clear();
144
155
  assistantMessageTextsByThreadId.clear();
156
+ mirroredActivityKeysByThreadId.clear();
157
+ mirroredUserMessageKeysByThreadId.clear();
158
+ clearAllDesktopTurnCompletionTimers();
145
159
  pendingRoutesByRequestId.clear();
146
160
  recoveringThreadIds.clear();
147
161
  queuedChangesByThreadId.clear();
@@ -278,14 +292,21 @@ function createDesktopIpcActionFollower({
278
292
  }
279
293
 
280
294
  rawStatesByThreadId.set(threadId, nextState);
281
- syncProjectedAssistantDeltas(threadId, baselineState, nextState);
295
+ syncProjectedLiveState(threadId, baselineState, nextState);
282
296
  syncProjectedActions(threadId, projectPendingDesktopActions(threadId, nextState));
283
297
  }
284
298
 
299
+ function syncProjectedLiveState(threadId, previousState, nextState) {
300
+ syncProjectedAssistantDeltas(threadId, previousState, nextState);
301
+ syncProjectedDesktopActivities(threadId, nextState);
302
+ }
303
+
285
304
  function syncProjectedAssistantDeltas(threadId, previousState, nextState) {
305
+ refreshTrackedDesktopTurnState(threadId, nextState);
286
306
  const previousTexts = assistantMessageTextsByThreadId.get(threadId);
287
307
  if (!previousTexts && !previousState) {
288
308
  assistantMessageTextsByThreadId.set(threadId, snapshotAssistantMessageTexts(nextState));
309
+ syncProjectedDesktopTurnCompletions(threadId, nextState);
289
310
  return;
290
311
  }
291
312
 
@@ -297,13 +318,179 @@ function createDesktopIpcActionFollower({
297
318
  );
298
319
  if (notifications.length === 0) {
299
320
  assistantMessageTextsByThreadId.set(threadId, snapshotAssistantMessageTexts(nextState));
321
+ syncProjectedDesktopTurnCompletions(threadId, nextState);
300
322
  return;
301
323
  }
302
324
 
303
- for (const notification of notifications) {
325
+ const activeDeltaTurnIds = new Set(
326
+ notifications
327
+ .map((notification) => readString(notification?.params?.turnId) || readString(notification?.params?.turn_id))
328
+ .filter(Boolean)
329
+ );
330
+ const userNotifications = projectDesktopUserMessageNotifications(
331
+ threadId,
332
+ nextState,
333
+ mirroredUserMessageKeysForThread(threadId),
334
+ activeDeltaTurnIds
335
+ );
336
+
337
+ // Desktop IPC state can report assistant text growth before rollout replay catches
338
+ // up with the user prelude. Emit the opening prompt first to avoid mobile row jumps.
339
+ for (const notification of [...userNotifications, ...notifications]) {
304
340
  sendApplicationResponse(JSON.stringify(notification));
305
341
  }
342
+ for (const turnId of activeDeltaTurnIds) {
343
+ noteDesktopIpcTurnActivity(threadId, turnId, nextState);
344
+ }
306
345
  assistantMessageTextsByThreadId.set(threadId, snapshotAssistantMessageTexts(nextState));
346
+ syncProjectedDesktopTurnCompletions(threadId, nextState);
347
+ }
348
+
349
+ function syncProjectedDesktopActivities(threadId, nextState) {
350
+ refreshTrackedDesktopTurnState(threadId, nextState);
351
+ const activityKeys = mirroredActivityKeysForThread(threadId);
352
+ const notifications = projectDesktopActivityNotifications(threadId, nextState, activityKeys);
353
+ if (notifications.length === 0) {
354
+ syncProjectedDesktopTurnCompletions(threadId, nextState);
355
+ return;
356
+ }
357
+
358
+ const activeActivityTurnIds = new Set(
359
+ notifications
360
+ .map((notification) => readString(notification?.params?.turnId) || readString(notification?.params?.turn_id))
361
+ .filter(Boolean)
362
+ );
363
+ const userNotifications = projectDesktopUserMessageNotifications(
364
+ threadId,
365
+ nextState,
366
+ mirroredUserMessageKeysForThread(threadId),
367
+ activeActivityTurnIds
368
+ );
369
+
370
+ // Tool-call snapshots are independent from assistant text deltas. Emit them
371
+ // through the same app-server event names rollout mirroring uses so iOS keeps
372
+ // showing active tool rows while the Mac-owned run is still executing.
373
+ for (const notification of [...userNotifications, ...notifications]) {
374
+ sendApplicationResponse(JSON.stringify(notification));
375
+ }
376
+ for (const turnId of activeActivityTurnIds) {
377
+ noteDesktopIpcTurnActivity(threadId, turnId, nextState);
378
+ }
379
+ syncProjectedDesktopTurnCompletions(threadId, nextState);
380
+ }
381
+
382
+ function mirroredUserMessageKeysForThread(threadId) {
383
+ let keys = mirroredUserMessageKeysByThreadId.get(threadId);
384
+ if (!keys) {
385
+ keys = new Set();
386
+ mirroredUserMessageKeysByThreadId.set(threadId, keys);
387
+ }
388
+ return keys;
389
+ }
390
+
391
+ function mirroredActivityKeysForThread(threadId) {
392
+ let keys = mirroredActivityKeysByThreadId.get(threadId);
393
+ if (!keys) {
394
+ keys = new Set();
395
+ mirroredActivityKeysByThreadId.set(threadId, keys);
396
+ }
397
+ return keys;
398
+ }
399
+
400
+ function noteDesktopIpcTurnActivity(threadId, turnId, latestState) {
401
+ if (!turnId || turnCompletionIdleMs <= 0) {
402
+ return;
403
+ }
404
+
405
+ let turns = activeDesktopTurnsByThreadId.get(threadId);
406
+ if (!turns) {
407
+ turns = new Map();
408
+ activeDesktopTurnsByThreadId.set(threadId, turns);
409
+ }
410
+
411
+ const existing = turns.get(turnId);
412
+ if (existing?.timer) {
413
+ clearTimeoutFn(existing.timer);
414
+ }
415
+
416
+ const entry = { latestState, timer: null };
417
+ turns.set(turnId, entry);
418
+ scheduleDesktopIpcTurnIdleCompletion(threadId, turnId, entry);
419
+ }
420
+
421
+ function scheduleDesktopIpcTurnIdleCompletion(threadId, turnId, entry) {
422
+ entry.timer = setTimeoutFn(() => {
423
+ const currentTurns = activeDesktopTurnsByThreadId.get(threadId);
424
+ const currentEntry = currentTurns?.get(turnId);
425
+ if (!currentEntry) {
426
+ return;
427
+ }
428
+
429
+ if (hasOpenDesktopRequestForTurn(currentEntry.latestState, turnId)
430
+ || hasActiveDesktopActivityForTurn(currentEntry.latestState, turnId)) {
431
+ scheduleDesktopIpcTurnIdleCompletion(threadId, turnId, currentEntry);
432
+ return;
433
+ }
434
+
435
+ completeDesktopIpcTurn(threadId, turnId);
436
+ }, turnCompletionIdleMs);
437
+ entry.timer.unref?.();
438
+ }
439
+
440
+ function refreshTrackedDesktopTurnState(threadId, latestState) {
441
+ const turns = activeDesktopTurnsByThreadId.get(threadId);
442
+ if (!turns) {
443
+ return;
444
+ }
445
+
446
+ for (const entry of turns.values()) {
447
+ entry.latestState = latestState;
448
+ }
449
+ }
450
+
451
+ function syncProjectedDesktopTurnCompletions(threadId, nextState) {
452
+ const turns = activeDesktopTurnsByThreadId.get(threadId);
453
+ if (!turns || turns.size === 0) {
454
+ return;
455
+ }
456
+
457
+ const completions = projectDesktopTurnCompletedNotifications(
458
+ threadId,
459
+ nextState,
460
+ new Set(turns.keys())
461
+ );
462
+ for (const notification of completions) {
463
+ completeDesktopIpcTurn(threadId, notification.params.turnId, notification.params.status || "completed");
464
+ }
465
+ }
466
+
467
+ function completeDesktopIpcTurn(threadId, turnId, status = "completed") {
468
+ const turns = activeDesktopTurnsByThreadId.get(threadId);
469
+ const entry = turns?.get(turnId);
470
+ if (!entry) {
471
+ return;
472
+ }
473
+
474
+ if (entry.timer) {
475
+ clearTimeoutFn(entry.timer);
476
+ }
477
+ turns.delete(turnId);
478
+ if (turns.size === 0) {
479
+ activeDesktopTurnsByThreadId.delete(threadId);
480
+ }
481
+
482
+ sendApplicationResponse(JSON.stringify(createDesktopIpcTurnCompletedNotification(threadId, turnId, status)));
483
+ }
484
+
485
+ function clearAllDesktopTurnCompletionTimers() {
486
+ for (const turns of activeDesktopTurnsByThreadId.values()) {
487
+ for (const entry of turns.values()) {
488
+ if (entry.timer) {
489
+ clearTimeoutFn(entry.timer);
490
+ }
491
+ }
492
+ }
493
+ activeDesktopTurnsByThreadId.clear();
307
494
  }
308
495
 
309
496
  return {
@@ -605,6 +792,7 @@ function projectDesktopAssistantDeltaNotifications(
605
792
  turnId: message.turnId,
606
793
  itemId: message.itemId,
607
794
  delta,
795
+ ...(message.phase ? { phase: message.phase } : {}),
608
796
  },
609
797
  });
610
798
  }
@@ -612,10 +800,263 @@ function projectDesktopAssistantDeltaNotifications(
612
800
  return notifications;
613
801
  }
614
802
 
803
+ function projectDesktopUserMessageNotifications(
804
+ threadId,
805
+ conversationState,
806
+ mirroredKeys = new Set(),
807
+ turnIdFilter = null
808
+ ) {
809
+ const messages = collectUserMessages(conversationState);
810
+ const notifications = [];
811
+
812
+ for (const message of messages) {
813
+ if (turnIdFilter && turnIdFilter.size > 0 && !turnIdFilter.has(message.turnId)) {
814
+ continue;
815
+ }
816
+ if (mirroredKeys.has(message.key)) {
817
+ continue;
818
+ }
819
+
820
+ mirroredKeys.add(message.key);
821
+ notifications.push({
822
+ method: "codex/event/user_message",
823
+ params: {
824
+ threadId,
825
+ turnId: message.turnId,
826
+ message: message.text,
827
+ ...(message.itemId ? { id: message.itemId } : {}),
828
+ ...(message.timestamp ? { timestamp: message.timestamp } : {}),
829
+ remodexDesktopMirror: true,
830
+ remodexDesktopIpcMirror: true,
831
+ },
832
+ });
833
+ }
834
+
835
+ return notifications;
836
+ }
837
+
838
+ function projectDesktopActivityNotifications(threadId, conversationState, mirroredKeys = new Set()) {
839
+ const turns = Array.isArray(conversationState?.turns) ? conversationState.turns : [];
840
+ const notifications = [];
841
+
842
+ for (const turn of turns) {
843
+ const turnId = readString(turn?.id) || readString(turn?.turnId) || readString(turn?.turn_id);
844
+ if (!turnId || (
845
+ !hasActiveDesktopActivityForTurn(conversationState, turnId)
846
+ && !isDesktopTurnLive(turn, conversationState)
847
+ && !hasMirroredActivityOutputPending(turn, mirroredKeys)
848
+ )) {
849
+ continue;
850
+ }
851
+
852
+ const items = Array.isArray(turn?.items) ? turn.items : [];
853
+ const callsById = new Map();
854
+ for (const item of items) {
855
+ if (isDesktopActivityCallItem(item)) {
856
+ const callId = desktopActivityCallId(item);
857
+ if (callId) {
858
+ callsById.set(callId, item);
859
+ }
860
+ }
861
+ }
862
+
863
+ for (const item of items) {
864
+ if (isDesktopActivityCallItem(item)) {
865
+ notifications.push(...projectDesktopActivityBeginNotifications(threadId, turnId, item, mirroredKeys));
866
+ } else if (isDesktopActivityOutputItem(item)) {
867
+ const callId = desktopActivityCallId(item);
868
+ notifications.push(...projectDesktopActivityOutputNotifications(
869
+ threadId,
870
+ turnId,
871
+ item,
872
+ callsById.get(callId),
873
+ mirroredKeys
874
+ ));
875
+ }
876
+ }
877
+ }
878
+
879
+ return notifications;
880
+ }
881
+
882
+ function projectDesktopActivityBeginNotifications(threadId, turnId, item, mirroredKeys) {
883
+ const callId = desktopActivityCallId(item);
884
+ const toolName = readString(item?.name) || readString(item?.toolName) || readString(item?.tool_name);
885
+ if (!callId || !toolName || !markMirroredActivityKey(mirroredKeys, turnId, callId, "begin")) {
886
+ return [];
887
+ }
888
+
889
+ if (isCommandToolName(toolName)) {
890
+ const argumentsObject = parseToolArguments(item?.arguments);
891
+ return [createDesktopIpcNotification("codex/event/exec_command_begin", {
892
+ threadId,
893
+ turnId,
894
+ call_id: callId,
895
+ command: resolveToolCommand(toolName, argumentsObject),
896
+ cwd: resolveToolWorkingDirectory(argumentsObject, item),
897
+ status: "running",
898
+ })];
899
+ }
900
+
901
+ if (toolName === "apply_patch") {
902
+ const isCompletedPatch = Boolean(terminalStatusFromObject(item));
903
+ const fileChange = buildApplyPatchFileChangeItem({
904
+ callId,
905
+ patch: readString(item?.input) || readString(item?.arguments),
906
+ status: readString(item?.status) || (isCompletedPatch ? "completed" : "inProgress"),
907
+ idFallback: buildSyntheticActivityItemId("file-change", threadId, turnId, callId),
908
+ cwd: readString(item?.cwd) || readString(item?.workdir),
909
+ });
910
+ if (fileChange) {
911
+ return [createDesktopIpcNotification(
912
+ isCompletedPatch ? "codex/event/patch_apply_end" : "codex/event/patch_apply_begin",
913
+ {
914
+ threadId,
915
+ turnId,
916
+ id: turnId,
917
+ call_id: callId,
918
+ itemId: fileChange.id,
919
+ status: fileChange.status,
920
+ ...(isCompletedPatch ? { success: true } : {}),
921
+ changes: fileChange.changes,
922
+ }
923
+ )];
924
+ }
925
+ }
926
+
927
+ return [createDesktopIpcNotification("codex/event/background_event", {
928
+ threadId,
929
+ turnId,
930
+ call_id: callId,
931
+ message: genericToolActivityMessage(toolName),
932
+ })];
933
+ }
934
+
935
+ function projectDesktopActivityOutputNotifications(threadId, turnId, item, callItem, mirroredKeys) {
936
+ const callId = desktopActivityCallId(item);
937
+ const toolName = readString(callItem?.name) || readString(callItem?.toolName) || readString(callItem?.tool_name);
938
+ if (!callId || !toolName || !mirroredKeys.has(activityMirrorKey(turnId, callId, "begin"))) {
939
+ return [];
940
+ }
941
+ if (!markMirroredActivityKey(mirroredKeys, turnId, callId, "output")) {
942
+ return [];
943
+ }
944
+
945
+ if (!isCommandToolName(toolName)) {
946
+ return [];
947
+ }
948
+
949
+ const argumentsObject = parseToolArguments(callItem?.arguments);
950
+ const command = resolveToolCommand(toolName, argumentsObject);
951
+ const cwd = resolveToolWorkingDirectory(argumentsObject, callItem);
952
+ const output = readString(item?.output) || readString(item?.text) || readString(item?.content);
953
+ const notifications = [];
954
+ if (output) {
955
+ notifications.push(createDesktopIpcNotification("codex/event/exec_command_output_delta", {
956
+ threadId,
957
+ turnId,
958
+ call_id: callId,
959
+ command,
960
+ cwd,
961
+ chunk: output,
962
+ }));
963
+ }
964
+ notifications.push(createDesktopIpcNotification("codex/event/exec_command_end", {
965
+ threadId,
966
+ turnId,
967
+ call_id: callId,
968
+ command,
969
+ cwd,
970
+ status: "completed",
971
+ output: output || "",
972
+ }));
973
+ return notifications;
974
+ }
975
+
976
+ function projectDesktopTurnCompletedNotifications(
977
+ threadId,
978
+ conversationState,
979
+ trackedTurnIds = new Set()
980
+ ) {
981
+ if (!trackedTurnIds || trackedTurnIds.size === 0) {
982
+ return [];
983
+ }
984
+
985
+ const turns = Array.isArray(conversationState?.turns) ? conversationState.turns : [];
986
+ const notifications = [];
987
+ for (const turn of turns) {
988
+ const turnId = readString(turn?.id) || readString(turn?.turnId) || readString(turn?.turn_id);
989
+ if (!turnId || !trackedTurnIds.has(turnId)) {
990
+ continue;
991
+ }
992
+ // Terminal snapshots can race active tool rows. Keep tracking the turn until
993
+ // the activity itself closes, otherwise the phone may stay running forever.
994
+ if (hasActiveDesktopActivityForTurn(conversationState, turnId)) {
995
+ continue;
996
+ }
997
+ if (!isDesktopTurnTerminal(turn, conversationState)) {
998
+ continue;
999
+ }
1000
+
1001
+ notifications.push(createDesktopIpcTurnCompletedNotification(
1002
+ threadId,
1003
+ turnId,
1004
+ desktopTerminalStatus(turn, conversationState) || "completed"
1005
+ ));
1006
+ }
1007
+ return notifications;
1008
+ }
1009
+
1010
+ function createDesktopIpcTurnCompletedNotification(threadId, turnId, status = "completed") {
1011
+ return {
1012
+ method: "turn/completed",
1013
+ params: {
1014
+ threadId,
1015
+ turnId,
1016
+ id: turnId,
1017
+ status,
1018
+ remodexDesktopMirror: true,
1019
+ remodexDesktopIpcMirror: true,
1020
+ },
1021
+ };
1022
+ }
1023
+
615
1024
  function snapshotAssistantMessageTexts(conversationState) {
616
1025
  return new Map(collectAssistantMessages(conversationState).map((message) => [message.key, message.text]));
617
1026
  }
618
1027
 
1028
+ function collectUserMessages(conversationState) {
1029
+ const turns = Array.isArray(conversationState?.turns) ? conversationState.turns : [];
1030
+ const messages = [];
1031
+ for (const turn of turns) {
1032
+ const turnId = readString(turn?.id) || readString(turn?.turnId) || readString(turn?.turn_id);
1033
+ const items = Array.isArray(turn?.items) ? turn.items : [];
1034
+ for (const item of items) {
1035
+ if (!isUserMessageItem(item)) {
1036
+ continue;
1037
+ }
1038
+
1039
+ const text = userMessageText(item);
1040
+ if (!turnId || !text) {
1041
+ continue;
1042
+ }
1043
+
1044
+ const itemId = readString(item?.id) || readString(item?.itemId) || readString(item?.item_id);
1045
+ messages.push({
1046
+ key: userMessageKey(turnId, itemId, text),
1047
+ turnId,
1048
+ itemId,
1049
+ text,
1050
+ timestamp: readString(item?.createdAt)
1051
+ || readString(item?.created_at)
1052
+ || readString(item?.timestamp)
1053
+ || readString(item?.time),
1054
+ });
1055
+ }
1056
+ }
1057
+ return messages;
1058
+ }
1059
+
619
1060
  function collectAssistantMessages(conversationState) {
620
1061
  const turns = Array.isArray(conversationState?.turns) ? conversationState.turns : [];
621
1062
  const messages = [];
@@ -629,6 +1070,7 @@ function collectAssistantMessages(conversationState) {
629
1070
 
630
1071
  const itemId = readString(item?.id) || readString(item?.itemId) || readString(item?.item_id);
631
1072
  const text = assistantMessageText(item);
1073
+ const phase = assistantMessagePhase(item);
632
1074
  if (!turnId || !itemId) {
633
1075
  continue;
634
1076
  }
@@ -637,6 +1079,7 @@ function collectAssistantMessages(conversationState) {
637
1079
  key: `${turnId}:${itemId}`,
638
1080
  turnId,
639
1081
  itemId,
1082
+ phase,
640
1083
  text,
641
1084
  });
642
1085
  }
@@ -644,6 +1087,295 @@ function collectAssistantMessages(conversationState) {
644
1087
  return messages;
645
1088
  }
646
1089
 
1090
+ function userMessageKey(turnId, itemId, text) {
1091
+ if (itemId) {
1092
+ return `${turnId}:${itemId}`;
1093
+ }
1094
+ return `${turnId}:text:${crypto
1095
+ .createHash("sha256")
1096
+ .update(text)
1097
+ .digest("hex")
1098
+ .slice(0, 16)}`;
1099
+ }
1100
+
1101
+ function markMirroredActivityKey(mirroredKeys, turnId, callId, phase) {
1102
+ const key = activityMirrorKey(turnId, callId, phase);
1103
+ if (mirroredKeys.has(key)) {
1104
+ return false;
1105
+ }
1106
+ mirroredKeys.add(key);
1107
+ return true;
1108
+ }
1109
+
1110
+ function activityMirrorKey(turnId, callId, phase) {
1111
+ return `${turnId}:${callId}:${phase}`;
1112
+ }
1113
+
1114
+ function hasMirroredActivityOutputPending(turn, mirroredKeys) {
1115
+ const turnId = readString(turn?.id) || readString(turn?.turnId) || readString(turn?.turn_id);
1116
+ const items = Array.isArray(turn?.items) ? turn.items : [];
1117
+ return items.some((item) => {
1118
+ if (!isDesktopActivityOutputItem(item)) {
1119
+ return false;
1120
+ }
1121
+ const callId = desktopActivityCallId(item);
1122
+ return callId
1123
+ && mirroredKeys.has(activityMirrorKey(turnId, callId, "begin"))
1124
+ && !mirroredKeys.has(activityMirrorKey(turnId, callId, "output"));
1125
+ });
1126
+ }
1127
+
1128
+ function isDesktopTurnTerminal(turn, conversationState) {
1129
+ if (desktopTerminalStatus(turn, conversationState)) {
1130
+ return true;
1131
+ }
1132
+
1133
+ return hasExplicitFalseFlag(turn, ["running", "isRunning", "streaming", "isStreaming"])
1134
+ || (
1135
+ hasExplicitFalseFlag(conversationState, ["running", "isRunning", "streaming", "isStreaming"])
1136
+ && !hasOpenDesktopRequestForTurn(conversationState, readString(turn?.id) || readString(turn?.turnId) || readString(turn?.turn_id))
1137
+ );
1138
+ }
1139
+
1140
+ function isDesktopTurnLive(turn, conversationState) {
1141
+ return hasExplicitTrueFlag(turn, ["running", "isRunning", "streaming", "isStreaming"])
1142
+ || hasExplicitTrueFlag(conversationState, ["running", "isRunning", "streaming", "isStreaming"])
1143
+ || ACTIVE_STATUS_TOKENS.has(normalizeToken(readString(turn?.status)))
1144
+ || ACTIVE_STATUS_TOKENS.has(normalizeToken(readString(turn?.state)))
1145
+ || ACTIVE_STATUS_TOKENS.has(normalizeToken(readString(turn?.phase)))
1146
+ || ACTIVE_STATUS_TOKENS.has(normalizeToken(readString(conversationState?.status)))
1147
+ || ACTIVE_STATUS_TOKENS.has(normalizeToken(readString(conversationState?.state)))
1148
+ || ACTIVE_STATUS_TOKENS.has(normalizeToken(readString(conversationState?.phase)));
1149
+ }
1150
+
1151
+ function desktopTerminalStatus(turn, conversationState) {
1152
+ const terminal = terminalStatusFromObject(turn)
1153
+ || terminalStatusFromObject(turn?.turn)
1154
+ || terminalStatusFromObject(conversationState);
1155
+ return terminal || "";
1156
+ }
1157
+
1158
+ function terminalStatusFromObject(value) {
1159
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
1160
+ return "";
1161
+ }
1162
+
1163
+ const booleanStatus = terminalStatusFromBooleans(value);
1164
+ if (booleanStatus) {
1165
+ return booleanStatus;
1166
+ }
1167
+
1168
+ const candidates = [
1169
+ value.status,
1170
+ value.state,
1171
+ value.phase,
1172
+ value.lifecycle,
1173
+ value.lifecycleStatus,
1174
+ value.lifecycle_status,
1175
+ value.runStatus,
1176
+ value.run_status,
1177
+ value.turnStatus,
1178
+ value.turn_status,
1179
+ ];
1180
+ for (const candidate of candidates) {
1181
+ const token = normalizeToken(readString(candidate));
1182
+ if (TERMINAL_STATUS_TOKENS.has(token)) {
1183
+ return canonicalTerminalStatus(token);
1184
+ }
1185
+ }
1186
+ return "";
1187
+ }
1188
+
1189
+ function terminalStatusFromBooleans(value) {
1190
+ if (value.completed === true || value.complete === true || value.done === true || value.finished === true) {
1191
+ return "completed";
1192
+ }
1193
+ if (value.failed === true || value.error === true) {
1194
+ return "failed";
1195
+ }
1196
+ if (value.cancelled === true || value.canceled === true || value.interrupted === true) {
1197
+ return "canceled";
1198
+ }
1199
+ return "";
1200
+ }
1201
+
1202
+ const TERMINAL_STATUS_TOKENS = new Set([
1203
+ "completed",
1204
+ "complete",
1205
+ "finished",
1206
+ "succeeded",
1207
+ "success",
1208
+ "failed",
1209
+ "failure",
1210
+ "error",
1211
+ "cancelled",
1212
+ "canceled",
1213
+ "interrupted",
1214
+ ]);
1215
+
1216
+ function canonicalTerminalStatus(token) {
1217
+ if (token === "failed" || token === "failure" || token === "error") {
1218
+ return "failed";
1219
+ }
1220
+ if (token === "cancelled" || token === "canceled" || token === "interrupted") {
1221
+ return "canceled";
1222
+ }
1223
+ return "completed";
1224
+ }
1225
+
1226
+ function hasExplicitFalseFlag(value, keys) {
1227
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
1228
+ return false;
1229
+ }
1230
+
1231
+ return keys.some((key) => Object.prototype.hasOwnProperty.call(value, key) && value[key] === false);
1232
+ }
1233
+
1234
+ function hasExplicitTrueFlag(value, keys) {
1235
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
1236
+ return false;
1237
+ }
1238
+
1239
+ return keys.some((key) => Object.prototype.hasOwnProperty.call(value, key) && value[key] === true);
1240
+ }
1241
+
1242
+ const ACTIVE_STATUS_TOKENS = new Set([
1243
+ "running",
1244
+ "streaming",
1245
+ "inprogress",
1246
+ "inflight",
1247
+ "started",
1248
+ "pending",
1249
+ "active",
1250
+ ]);
1251
+
1252
+ function hasOpenDesktopRequestForTurn(conversationState, turnId) {
1253
+ if (!turnId) {
1254
+ return false;
1255
+ }
1256
+
1257
+ const requests = Array.isArray(conversationState?.requests) ? conversationState.requests : [];
1258
+ return requests.some((request) => {
1259
+ if (!request || request.completed === true) {
1260
+ return false;
1261
+ }
1262
+
1263
+ const params = request.params && typeof request.params === "object" && !Array.isArray(request.params)
1264
+ ? request.params
1265
+ : {};
1266
+ const requestTurnId = readString(params.turnId)
1267
+ || readString(params.turn_id)
1268
+ || readString(request.turnId)
1269
+ || readString(request.turn_id);
1270
+ return requestTurnId === turnId;
1271
+ });
1272
+ }
1273
+
1274
+ function hasActiveDesktopActivityForTurn(conversationState, turnId) {
1275
+ const turn = desktopTurnById(conversationState, turnId);
1276
+ const items = Array.isArray(turn?.items) ? turn.items : [];
1277
+ const completedActivityIds = completedDesktopActivityIds(items);
1278
+ return items.some((item) => isActiveDesktopActivityItem(item, completedActivityIds));
1279
+ }
1280
+
1281
+ function desktopTurnById(conversationState, turnId) {
1282
+ if (!turnId) {
1283
+ return null;
1284
+ }
1285
+
1286
+ const turns = Array.isArray(conversationState?.turns) ? conversationState.turns : [];
1287
+ return turns.find((turn) => {
1288
+ const candidateTurnId = readString(turn?.id) || readString(turn?.turnId) || readString(turn?.turn_id);
1289
+ return candidateTurnId === turnId;
1290
+ }) || null;
1291
+ }
1292
+
1293
+ function isActiveDesktopActivityItem(item, completedActivityIds = new Set()) {
1294
+ if (!item || typeof item !== "object" || Array.isArray(item)) {
1295
+ return false;
1296
+ }
1297
+ if (!isDesktopActivityItem(item) || isDesktopActivityOutputItem(item) || terminalStatusFromObject(item)) {
1298
+ return false;
1299
+ }
1300
+ if (hasExplicitTrueFlag(item, ["running", "isRunning", "streaming", "isStreaming"])
1301
+ || ACTIVE_STATUS_TOKENS.has(normalizeToken(readString(item.status)))
1302
+ || ACTIVE_STATUS_TOKENS.has(normalizeToken(readString(item.state)))
1303
+ || ACTIVE_STATUS_TOKENS.has(normalizeToken(readString(item.phase)))
1304
+ || ACTIVE_STATUS_TOKENS.has(normalizeToken(readString(item.lifecycle)))) {
1305
+ return true;
1306
+ }
1307
+
1308
+ // Codex Desktop often represents a live tool as a bare function_call/custom_tool_call
1309
+ // with only call_id, then appends a separate *_output item. Treat the call as active
1310
+ // until that output appears, even when no explicit "running" status is present.
1311
+ const activityId = desktopActivityCallId(item);
1312
+ return isDesktopActivityCallItem(item) && activityId && !completedActivityIds.has(activityId);
1313
+ }
1314
+
1315
+ function isDesktopActivityItem(item) {
1316
+ const type = normalizeToken(item?.type);
1317
+ return type.includes("tool")
1318
+ || type.includes("command")
1319
+ || type.includes("exec")
1320
+ || type.includes("mcp")
1321
+ || type.includes("function");
1322
+ }
1323
+
1324
+ function isDesktopActivityCallItem(item) {
1325
+ const type = normalizeToken(item?.type);
1326
+ if (isDesktopActivityOutputItem(item)) {
1327
+ return false;
1328
+ }
1329
+ return type.endsWith("call")
1330
+ || type.includes("toolcall")
1331
+ || type.includes("functioncall")
1332
+ || type.includes("commandexecution")
1333
+ || type.includes("localshellcall");
1334
+ }
1335
+
1336
+ function isDesktopActivityOutputItem(item) {
1337
+ const type = normalizeToken(item?.type);
1338
+ return type.includes("output")
1339
+ || type.includes("result")
1340
+ || type.endsWith("end")
1341
+ || type.includes("completed");
1342
+ }
1343
+
1344
+ function completedDesktopActivityIds(items) {
1345
+ const ids = new Set();
1346
+ for (const item of items) {
1347
+ if (!item || typeof item !== "object" || Array.isArray(item)) {
1348
+ continue;
1349
+ }
1350
+ if (!isDesktopActivityOutputItem(item) && !terminalStatusFromObject(item)) {
1351
+ continue;
1352
+ }
1353
+ const id = desktopActivityCallId(item);
1354
+ if (id) {
1355
+ ids.add(id);
1356
+ }
1357
+ }
1358
+ return ids;
1359
+ }
1360
+
1361
+ function desktopActivityCallId(item) {
1362
+ return readString(item?.call_id)
1363
+ || readString(item?.callId)
1364
+ || readString(item?.tool_call_id)
1365
+ || readString(item?.toolCallId)
1366
+ || readString(item?.requestId)
1367
+ || readString(item?.request_id)
1368
+ || readString(item?.id);
1369
+ }
1370
+
1371
+ function isUserMessageItem(item) {
1372
+ const type = normalizeToken(item?.type);
1373
+ if (type === "usermessage") {
1374
+ return true;
1375
+ }
1376
+ return type === "message" && normalizeToken(item?.role) === "user";
1377
+ }
1378
+
647
1379
  function isAssistantMessageItem(item) {
648
1380
  const type = normalizeToken(item?.type);
649
1381
  if (type === "agentmessage" || type === "assistantmessage") {
@@ -652,6 +1384,41 @@ function isAssistantMessageItem(item) {
652
1384
  return type === "message" && normalizeToken(item?.role) === "assistant";
653
1385
  }
654
1386
 
1387
+ function userMessageText(item) {
1388
+ const directText = readString(item?.text) || readString(item?.message);
1389
+ if (directText) {
1390
+ return directText;
1391
+ }
1392
+
1393
+ const content = Array.isArray(item?.content) ? item.content : [];
1394
+ return content
1395
+ .map((entry) => entry && typeof entry === "object" ? entry : null)
1396
+ .filter(Boolean)
1397
+ .map((entry) => readString(entry.text) || readString(entry?.data?.text))
1398
+ .filter(Boolean)
1399
+ .join("");
1400
+ }
1401
+
1402
+ function assistantMessagePhase(item) {
1403
+ return normalizeAssistantPhase(
1404
+ readString(item?.phase)
1405
+ || readString(item?.assistantPhase)
1406
+ || readString(item?.assistant_phase)
1407
+ || readString(item?.metadata?.phase)
1408
+ );
1409
+ }
1410
+
1411
+ function normalizeAssistantPhase(value) {
1412
+ const normalized = normalizeToken(value);
1413
+ if (!normalized) {
1414
+ return "";
1415
+ }
1416
+ if (normalized === "finalanswer") {
1417
+ return "final_answer";
1418
+ }
1419
+ return normalized;
1420
+ }
1421
+
655
1422
  function assistantMessageText(item) {
656
1423
  const directText = readString(item?.text) || readString(item?.message);
657
1424
  if (directText) {
@@ -817,6 +1584,68 @@ function requestIdKey(value) {
817
1584
  return "";
818
1585
  }
819
1586
 
1587
+ function parseToolArguments(rawArguments) {
1588
+ const parsed = safeParseJSON(rawArguments);
1589
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
1590
+ }
1591
+
1592
+ function resolveToolCommand(toolName, argumentsObject) {
1593
+ if (!isCommandToolName(toolName)) {
1594
+ return toolName;
1595
+ }
1596
+
1597
+ return readString(argumentsObject.cmd)
1598
+ || readString(argumentsObject.command)
1599
+ || readString(argumentsObject.raw_command)
1600
+ || readString(argumentsObject.rawCommand)
1601
+ || toolName;
1602
+ }
1603
+
1604
+ function resolveToolWorkingDirectory(argumentsObject, item = {}) {
1605
+ return readString(argumentsObject.workdir)
1606
+ || readString(argumentsObject.cwd)
1607
+ || readString(argumentsObject.working_directory)
1608
+ || readString(item?.cwd)
1609
+ || readString(item?.workdir)
1610
+ || "";
1611
+ }
1612
+
1613
+ function isCommandToolName(toolName) {
1614
+ const normalized = readString(toolName).toLowerCase();
1615
+ return normalized === "exec_command"
1616
+ || normalized === "shell_command"
1617
+ || normalized.endsWith(".exec_command")
1618
+ || normalized.endsWith(".shell_command");
1619
+ }
1620
+
1621
+ function genericToolActivityMessage(toolName) {
1622
+ switch (readString(toolName).toLowerCase()) {
1623
+ case "apply_patch":
1624
+ return "Applying patch";
1625
+ case "write_stdin":
1626
+ return "Writing to terminal";
1627
+ case "read_thread_terminal":
1628
+ return "Reading terminal output";
1629
+ default:
1630
+ return `Running ${toolName}`;
1631
+ }
1632
+ }
1633
+
1634
+ function buildSyntheticActivityItemId(kind, threadId, turnId, callId) {
1635
+ return `${kind}:${threadId}:${turnId}:${callId}`;
1636
+ }
1637
+
1638
+ function createDesktopIpcNotification(method, params = {}) {
1639
+ return {
1640
+ method,
1641
+ params: {
1642
+ remodexDesktopMirror: true,
1643
+ remodexDesktopIpcMirror: true,
1644
+ ...params,
1645
+ },
1646
+ };
1647
+ }
1648
+
820
1649
  function readString(value) {
821
1650
  return typeof value === "string" && value.trim() ? value.trim() : "";
822
1651
  }
@@ -843,7 +1672,11 @@ module.exports = {
843
1672
  applyConversationStateChange,
844
1673
  createDesktopIpcActionFollower,
845
1674
  desktopFollowerPayloadForResponse,
1675
+ hasActiveDesktopActivityForTurn,
846
1676
  projectDesktopAssistantDeltaNotifications,
1677
+ projectDesktopActivityNotifications,
1678
+ projectDesktopTurnCompletedNotifications,
1679
+ projectDesktopUserMessageNotifications,
847
1680
  projectPendingDesktopActions,
848
1681
  resolveDefaultIpcSocketPath,
849
1682
  seedConversationStateFromThreadRead,