@makerbi/remodex 1.5.1 → 1.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -127,13 +127,19 @@ function stopMacOSBridgeService({
127
127
  platform = process.platform,
128
128
  execFileSyncImpl = execFileSync,
129
129
  fsImpl = fs,
130
+ processImpl = process,
130
131
  } = {}) {
131
132
  assertDarwinPlatform(platform);
133
+ const previousStatus = readBridgeStatus({ env, fsImpl });
132
134
  bootoutLaunchAgent({
133
135
  env,
134
136
  execFileSyncImpl,
135
137
  ignoreMissing: true,
136
138
  });
139
+ terminateRecordedBridgeProcess(previousStatus, {
140
+ execFileSyncImpl,
141
+ processImpl,
142
+ });
137
143
  clearPairingSession({ env, fsImpl });
138
144
  clearBridgeStatus({ env, fsImpl });
139
145
  }
@@ -156,6 +162,44 @@ function resetMacOSBridgePairing({
156
162
  return resetBridgePairingImpl();
157
163
  }
158
164
 
165
+ // Stops orphaned run-service processes left behind when launchd reports the job missing.
166
+ function terminateRecordedBridgeProcess(status, {
167
+ execFileSyncImpl = execFileSync,
168
+ processImpl = process,
169
+ } = {}) {
170
+ const pid = Number(status?.pid);
171
+ if (!Number.isInteger(pid) || pid <= 0 || pid === processImpl.pid) {
172
+ return false;
173
+ }
174
+
175
+ if (!isRecordedRemodexBridgeProcess(pid, { execFileSyncImpl })) {
176
+ return false;
177
+ }
178
+
179
+ try {
180
+ processImpl.kill(pid, "SIGTERM");
181
+ return true;
182
+ } catch {
183
+ return false;
184
+ }
185
+ }
186
+
187
+ // Checks the command line before killing so stale status files cannot target unrelated processes.
188
+ function isRecordedRemodexBridgeProcess(pid, { execFileSyncImpl = execFileSync } = {}) {
189
+ try {
190
+ const command = execFileSyncImpl("ps", [
191
+ "-p",
192
+ String(pid),
193
+ "-o",
194
+ "command=",
195
+ ], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
196
+ return command.includes("remodex")
197
+ && command.includes("run-service");
198
+ } catch {
199
+ return false;
200
+ }
201
+ }
202
+
159
203
  function getMacOSBridgeServiceStatus({
160
204
  env = process.env,
161
205
  platform = process.platform,
@@ -92,7 +92,7 @@ function resolveBridgeRelaySession(state, { persist = true } = {}) {
92
92
  };
93
93
  }
94
94
 
95
- // Persists the trusted iPhone identity so reconnects can be authenticated during the current pairing flow.
95
+ // Persists trusted mobile identities so reconnects can be authenticated across paired iPhone/iPad clients.
96
96
  function rememberTrustedPhone(state, phoneDeviceId, phoneIdentityPublicKey, { persist = true } = {}) {
97
97
  const normalizedDeviceId = normalizeNonEmptyString(phoneDeviceId);
98
98
  const normalizedPublicKey = normalizeNonEmptyString(phoneIdentityPublicKey);
@@ -100,10 +100,10 @@ function rememberTrustedPhone(state, phoneDeviceId, phoneIdentityPublicKey, { pe
100
100
  return state;
101
101
  }
102
102
 
103
- // Remodex supports one trusted iPhone per Mac, so a new trust record replaces old ones.
104
103
  const nextState = normalizeBridgeDeviceState({
105
104
  ...state,
106
105
  trustedPhones: {
106
+ ...(state?.trustedPhones || {}),
107
107
  [normalizedDeviceId]: normalizedPublicKey,
108
108
  },
109
109
  });
@@ -0,0 +1,197 @@
1
+ // FILE: session-jsonl-history.js
2
+ // Purpose: Reconstructs a small thread/turns/list page from local Codex session JSONL files.
3
+
4
+ const fs = require("fs");
5
+
6
+ function readThreadTurnsListPageFromSessionJsonl(filePath, {
7
+ threadId = "",
8
+ limit = 5,
9
+ maxLimit = 5,
10
+ cursor = null,
11
+ fsModule = fs,
12
+ } = {}) {
13
+ if (!filePath || cursor != null) {
14
+ return null;
15
+ }
16
+
17
+ const content = fsModule.readFileSync(filePath, "utf8");
18
+ const turns = parseSessionJsonlTurns(content, { threadId });
19
+ if (turns.length === 0) {
20
+ return null;
21
+ }
22
+
23
+ const requestedLimit = Number.isInteger(limit) && limit > 0 ? limit : 5;
24
+ const requestedMaxLimit = Number.isInteger(maxLimit) && maxLimit > 0 ? maxLimit : 5;
25
+ const safeLimit = Math.min(requestedLimit, requestedMaxLimit, 5);
26
+ const pageTurns = turns.slice(-safeLimit).reverse();
27
+ return {
28
+ data: pageTurns,
29
+ nextCursor: turns.length > pageTurns.length ? "remodex-jsonl-fallback-older-unavailable" : null,
30
+ remodexJsonlFallback: true,
31
+ };
32
+ }
33
+
34
+ function parseSessionJsonlTurns(content, { threadId = "" } = {}) {
35
+ const turns = [];
36
+ const turnsById = new Map();
37
+ let activeTurnId = "";
38
+ let sessionThreadId = normalizeString(threadId);
39
+
40
+ const lines = String(content || "").split(/\r?\n/);
41
+ for (let index = 0; index < lines.length; index += 1) {
42
+ const line = lines[index].trim();
43
+ if (!line) {
44
+ continue;
45
+ }
46
+
47
+ let entry;
48
+ try {
49
+ entry = JSON.parse(line);
50
+ } catch {
51
+ continue;
52
+ }
53
+
54
+ if (entry?.type === "session_meta") {
55
+ const payload = objectValue(entry.payload);
56
+ sessionThreadId ||= normalizeString(payload?.id)
57
+ || normalizeString(payload?.thread_id)
58
+ || normalizeString(payload?.threadId);
59
+ continue;
60
+ }
61
+
62
+ if (entry?.type === "event_msg") {
63
+ const payload = objectValue(entry.payload);
64
+ const eventType = normalizeString(payload?.type);
65
+ if (eventType === "task_started") {
66
+ activeTurnId = normalizeString(payload?.turn_id)
67
+ || normalizeString(payload?.turnId)
68
+ || activeTurnId
69
+ || `turn-line-${index + 1}`;
70
+ ensureTurn(turns, turnsById, activeTurnId, sessionThreadId, entry.timestamp);
71
+ continue;
72
+ }
73
+
74
+ if (eventType === "task_complete") {
75
+ const turn = ensureTurn(
76
+ turns,
77
+ turnsById,
78
+ normalizeString(payload?.turn_id) || normalizeString(payload?.turnId) || activeTurnId || `turn-line-${index + 1}`,
79
+ sessionThreadId,
80
+ entry.timestamp
81
+ );
82
+ turn.status = "completed";
83
+ continue;
84
+ }
85
+
86
+ if (eventType === "user_message") {
87
+ const turn = ensureTurn(
88
+ turns,
89
+ turnsById,
90
+ normalizeString(payload?.turn_id) || normalizeString(payload?.turnId) || activeTurnId || `turn-line-${index + 1}`,
91
+ sessionThreadId,
92
+ entry.timestamp
93
+ );
94
+ turn.items.push({
95
+ id: normalizeString(payload?.id) || `user-message-line-${index + 1}`,
96
+ type: "user_message",
97
+ role: "user",
98
+ text: normalizeString(payload?.message) || normalizeString(payload?.text),
99
+ });
100
+ continue;
101
+ }
102
+
103
+ // The final assistant text is usually present again as a response_item message.
104
+ // Skipping event agent_message avoids double-rendering streaming/final chunks.
105
+ continue;
106
+ }
107
+
108
+ if (entry?.type === "response_item") {
109
+ const payload = objectValue(entry.payload);
110
+ if (!payload) {
111
+ continue;
112
+ }
113
+ const turn = ensureTurn(
114
+ turns,
115
+ turnsById,
116
+ normalizeString(payload.turn_id) || normalizeString(payload.turnId) || activeTurnId || `turn-line-${index + 1}`,
117
+ sessionThreadId,
118
+ entry.timestamp
119
+ );
120
+ const item = normalizeResponseItemForHistory(payload, index + 1);
121
+ if (item) {
122
+ turn.items.push(item);
123
+ }
124
+ }
125
+ }
126
+
127
+ return turns.filter((turn) => turn.items.length > 0);
128
+ }
129
+
130
+ function ensureTurn(turns, turnsById, turnId, threadId, timestamp) {
131
+ const normalizedTurnId = normalizeString(turnId) || `turn-${turns.length + 1}`;
132
+ let turn = turnsById.get(normalizedTurnId);
133
+ if (!turn) {
134
+ turn = {
135
+ id: normalizedTurnId,
136
+ threadId: normalizeString(threadId) || undefined,
137
+ createdAt: normalizeString(timestamp) || undefined,
138
+ status: "running",
139
+ items: [],
140
+ };
141
+ turnsById.set(normalizedTurnId, turn);
142
+ turns.push(turn);
143
+ }
144
+ if (!turn.createdAt && timestamp) {
145
+ turn.createdAt = normalizeString(timestamp);
146
+ }
147
+ return turn;
148
+ }
149
+
150
+ function normalizeResponseItemForHistory(payload, lineNumber) {
151
+ const type = normalizeHistoryItemType(payload.type);
152
+ if (!type) {
153
+ return null;
154
+ }
155
+
156
+ const item = {
157
+ ...payload,
158
+ id: normalizeString(payload.id)
159
+ || normalizeString(payload.item_id)
160
+ || normalizeString(payload.itemId)
161
+ || `response-item-line-${lineNumber}`,
162
+ type,
163
+ };
164
+
165
+ if (type === "message" && !normalizeString(item.role)) {
166
+ item.role = "assistant";
167
+ }
168
+
169
+ return item;
170
+ }
171
+
172
+ function normalizeHistoryItemType(rawType) {
173
+ const normalized = normalizeString(rawType).toLowerCase().replace(/[\s_-]+/g, "");
174
+ if (!normalized) {
175
+ return "";
176
+ }
177
+ if (normalized === "functioncall") {
178
+ return "tool_call";
179
+ }
180
+ if (normalized === "functioncalloutput") {
181
+ return "tool_call_output";
182
+ }
183
+ return rawType;
184
+ }
185
+
186
+ function objectValue(value) {
187
+ return value && typeof value === "object" && !Array.isArray(value) ? value : null;
188
+ }
189
+
190
+ function normalizeString(value) {
191
+ return typeof value === "string" && value.trim() ? value.trim() : "";
192
+ }
193
+
194
+ module.exports = {
195
+ parseSessionJsonlTurns,
196
+ readThreadTurnsListPageFromSessionJsonl,
197
+ };