@makerbi/remodex 1.3.8 → 1.3.9

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 CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@makerbi/remodex",
3
- "version": "1.3.8",
3
+ "version": "1.3.9",
4
4
  "description": "Local bridge between Codex and the Remodex mobile app. Run `remodex up` to start.",
5
5
  "main": "src/index.js",
6
6
  "bin": {
7
- "remodex": "bin/remodex.js"
7
+ "remodex": "./bin/remodex.js"
8
8
  },
9
9
  "files": [
10
10
  "bin/",
package/src/bridge.js CHANGED
@@ -51,12 +51,14 @@ const { createShortPairingCode, SHORT_PAIRING_CODE_LENGTH } = require("./qr");
51
51
 
52
52
  const execFileAsync = promisify(execFile);
53
53
  const RELAY_WATCHDOG_PING_INTERVAL_MS = 10_000;
54
- const RELAY_WATCHDOG_STALE_AFTER_MS = 25_000;
54
+ // Keep the watchdog above the relay heartbeat cadence so quiet healthy sockets survive idle gaps.
55
+ const RELAY_WATCHDOG_STALE_AFTER_MS = 70_000;
55
56
  const BRIDGE_STATUS_HEARTBEAT_INTERVAL_MS = 5_000;
56
57
  const STALE_RELAY_STATUS_MESSAGE = "Relay heartbeat stalled; reconnect pending.";
57
58
  const RELAY_HISTORY_IMAGE_REFERENCE_URL = "remodex://history-image-elided";
58
- const RELAY_THREAD_PAYLOAD_SOFT_LIMIT_BYTES = 3 * 1024 * 1024;
59
+ const RELAY_THREAD_PAYLOAD_SOFT_LIMIT_BYTES = 4 * 1024 * 1024;
59
60
  const RELAY_HISTORY_TEXT_TAIL_LIMIT_CHARS = 24_000;
61
+ const RELAY_HISTORY_RECENT_TURN_TARGET = 40;
60
62
 
61
63
  function buildRelayUserAgentHeader({ version = bridgePackageVersion } = {}) {
62
64
  const normalizedVersion = typeof version === "string" && version.trim()
@@ -1376,8 +1378,8 @@ function normalizeNonEmptyString(value) {
1376
1378
  return typeof value === "string" && value.trim() ? value.trim() : "";
1377
1379
  }
1378
1380
 
1379
- // Shrinks `thread/read` and `thread/resume` snapshots by eliding bulky history payloads
1380
- // that the iPhone does not render directly (inline images, compaction replacement history).
1381
+ // Shrinks `thread/read` and `thread/resume` snapshots for mobile relay delivery.
1382
+ // This elides bulky blobs and replaces oversized older history with a compact marker.
1381
1383
  function sanitizeThreadHistoryImagesForRelay(rawMessage, requestMethod) {
1382
1384
  if (requestMethod !== "thread/read" && requestMethod !== "thread/resume") {
1383
1385
  return rawMessage;
@@ -1779,19 +1781,25 @@ function trimThreadPayloadForRelay(parsed, explicitThread = undefined) {
1779
1781
  }
1780
1782
 
1781
1783
  const turns = thread.turns;
1782
- let trimmedTurns = turns.slice();
1784
+ let trimmedTurns = turns.length > RELAY_HISTORY_RECENT_TURN_TARGET
1785
+ ? turns.slice(-RELAY_HISTORY_RECENT_TURN_TARGET)
1786
+ : turns.slice();
1783
1787
  while (trimmedTurns.length > 1) {
1784
- trimmedTurns = trimmedTurns.slice(1);
1785
- const candidateThread = {
1786
- ...thread,
1787
- turns: trimmedTurns,
1788
- historyTailTruncatedForRelay: true,
1789
- };
1788
+ if (trimmedTurns.length === turns.length) {
1789
+ trimmedTurns = trimmedTurns.slice(1);
1790
+ }
1791
+ const candidateThread = buildRelayHistoryCompactedThread(
1792
+ thread,
1793
+ buildRelayCompactedHistoryTurns(turns, trimmedTurns),
1794
+ Math.max(0, turns.length - trimmedTurns.length),
1795
+ trimmedTurns.length
1796
+ );
1790
1797
  encoded = encodeRelayThreadPayload(parsed, candidateThread);
1791
1798
  if (encoded != null && Buffer.byteLength(encoded, "utf8") <= RELAY_THREAD_PAYLOAD_SOFT_LIMIT_BYTES) {
1792
1799
  return encoded;
1793
1800
  }
1794
1801
  workingThread = candidateThread;
1802
+ trimmedTurns = trimmedTurns.slice(1);
1795
1803
  }
1796
1804
 
1797
1805
  const newestTurn = trimmedTurns[0];
@@ -1802,14 +1810,23 @@ function trimThreadPayloadForRelay(parsed, explicitThread = undefined) {
1802
1810
  let trimmedItems = newestTurn.items.slice();
1803
1811
  while (trimmedItems.length > 1) {
1804
1812
  trimmedItems = trimmedItems.slice(1);
1805
- const candidateThread = {
1806
- ...thread,
1807
- turns: [{
1813
+ const compactedTurnPrefix = buildRelayHistoryCompactionTurn(
1814
+ Math.max(0, turns.length - 1),
1815
+ 1,
1816
+ thread
1817
+ );
1818
+ const candidateThread = buildRelayHistoryCompactedThread(
1819
+ thread,
1820
+ compactedTurnPrefix ? [compactedTurnPrefix, {
1821
+ ...newestTurn,
1822
+ items: trimmedItems,
1823
+ }] : [{
1808
1824
  ...newestTurn,
1809
1825
  items: trimmedItems,
1810
1826
  }],
1811
- historyTailTruncatedForRelay: true,
1812
- };
1827
+ Math.max(0, turns.length - 1),
1828
+ 1
1829
+ );
1813
1830
  encoded = encodeRelayThreadPayload(parsed, candidateThread);
1814
1831
  if (encoded != null && Buffer.byteLength(encoded, "utf8") <= RELAY_THREAD_PAYLOAD_SOFT_LIMIT_BYTES) {
1815
1832
  return encoded;
@@ -1826,28 +1843,93 @@ function trimThreadPayloadForRelay(parsed, explicitThread = undefined) {
1826
1843
  mostRecentItem,
1827
1844
  RELAY_HISTORY_TEXT_TAIL_LIMIT_CHARS
1828
1845
  );
1829
- let candidateThread = {
1830
- ...thread,
1831
- turns: [{
1832
- ...newestTurn,
1833
- items: [truncatedItem],
1834
- }],
1835
- historyTailTruncatedForRelay: true,
1836
- };
1846
+ let candidateThread = buildRelayHistoryCompactedThread(
1847
+ thread,
1848
+ [
1849
+ ...buildRelayCompactedHistoryTurns(turns, [newestTurn]).slice(0, -1),
1850
+ {
1851
+ ...newestTurn,
1852
+ items: [truncatedItem],
1853
+ },
1854
+ ],
1855
+ Math.max(0, turns.length - 1),
1856
+ 1
1857
+ );
1837
1858
  encoded = encodeRelayThreadPayload(parsed, candidateThread);
1838
1859
  if (encoded != null && Buffer.byteLength(encoded, "utf8") <= RELAY_THREAD_PAYLOAD_SOFT_LIMIT_BYTES) {
1839
1860
  return encoded;
1840
1861
  }
1841
1862
 
1842
- candidateThread = {
1863
+ candidateThread = buildRelayHistoryCompactedThread(
1864
+ thread,
1865
+ [
1866
+ ...buildRelayCompactedHistoryTurns(turns, [newestTurn]).slice(0, -1),
1867
+ {
1868
+ ...newestTurn,
1869
+ items: [compactHistoryItemForRelay(mostRecentItem, RELAY_HISTORY_TEXT_TAIL_LIMIT_CHARS)],
1870
+ },
1871
+ ],
1872
+ Math.max(0, turns.length - 1),
1873
+ 1
1874
+ );
1875
+ return encodeRelayThreadPayload(parsed, candidateThread);
1876
+ }
1877
+
1878
+ function buildRelayHistoryCompactedThread(thread, turns, omittedTurnCount, keptTurnCount) {
1879
+ return {
1843
1880
  ...thread,
1844
- turns: [{
1845
- ...newestTurn,
1846
- items: [compactHistoryItemForRelay(mostRecentItem, RELAY_HISTORY_TEXT_TAIL_LIMIT_CHARS)],
1847
- }],
1881
+ turns,
1848
1882
  historyTailTruncatedForRelay: true,
1883
+ remodexHistoryCompacted: omittedTurnCount > 0,
1884
+ remodexOmittedTurnCount: omittedTurnCount,
1885
+ remodexKeptTurnCount: keptTurnCount,
1886
+ };
1887
+ }
1888
+
1889
+ function buildRelayCompactedHistoryTurns(allTurns, keptTurns) {
1890
+ const omittedTurnCount = Math.max(0, allTurns.length - keptTurns.length);
1891
+ const compactionTurn = buildRelayHistoryCompactionTurn(
1892
+ omittedTurnCount,
1893
+ keptTurns.length,
1894
+ allTurns[0]
1895
+ );
1896
+ return compactionTurn ? [compactionTurn, ...keptTurns] : keptTurns;
1897
+ }
1898
+
1899
+ function buildRelayHistoryCompactionTurn(omittedTurnCount, keptTurnCount, idSource = {}) {
1900
+ if (omittedTurnCount <= 0) {
1901
+ return null;
1902
+ }
1903
+
1904
+ const baseId = normalizeNonEmptyString(idSource?.id)
1905
+ || normalizeNonEmptyString(idSource?.turnId)
1906
+ || normalizeNonEmptyString(idSource?.turn_id)
1907
+ || "history";
1908
+ const text = [
1909
+ "Earlier conversation compacted for mobile loading.",
1910
+ "",
1911
+ `Older turns omitted: ${omittedTurnCount}`,
1912
+ `Recent turns kept: ${keptTurnCount}`,
1913
+ "Full history remains available on the Mac runtime.",
1914
+ ].join("\n");
1915
+
1916
+ return {
1917
+ id: `remodex-history-compacted-${baseId}`,
1918
+ remodexSynthetic: true,
1919
+ remodexHistoryCompacted: true,
1920
+ remodexOmittedTurnCount: omittedTurnCount,
1921
+ remodexKeptTurnCount: keptTurnCount,
1922
+ items: [
1923
+ {
1924
+ id: `remodex-history-compacted-item-${baseId}`,
1925
+ type: "assistant_message",
1926
+ role: "assistant",
1927
+ text,
1928
+ remodexSynthetic: true,
1929
+ remodexHistoryCompacted: true,
1930
+ },
1931
+ ],
1849
1932
  };
1850
- return encodeRelayThreadPayload(parsed, candidateThread);
1851
1933
  }
1852
1934
 
1853
1935
  function encodeRelayThreadPayload(parsed, thread) {
@@ -4,8 +4,8 @@
4
4
  // Exports: version comparison + bridge/iPhone compatibility helpers
5
5
  // Depends on: none
6
6
 
7
- const MINIMUM_SUPPORTED_IOS_APP_VERSION = "1.1";
8
- const IOS_APP_COMPATIBILITY_GATE_BRIDGE_VERSION = "1.3.8";
7
+ const MINIMUM_SUPPORTED_IOS_APP_VERSION = "1.5";
8
+ const IOS_APP_COMPATIBILITY_GATE_BRIDGE_VERSION = "1.3.9";
9
9
  const LEGACY_BRIDGE_VERSION_FOR_IOS_1_0 = "1.3.7";
10
10
  const LEGACY_BRIDGE_DOWNGRADE_COMMAND = `npm install -g remodex@${LEGACY_BRIDGE_VERSION_FOR_IOS_1_0}`;
11
11
  const NOTICE_BOX_WIDTH = 74;
@@ -334,6 +334,11 @@ function restartLaunchAgent({
334
334
  launchAgentDomain(env),
335
335
  plistPath,
336
336
  ], { stdio: ["ignore", "ignore", "pipe"] });
337
+ execFileSyncImpl("launchctl", [
338
+ "kickstart",
339
+ "-k",
340
+ launchAgentLabelDomain(env),
341
+ ], { stdio: ["ignore", "ignore", "pipe"] });
337
342
  }
338
343
 
339
344
  function bootoutLaunchAgent({
@@ -9,6 +9,9 @@ const os = require("os");
9
9
  const path = require("path");
10
10
 
11
11
  const DEFAULT_DIRECTORY_LIMIT = 200;
12
+ const DEFAULT_DIRECTORY_SEARCH_LIMIT = 80;
13
+ const DEFAULT_DIRECTORY_SEARCH_MAX_DEPTH = 8;
14
+ const DEFAULT_DIRECTORY_SEARCH_MAX_VISITED = 5000;
12
15
  const DEFAULT_HIDDEN_DIRECTORY_NAMES = new Set(["Library"]);
13
16
 
14
17
  // ─── ENTRY POINT ─────────────────────────────────────────────
@@ -57,6 +60,8 @@ async function handleProjectMethod(method, params, options = {}) {
57
60
  return projectQuickLocations(options);
58
61
  case "project/listDirectory":
59
62
  return projectListDirectory(params, options);
63
+ case "project/searchDirectories":
64
+ return projectSearchDirectories(params, options);
60
65
  case "project/validatePath":
61
66
  return projectValidatePath(params, options);
62
67
  case "project/createDirectory":
@@ -112,6 +117,32 @@ async function projectListDirectory(params, options = {}) {
112
117
  };
113
118
  }
114
119
 
120
+ async function projectSearchDirectories(params, options = {}) {
121
+ const requestedPath = readString(params.path) || resolveHomeDir(options);
122
+ const query = readString(params.query);
123
+ const directory = await requireUsableDirectory(requestedPath, options);
124
+ if (!query) {
125
+ return {
126
+ path: directory.path,
127
+ entries: [],
128
+ };
129
+ }
130
+
131
+ const includeHidden = params.includeHidden === true;
132
+ const entries = await searchDirectoryEntries(directory.path, query, {
133
+ ...options,
134
+ includeHidden,
135
+ limit: normalizeSearchLimit(params.limit),
136
+ maxDepth: normalizeSearchDepth(params.maxDepth),
137
+ maxVisited: normalizeSearchVisitedLimit(params.maxVisited),
138
+ });
139
+
140
+ return {
141
+ path: directory.path,
142
+ entries,
143
+ };
144
+ }
145
+
115
146
  async function projectValidatePath(params, options = {}) {
116
147
  const requestedPath = readString(params.path);
117
148
  if (!requestedPath) {
@@ -181,6 +212,62 @@ async function readDirectoryEntries(directoryPath, options = {}) {
181
212
  .slice(0, options.limit || DEFAULT_DIRECTORY_LIMIT);
182
213
  }
183
214
 
215
+ async function searchDirectoryEntries(rootPath, query, options = {}) {
216
+ const tokens = searchTokens(query);
217
+ if (!tokens.length) {
218
+ return [];
219
+ }
220
+
221
+ const limit = options.limit || DEFAULT_DIRECTORY_SEARCH_LIMIT;
222
+ const maxDepth = options.maxDepth ?? DEFAULT_DIRECTORY_SEARCH_MAX_DEPTH;
223
+ const maxVisited = options.maxVisited || DEFAULT_DIRECTORY_SEARCH_MAX_VISITED;
224
+ const queue = [{ directoryPath: rootPath, depth: 0 }];
225
+ const visitedDirectories = new Set([realpathSyncIfAvailable(rootPath) || rootPath]);
226
+ const matches = [];
227
+ let visitedCount = 0;
228
+
229
+ while (queue.length && matches.length < limit && visitedCount < maxVisited) {
230
+ const { directoryPath, depth } = queue.shift();
231
+ visitedCount += 1;
232
+
233
+ let dirents;
234
+ try {
235
+ dirents = await fs.promises.readdir(directoryPath, { withFileTypes: true });
236
+ } catch {
237
+ continue;
238
+ }
239
+
240
+ for (const dirent of sortedDirents(dirents)) {
241
+ if (!options.includeHidden && isHiddenDirectoryName(dirent.name)) {
242
+ continue;
243
+ }
244
+
245
+ const childPath = path.join(directoryPath, dirent.name);
246
+ const directory = await directoryEntryForPath(childPath, dirent, options);
247
+ if (!directory) {
248
+ continue;
249
+ }
250
+
251
+ if (directoryMatchesSearch(directory, tokens)) {
252
+ matches.push(directory);
253
+ if (matches.length >= limit) {
254
+ break;
255
+ }
256
+ }
257
+
258
+ if (!dirent.isSymbolicLink() && depth < maxDepth) {
259
+ const realPath = directory.path;
260
+ if (!visitedDirectories.has(realPath)) {
261
+ visitedDirectories.add(realPath);
262
+ queue.push({ directoryPath: realPath, depth: depth + 1 });
263
+ }
264
+ }
265
+ }
266
+ }
267
+
268
+ return matches;
269
+ }
270
+
184
271
  async function directoryEntryForPath(candidatePath, dirent, options = {}) {
185
272
  if (!dirent.isDirectory() && !dirent.isSymbolicLink()) {
186
273
  return null;
@@ -325,6 +412,52 @@ function normalizeLimit(rawLimit) {
325
412
  return Math.min(Math.floor(numericLimit), DEFAULT_DIRECTORY_LIMIT);
326
413
  }
327
414
 
415
+ function normalizeSearchLimit(rawLimit) {
416
+ const numericLimit = Number(rawLimit);
417
+ if (!Number.isFinite(numericLimit) || numericLimit <= 0) {
418
+ return DEFAULT_DIRECTORY_SEARCH_LIMIT;
419
+ }
420
+
421
+ return Math.min(Math.floor(numericLimit), DEFAULT_DIRECTORY_SEARCH_LIMIT);
422
+ }
423
+
424
+ function normalizeSearchDepth(rawDepth) {
425
+ const numericDepth = Number(rawDepth);
426
+ if (!Number.isFinite(numericDepth) || numericDepth < 0) {
427
+ return DEFAULT_DIRECTORY_SEARCH_MAX_DEPTH;
428
+ }
429
+
430
+ return Math.min(Math.floor(numericDepth), DEFAULT_DIRECTORY_SEARCH_MAX_DEPTH);
431
+ }
432
+
433
+ function normalizeSearchVisitedLimit(rawLimit) {
434
+ const numericLimit = Number(rawLimit);
435
+ if (!Number.isFinite(numericLimit) || numericLimit <= 0) {
436
+ return DEFAULT_DIRECTORY_SEARCH_MAX_VISITED;
437
+ }
438
+
439
+ return Math.min(Math.floor(numericLimit), DEFAULT_DIRECTORY_SEARCH_MAX_VISITED);
440
+ }
441
+
442
+ function sortedDirents(dirents) {
443
+ return [...dirents].sort((left, right) => (
444
+ left.name.localeCompare(right.name, undefined, { sensitivity: "base" })
445
+ ));
446
+ }
447
+
448
+ function searchTokens(query) {
449
+ return query
450
+ .toLowerCase()
451
+ .split(/\s+/)
452
+ .map((token) => token.trim())
453
+ .filter(Boolean);
454
+ }
455
+
456
+ function directoryMatchesSearch(directory, tokens) {
457
+ const haystack = directory.name.toLowerCase();
458
+ return tokens.every((token) => haystack.includes(token));
459
+ }
460
+
328
461
  function resolveHomeDir(options = {}) {
329
462
  return options.homeDir || os.homedir();
330
463
  }
@@ -353,6 +486,7 @@ module.exports = {
353
486
  handleProjectMethod,
354
487
  projectQuickLocations,
355
488
  projectListDirectory,
489
+ projectSearchDirectories,
356
490
  projectValidatePath,
357
491
  projectCreateDirectory,
358
492
  validateDirectory,