@makerbi/remodex 1.3.8 → 1.3.10
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.js +0 -0
- package/package.json +1 -1
- package/src/bridge.js +174 -43
- package/src/ios-app-compatibility.js +2 -2
- package/src/macos-launch-agent.js +5 -0
- package/src/package-version-status.js +1 -1
- package/src/project-handler.js +134 -0
- package/src/workspace-checkpoints.js +406 -0
- package/src/workspace-handler.js +129 -18
- package/README.md +0 -483
package/bin/remodex.js
CHANGED
|
File without changes
|
package/package.json
CHANGED
package/src/bridge.js
CHANGED
|
@@ -51,12 +51,17 @@ 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
|
-
|
|
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.";
|
|
58
|
+
const CLOSE_CODE_INVALID_RELAY_REQUEST = 4000;
|
|
59
|
+
const CLOSE_CODE_REPLACED_BY_NEW_MAC = 4001;
|
|
60
|
+
const CLOSE_CODE_MAC_UNAUTHORIZED = 4005;
|
|
57
61
|
const RELAY_HISTORY_IMAGE_REFERENCE_URL = "remodex://history-image-elided";
|
|
58
|
-
const RELAY_THREAD_PAYLOAD_SOFT_LIMIT_BYTES =
|
|
62
|
+
const RELAY_THREAD_PAYLOAD_SOFT_LIMIT_BYTES = 4 * 1024 * 1024;
|
|
59
63
|
const RELAY_HISTORY_TEXT_TAIL_LIMIT_CHARS = 24_000;
|
|
64
|
+
const RELAY_HISTORY_RECENT_TURN_TARGET = 40;
|
|
60
65
|
|
|
61
66
|
function buildRelayUserAgentHeader({ version = bridgePackageVersion } = {}) {
|
|
62
67
|
const normalizedVersion = typeof version === "string" && version.trim()
|
|
@@ -144,6 +149,7 @@ function startBridge({
|
|
|
144
149
|
let lastRelayActivityAt = 0;
|
|
145
150
|
let lastPublishedBridgeStatus = null;
|
|
146
151
|
let lastConnectionStatus = null;
|
|
152
|
+
let lastConnectionError = "";
|
|
147
153
|
let codexLaunchState = config.codexEndpoint ? "connected" : "starting";
|
|
148
154
|
let codexHandshakeState = config.codexEndpoint ? "warm" : "cold";
|
|
149
155
|
const forwardedInitializeRequestIds = new Set();
|
|
@@ -320,35 +326,42 @@ function startBridge({
|
|
|
320
326
|
}
|
|
321
327
|
|
|
322
328
|
// Keeps npm start output compact by emitting only high-signal connection states.
|
|
323
|
-
function logConnectionStatus(status) {
|
|
324
|
-
if (lastConnectionStatus === status) {
|
|
329
|
+
function logConnectionStatus(status, lastError = "") {
|
|
330
|
+
if (lastConnectionStatus === status && lastConnectionError === lastError) {
|
|
325
331
|
return;
|
|
326
332
|
}
|
|
327
333
|
|
|
328
334
|
lastConnectionStatus = status;
|
|
335
|
+
lastConnectionError = lastError;
|
|
329
336
|
publishBridgeStatus({
|
|
330
337
|
state: "running",
|
|
331
338
|
connectionStatus: status,
|
|
332
339
|
pid: process.pid,
|
|
333
|
-
lastError
|
|
340
|
+
lastError,
|
|
334
341
|
});
|
|
335
342
|
console.log(`[remodex] ${status}`);
|
|
343
|
+
if (lastError) {
|
|
344
|
+
console.error(`[remodex] ${lastError}`);
|
|
345
|
+
}
|
|
336
346
|
}
|
|
337
347
|
|
|
338
348
|
// Retries the relay socket while preserving the active Codex process and session id.
|
|
339
|
-
function scheduleRelayReconnect(closeCode) {
|
|
349
|
+
function scheduleRelayReconnect(closeCode, closeReason = "") {
|
|
340
350
|
if (isShuttingDown) {
|
|
341
351
|
return;
|
|
342
352
|
}
|
|
343
353
|
|
|
344
|
-
if (closeCode
|
|
345
|
-
|
|
354
|
+
if (isTerminalRelayCloseCode(closeCode)) {
|
|
355
|
+
const lastError = buildRelayCloseStatusError(closeCode, closeReason);
|
|
356
|
+
logConnectionStatus("disconnected", lastError);
|
|
346
357
|
shutdown(codex, () => socket, () => {
|
|
347
358
|
isShuttingDown = true;
|
|
348
359
|
bridgeWakeAssertion.stop();
|
|
349
360
|
clearReconnectTimer();
|
|
350
361
|
clearRelayWatchdog();
|
|
351
362
|
clearBridgeStatusHeartbeat();
|
|
363
|
+
}, {
|
|
364
|
+
exitCode: closeCode === CLOSE_CODE_MAC_UNAUTHORIZED ? 1 : 0,
|
|
352
365
|
});
|
|
353
366
|
return;
|
|
354
367
|
}
|
|
@@ -419,18 +432,19 @@ function startBridge({
|
|
|
419
432
|
markRelayActivity();
|
|
420
433
|
});
|
|
421
434
|
|
|
422
|
-
nextSocket.on("close", (code) => {
|
|
435
|
+
nextSocket.on("close", (code, reason) => {
|
|
436
|
+
const closeReason = normalizeWebSocketCloseReason(reason);
|
|
423
437
|
if (socket === nextSocket) {
|
|
424
438
|
clearRelayWatchdog();
|
|
425
439
|
}
|
|
426
|
-
logConnectionStatus("disconnected");
|
|
440
|
+
logConnectionStatus("disconnected", buildRelayCloseStatusError(code, closeReason));
|
|
427
441
|
if (socket === nextSocket) {
|
|
428
442
|
socket = null;
|
|
429
443
|
}
|
|
430
444
|
stopContextUsageWatcher();
|
|
431
445
|
rolloutLiveMirror?.stopAll();
|
|
432
446
|
desktopRefresher.handleTransportReset();
|
|
433
|
-
scheduleRelayReconnect(code);
|
|
447
|
+
scheduleRelayReconnect(code, closeReason);
|
|
434
448
|
});
|
|
435
449
|
|
|
436
450
|
nextSocket.on("error", () => {
|
|
@@ -471,12 +485,13 @@ function startBridge({
|
|
|
471
485
|
codex.onClose(() => {
|
|
472
486
|
clearRelayWatchdog();
|
|
473
487
|
clearBridgeStatusHeartbeat();
|
|
474
|
-
|
|
488
|
+
const lastError = lastConnectionError || "";
|
|
489
|
+
logConnectionStatus("disconnected", lastError);
|
|
475
490
|
publishBridgeStatus({
|
|
476
491
|
state: "stopped",
|
|
477
492
|
connectionStatus: "disconnected",
|
|
478
493
|
pid: process.pid,
|
|
479
|
-
lastError
|
|
494
|
+
lastError,
|
|
480
495
|
});
|
|
481
496
|
isShuttingDown = true;
|
|
482
497
|
bridgeWakeAssertion.stop();
|
|
@@ -1279,7 +1294,7 @@ function buildMacRegistration(deviceState, pairingSession) {
|
|
|
1279
1294
|
};
|
|
1280
1295
|
}
|
|
1281
1296
|
|
|
1282
|
-
function shutdown(codex, getSocket, beforeExit = () => {}) {
|
|
1297
|
+
function shutdown(codex, getSocket, beforeExit = () => {}, { exitCode = 0 } = {}) {
|
|
1283
1298
|
beforeExit();
|
|
1284
1299
|
|
|
1285
1300
|
const socket = getSocket();
|
|
@@ -1289,7 +1304,41 @@ function shutdown(codex, getSocket, beforeExit = () => {}) {
|
|
|
1289
1304
|
|
|
1290
1305
|
codex.shutdown();
|
|
1291
1306
|
|
|
1292
|
-
setTimeout(() => process.exit(
|
|
1307
|
+
setTimeout(() => process.exit(exitCode), 100);
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1310
|
+
function isTerminalRelayCloseCode(closeCode) {
|
|
1311
|
+
return closeCode === CLOSE_CODE_INVALID_RELAY_REQUEST
|
|
1312
|
+
|| closeCode === CLOSE_CODE_REPLACED_BY_NEW_MAC
|
|
1313
|
+
|| closeCode === CLOSE_CODE_MAC_UNAUTHORIZED;
|
|
1314
|
+
}
|
|
1315
|
+
|
|
1316
|
+
function normalizeWebSocketCloseReason(reason) {
|
|
1317
|
+
if (typeof reason === "string") {
|
|
1318
|
+
return reason.trim();
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
if (Buffer.isBuffer(reason)) {
|
|
1322
|
+
return reason.toString("utf8").trim();
|
|
1323
|
+
}
|
|
1324
|
+
|
|
1325
|
+
return "";
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1328
|
+
function buildRelayCloseStatusError(closeCode, closeReason = "") {
|
|
1329
|
+
if (!Number.isInteger(closeCode) || closeCode === 1000 || closeCode === 1005) {
|
|
1330
|
+
return "";
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
const normalizedReason = normalizeNonEmptyString(closeReason);
|
|
1334
|
+
if (closeCode === CLOSE_CODE_MAC_UNAUTHORIZED) {
|
|
1335
|
+
return normalizedReason
|
|
1336
|
+
|| "Relay authorization failed. Set REMODEX_RELAY_ACCESS_TOKEN or use a relay that does not require a Mac access token.";
|
|
1337
|
+
}
|
|
1338
|
+
|
|
1339
|
+
return normalizedReason
|
|
1340
|
+
? `Relay closed the connection (${closeCode}): ${normalizedReason}`
|
|
1341
|
+
: `Relay closed the connection (${closeCode}).`;
|
|
1293
1342
|
}
|
|
1294
1343
|
|
|
1295
1344
|
function extractBridgeMessageContext(rawMessage) {
|
|
@@ -1376,8 +1425,8 @@ function normalizeNonEmptyString(value) {
|
|
|
1376
1425
|
return typeof value === "string" && value.trim() ? value.trim() : "";
|
|
1377
1426
|
}
|
|
1378
1427
|
|
|
1379
|
-
// Shrinks `thread/read` and `thread/resume` snapshots
|
|
1380
|
-
//
|
|
1428
|
+
// Shrinks `thread/read` and `thread/resume` snapshots for mobile relay delivery.
|
|
1429
|
+
// This elides bulky blobs and replaces oversized older history with a compact marker.
|
|
1381
1430
|
function sanitizeThreadHistoryImagesForRelay(rawMessage, requestMethod) {
|
|
1382
1431
|
if (requestMethod !== "thread/read" && requestMethod !== "thread/resume") {
|
|
1383
1432
|
return rawMessage;
|
|
@@ -1779,19 +1828,25 @@ function trimThreadPayloadForRelay(parsed, explicitThread = undefined) {
|
|
|
1779
1828
|
}
|
|
1780
1829
|
|
|
1781
1830
|
const turns = thread.turns;
|
|
1782
|
-
let trimmedTurns = turns.
|
|
1831
|
+
let trimmedTurns = turns.length > RELAY_HISTORY_RECENT_TURN_TARGET
|
|
1832
|
+
? turns.slice(-RELAY_HISTORY_RECENT_TURN_TARGET)
|
|
1833
|
+
: turns.slice();
|
|
1783
1834
|
while (trimmedTurns.length > 1) {
|
|
1784
|
-
trimmedTurns
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1835
|
+
if (trimmedTurns.length === turns.length) {
|
|
1836
|
+
trimmedTurns = trimmedTurns.slice(1);
|
|
1837
|
+
}
|
|
1838
|
+
const candidateThread = buildRelayHistoryCompactedThread(
|
|
1839
|
+
thread,
|
|
1840
|
+
buildRelayCompactedHistoryTurns(turns, trimmedTurns),
|
|
1841
|
+
Math.max(0, turns.length - trimmedTurns.length),
|
|
1842
|
+
trimmedTurns.length
|
|
1843
|
+
);
|
|
1790
1844
|
encoded = encodeRelayThreadPayload(parsed, candidateThread);
|
|
1791
1845
|
if (encoded != null && Buffer.byteLength(encoded, "utf8") <= RELAY_THREAD_PAYLOAD_SOFT_LIMIT_BYTES) {
|
|
1792
1846
|
return encoded;
|
|
1793
1847
|
}
|
|
1794
1848
|
workingThread = candidateThread;
|
|
1849
|
+
trimmedTurns = trimmedTurns.slice(1);
|
|
1795
1850
|
}
|
|
1796
1851
|
|
|
1797
1852
|
const newestTurn = trimmedTurns[0];
|
|
@@ -1802,14 +1857,23 @@ function trimThreadPayloadForRelay(parsed, explicitThread = undefined) {
|
|
|
1802
1857
|
let trimmedItems = newestTurn.items.slice();
|
|
1803
1858
|
while (trimmedItems.length > 1) {
|
|
1804
1859
|
trimmedItems = trimmedItems.slice(1);
|
|
1805
|
-
const
|
|
1806
|
-
|
|
1807
|
-
|
|
1860
|
+
const compactedTurnPrefix = buildRelayHistoryCompactionTurn(
|
|
1861
|
+
Math.max(0, turns.length - 1),
|
|
1862
|
+
1,
|
|
1863
|
+
thread
|
|
1864
|
+
);
|
|
1865
|
+
const candidateThread = buildRelayHistoryCompactedThread(
|
|
1866
|
+
thread,
|
|
1867
|
+
compactedTurnPrefix ? [compactedTurnPrefix, {
|
|
1868
|
+
...newestTurn,
|
|
1869
|
+
items: trimmedItems,
|
|
1870
|
+
}] : [{
|
|
1808
1871
|
...newestTurn,
|
|
1809
1872
|
items: trimmedItems,
|
|
1810
1873
|
}],
|
|
1811
|
-
|
|
1812
|
-
|
|
1874
|
+
Math.max(0, turns.length - 1),
|
|
1875
|
+
1
|
|
1876
|
+
);
|
|
1813
1877
|
encoded = encodeRelayThreadPayload(parsed, candidateThread);
|
|
1814
1878
|
if (encoded != null && Buffer.byteLength(encoded, "utf8") <= RELAY_THREAD_PAYLOAD_SOFT_LIMIT_BYTES) {
|
|
1815
1879
|
return encoded;
|
|
@@ -1826,28 +1890,93 @@ function trimThreadPayloadForRelay(parsed, explicitThread = undefined) {
|
|
|
1826
1890
|
mostRecentItem,
|
|
1827
1891
|
RELAY_HISTORY_TEXT_TAIL_LIMIT_CHARS
|
|
1828
1892
|
);
|
|
1829
|
-
let candidateThread =
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
...newestTurn,
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1893
|
+
let candidateThread = buildRelayHistoryCompactedThread(
|
|
1894
|
+
thread,
|
|
1895
|
+
[
|
|
1896
|
+
...buildRelayCompactedHistoryTurns(turns, [newestTurn]).slice(0, -1),
|
|
1897
|
+
{
|
|
1898
|
+
...newestTurn,
|
|
1899
|
+
items: [truncatedItem],
|
|
1900
|
+
},
|
|
1901
|
+
],
|
|
1902
|
+
Math.max(0, turns.length - 1),
|
|
1903
|
+
1
|
|
1904
|
+
);
|
|
1837
1905
|
encoded = encodeRelayThreadPayload(parsed, candidateThread);
|
|
1838
1906
|
if (encoded != null && Buffer.byteLength(encoded, "utf8") <= RELAY_THREAD_PAYLOAD_SOFT_LIMIT_BYTES) {
|
|
1839
1907
|
return encoded;
|
|
1840
1908
|
}
|
|
1841
1909
|
|
|
1842
|
-
candidateThread =
|
|
1910
|
+
candidateThread = buildRelayHistoryCompactedThread(
|
|
1911
|
+
thread,
|
|
1912
|
+
[
|
|
1913
|
+
...buildRelayCompactedHistoryTurns(turns, [newestTurn]).slice(0, -1),
|
|
1914
|
+
{
|
|
1915
|
+
...newestTurn,
|
|
1916
|
+
items: [compactHistoryItemForRelay(mostRecentItem, RELAY_HISTORY_TEXT_TAIL_LIMIT_CHARS)],
|
|
1917
|
+
},
|
|
1918
|
+
],
|
|
1919
|
+
Math.max(0, turns.length - 1),
|
|
1920
|
+
1
|
|
1921
|
+
);
|
|
1922
|
+
return encodeRelayThreadPayload(parsed, candidateThread);
|
|
1923
|
+
}
|
|
1924
|
+
|
|
1925
|
+
function buildRelayHistoryCompactedThread(thread, turns, omittedTurnCount, keptTurnCount) {
|
|
1926
|
+
return {
|
|
1843
1927
|
...thread,
|
|
1844
|
-
turns
|
|
1845
|
-
...newestTurn,
|
|
1846
|
-
items: [compactHistoryItemForRelay(mostRecentItem, RELAY_HISTORY_TEXT_TAIL_LIMIT_CHARS)],
|
|
1847
|
-
}],
|
|
1928
|
+
turns,
|
|
1848
1929
|
historyTailTruncatedForRelay: true,
|
|
1930
|
+
remodexHistoryCompacted: omittedTurnCount > 0,
|
|
1931
|
+
remodexOmittedTurnCount: omittedTurnCount,
|
|
1932
|
+
remodexKeptTurnCount: keptTurnCount,
|
|
1933
|
+
};
|
|
1934
|
+
}
|
|
1935
|
+
|
|
1936
|
+
function buildRelayCompactedHistoryTurns(allTurns, keptTurns) {
|
|
1937
|
+
const omittedTurnCount = Math.max(0, allTurns.length - keptTurns.length);
|
|
1938
|
+
const compactionTurn = buildRelayHistoryCompactionTurn(
|
|
1939
|
+
omittedTurnCount,
|
|
1940
|
+
keptTurns.length,
|
|
1941
|
+
allTurns[0]
|
|
1942
|
+
);
|
|
1943
|
+
return compactionTurn ? [compactionTurn, ...keptTurns] : keptTurns;
|
|
1944
|
+
}
|
|
1945
|
+
|
|
1946
|
+
function buildRelayHistoryCompactionTurn(omittedTurnCount, keptTurnCount, idSource = {}) {
|
|
1947
|
+
if (omittedTurnCount <= 0) {
|
|
1948
|
+
return null;
|
|
1949
|
+
}
|
|
1950
|
+
|
|
1951
|
+
const baseId = normalizeNonEmptyString(idSource?.id)
|
|
1952
|
+
|| normalizeNonEmptyString(idSource?.turnId)
|
|
1953
|
+
|| normalizeNonEmptyString(idSource?.turn_id)
|
|
1954
|
+
|| "history";
|
|
1955
|
+
const text = [
|
|
1956
|
+
"Earlier conversation compacted for mobile loading.",
|
|
1957
|
+
"",
|
|
1958
|
+
`Older turns omitted: ${omittedTurnCount}`,
|
|
1959
|
+
`Recent turns kept: ${keptTurnCount}`,
|
|
1960
|
+
"Full history remains available on the Mac runtime.",
|
|
1961
|
+
].join("\n");
|
|
1962
|
+
|
|
1963
|
+
return {
|
|
1964
|
+
id: `remodex-history-compacted-${baseId}`,
|
|
1965
|
+
remodexSynthetic: true,
|
|
1966
|
+
remodexHistoryCompacted: true,
|
|
1967
|
+
remodexOmittedTurnCount: omittedTurnCount,
|
|
1968
|
+
remodexKeptTurnCount: keptTurnCount,
|
|
1969
|
+
items: [
|
|
1970
|
+
{
|
|
1971
|
+
id: `remodex-history-compacted-item-${baseId}`,
|
|
1972
|
+
type: "assistant_message",
|
|
1973
|
+
role: "assistant",
|
|
1974
|
+
text,
|
|
1975
|
+
remodexSynthetic: true,
|
|
1976
|
+
remodexHistoryCompacted: true,
|
|
1977
|
+
},
|
|
1978
|
+
],
|
|
1849
1979
|
};
|
|
1850
|
-
return encodeRelayThreadPayload(parsed, candidateThread);
|
|
1851
1980
|
}
|
|
1852
1981
|
|
|
1853
1982
|
function encodeRelayThreadPayload(parsed, thread) {
|
|
@@ -2021,10 +2150,12 @@ function persistBridgePreferences(
|
|
|
2021
2150
|
|
|
2022
2151
|
module.exports = {
|
|
2023
2152
|
buildHeartbeatBridgeStatus,
|
|
2153
|
+
buildRelayCloseStatusError,
|
|
2024
2154
|
buildRelayAccessTokenHeaders,
|
|
2025
2155
|
buildRelayUserAgentHeader,
|
|
2026
2156
|
createMacOSBridgeWakeAssertion,
|
|
2027
2157
|
hasRelayConnectionGoneStale,
|
|
2158
|
+
isTerminalRelayCloseCode,
|
|
2028
2159
|
persistBridgePreferences,
|
|
2029
2160
|
sanitizeLiveGeneratedImageMessageForRelay,
|
|
2030
2161
|
sanitizeThreadHistoryImagesForRelay,
|
|
@@ -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.
|
|
8
|
-
const IOS_APP_COMPATIBILITY_GATE_BRIDGE_VERSION = "1.3.
|
|
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({
|
|
@@ -10,7 +10,7 @@ const { version: installedVersion = "" } = require("../package.json");
|
|
|
10
10
|
const DEFAULT_CACHE_TTL_MS = 30 * 60 * 1000;
|
|
11
11
|
const DEFAULT_EMPTY_CACHE_RETRY_MS = 60 * 1000;
|
|
12
12
|
const DEFAULT_INITIAL_FETCH_WAIT_MS = 250;
|
|
13
|
-
const REMODEX_REGISTRY_URL = "https://registry.npmjs.org/
|
|
13
|
+
const REMODEX_REGISTRY_URL = "https://registry.npmjs.org/@makerbi%2fremodex/latest";
|
|
14
14
|
|
|
15
15
|
function createBridgePackageVersionStatusReader({
|
|
16
16
|
cacheTtlMs = DEFAULT_CACHE_TTL_MS,
|
package/src/project-handler.js
CHANGED
|
@@ -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,
|