@echomem/mcp 1.4.27 → 1.4.28
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/dist/codex-session-files.js +20 -0
- package/dist/forensics.js +1 -1
- package/dist/migrate.js +24 -11
- package/dist/setup-page/client-core.js +18 -17
- package/dist/setup-page/client-extraction.js +207 -74
- package/dist/setup-page/client-report-city.js +142 -8
- package/dist/setup-page/styles-extraction.js +37 -0
- package/dist/setup-page/styles-mvp.js +189 -7
- package/dist/setup-page/styles-website-alignment.js +8 -3
- package/dist/setup.js +260 -35
- package/package.json +1 -1
package/dist/setup.js
CHANGED
|
@@ -25,8 +25,8 @@ import { fileURLToPath, pathToFileURL } from "node:url";
|
|
|
25
25
|
import axios from "axios";
|
|
26
26
|
import { KeyStore } from "./keystore.js";
|
|
27
27
|
import { fetchEncryptionConfig, deriveAndVerifyKey, setupNewEncryptionKey, verifyKeyB64 } from "./encryption.js";
|
|
28
|
-
import {
|
|
29
|
-
import { cmdMigrate, applyAccountImportStatus, applyFastAccountImportStatus,
|
|
28
|
+
import { runReport, buildStatsPayload } from "./report.js";
|
|
29
|
+
import { cmdMigrate, applyAccountImportStatus, applyFastAccountImportStatus, discoverMigratableSessions, discoverPendingSessionsTargeted, estimateMigrationEta, fetchProcessedImportKeys, isImportStatusUnsupported, markAccountImportStatusFailed, markAccountImportStatusUnavailable, markFastAccountImportStatusUnavailable, startMigration, summarizeFastMigratableDiscovery, MIGRATE_CONCURRENCY, } from "./migrate.js";
|
|
30
30
|
import { syncCodexUsage } from "./codex-sync.js";
|
|
31
31
|
import { renderSetupPage } from "./setup-page.js";
|
|
32
32
|
import { parseSetupPreviewState } from "./setup-preview.js";
|
|
@@ -1145,6 +1145,155 @@ function discoverMigratableSessionsOffThread() {
|
|
|
1145
1145
|
});
|
|
1146
1146
|
});
|
|
1147
1147
|
}
|
|
1148
|
+
export function discoverMigratableFastOffThread(opts = {}) {
|
|
1149
|
+
const migrateUrl = runtimeModuleUrl("migrate");
|
|
1150
|
+
const serializedOpts = JSON.stringify(opts);
|
|
1151
|
+
const code = `
|
|
1152
|
+
import { parentPort } from "node:worker_threads";
|
|
1153
|
+
import { discoverMigratableFastDiscovery } from ${JSON.stringify(migrateUrl)};
|
|
1154
|
+
|
|
1155
|
+
try {
|
|
1156
|
+
parentPort?.postMessage({ ok: true, discovery: discoverMigratableFastDiscovery(${serializedOpts}) });
|
|
1157
|
+
} catch (error) {
|
|
1158
|
+
parentPort?.postMessage({
|
|
1159
|
+
ok: false,
|
|
1160
|
+
message: error instanceof Error ? error.message : String(error),
|
|
1161
|
+
stack: error instanceof Error ? error.stack : undefined,
|
|
1162
|
+
});
|
|
1163
|
+
}
|
|
1164
|
+
`;
|
|
1165
|
+
const worker = new Worker(new URL(`data:text/javascript;charset=utf-8,${encodeURIComponent(code)}`));
|
|
1166
|
+
return new Promise((resolve, reject) => {
|
|
1167
|
+
let settled = false;
|
|
1168
|
+
worker.once("message", (message) => {
|
|
1169
|
+
settled = true;
|
|
1170
|
+
const msg = message;
|
|
1171
|
+
if (msg.ok === true && msg.discovery && typeof msg.discovery === "object") {
|
|
1172
|
+
resolve(msg.discovery);
|
|
1173
|
+
return;
|
|
1174
|
+
}
|
|
1175
|
+
const err = new Error(typeof msg.message === "string" ? msg.message : "Quick local discovery failed");
|
|
1176
|
+
if (typeof msg.stack === "string")
|
|
1177
|
+
err.stack = msg.stack;
|
|
1178
|
+
reject(err);
|
|
1179
|
+
});
|
|
1180
|
+
worker.once("error", (error) => {
|
|
1181
|
+
if (settled)
|
|
1182
|
+
return;
|
|
1183
|
+
settled = true;
|
|
1184
|
+
reject(error);
|
|
1185
|
+
});
|
|
1186
|
+
worker.once("exit", (code) => {
|
|
1187
|
+
if (settled)
|
|
1188
|
+
return;
|
|
1189
|
+
settled = true;
|
|
1190
|
+
reject(new Error(`Quick local discovery worker exited (code ${code}) without a result`));
|
|
1191
|
+
});
|
|
1192
|
+
});
|
|
1193
|
+
}
|
|
1194
|
+
/** Build the full local-history dashboard payload away from the callback server's event loop.
|
|
1195
|
+
* `collect()` can synchronously parse hundreds of JSONL files for tens of seconds; doing that on
|
|
1196
|
+
* the bridge thread prevents even localhost actions such as account switch from receiving a reply. */
|
|
1197
|
+
export function buildCollectedStatsPayloadOffThread(inject) {
|
|
1198
|
+
const reportUrl = runtimeModuleUrl("report");
|
|
1199
|
+
const serializedInject = JSON.stringify(inject);
|
|
1200
|
+
const code = `
|
|
1201
|
+
import { parentPort } from "node:worker_threads";
|
|
1202
|
+
import { collect, buildStatsPayload } from ${JSON.stringify(reportUrl)};
|
|
1203
|
+
|
|
1204
|
+
try {
|
|
1205
|
+
const payload = await buildStatsPayload(collect(), ${serializedInject});
|
|
1206
|
+
parentPort?.postMessage({ ok: true, payload });
|
|
1207
|
+
} catch (error) {
|
|
1208
|
+
parentPort?.postMessage({
|
|
1209
|
+
ok: false,
|
|
1210
|
+
message: error instanceof Error ? error.message : String(error),
|
|
1211
|
+
stack: error instanceof Error ? error.stack : undefined,
|
|
1212
|
+
});
|
|
1213
|
+
}
|
|
1214
|
+
`;
|
|
1215
|
+
const worker = new Worker(new URL(`data:text/javascript;charset=utf-8,${encodeURIComponent(code)}`));
|
|
1216
|
+
return new Promise((resolve, reject) => {
|
|
1217
|
+
let settled = false;
|
|
1218
|
+
const finish = (result) => {
|
|
1219
|
+
if (settled)
|
|
1220
|
+
return;
|
|
1221
|
+
settled = true;
|
|
1222
|
+
void worker.terminate();
|
|
1223
|
+
if (result.ok)
|
|
1224
|
+
resolve(result.payload);
|
|
1225
|
+
else
|
|
1226
|
+
reject(result.error);
|
|
1227
|
+
};
|
|
1228
|
+
worker.once("message", (message) => {
|
|
1229
|
+
const msg = message;
|
|
1230
|
+
if (msg.ok === true) {
|
|
1231
|
+
finish({ ok: true, payload: msg.payload });
|
|
1232
|
+
return;
|
|
1233
|
+
}
|
|
1234
|
+
const error = new Error(typeof msg.message === "string" ? msg.message : "Full local stats worker failed");
|
|
1235
|
+
if (typeof msg.stack === "string")
|
|
1236
|
+
error.stack = msg.stack;
|
|
1237
|
+
finish({ ok: false, error });
|
|
1238
|
+
});
|
|
1239
|
+
worker.once("error", (error) => {
|
|
1240
|
+
finish({ ok: false, error });
|
|
1241
|
+
});
|
|
1242
|
+
worker.once("exit", (code) => {
|
|
1243
|
+
if (settled)
|
|
1244
|
+
return;
|
|
1245
|
+
finish({ ok: false, error: new Error(`Full local stats worker exited (code ${code}) without a result`) });
|
|
1246
|
+
});
|
|
1247
|
+
});
|
|
1248
|
+
}
|
|
1249
|
+
export function createLocalDiscoveryCache(loaders = {}) {
|
|
1250
|
+
const loadQuick = loaders.loadQuick ?? (() => discoverMigratableFastOffThread());
|
|
1251
|
+
const loadExact = loaders.loadExact ?? (() => discoverMigratableSessionsOffThread());
|
|
1252
|
+
let quick = null;
|
|
1253
|
+
let quickPromise = null;
|
|
1254
|
+
let exact = null;
|
|
1255
|
+
let exactPromise = null;
|
|
1256
|
+
return {
|
|
1257
|
+
getQuick() {
|
|
1258
|
+
if (quick)
|
|
1259
|
+
return Promise.resolve(quick);
|
|
1260
|
+
if (quickPromise)
|
|
1261
|
+
return quickPromise;
|
|
1262
|
+
quickPromise = loadQuick()
|
|
1263
|
+
.then((discovery) => {
|
|
1264
|
+
quick = discovery;
|
|
1265
|
+
return discovery;
|
|
1266
|
+
})
|
|
1267
|
+
.catch((error) => {
|
|
1268
|
+
quickPromise = null;
|
|
1269
|
+
throw error;
|
|
1270
|
+
});
|
|
1271
|
+
return quickPromise;
|
|
1272
|
+
},
|
|
1273
|
+
getExact() {
|
|
1274
|
+
if (exact)
|
|
1275
|
+
return Promise.resolve(exact);
|
|
1276
|
+
if (exactPromise)
|
|
1277
|
+
return exactPromise;
|
|
1278
|
+
exactPromise = loadExact()
|
|
1279
|
+
.then((discovery) => {
|
|
1280
|
+
exact = discovery;
|
|
1281
|
+
return discovery;
|
|
1282
|
+
})
|
|
1283
|
+
.catch((error) => {
|
|
1284
|
+
exactPromise = null;
|
|
1285
|
+
throw error;
|
|
1286
|
+
});
|
|
1287
|
+
return exactPromise;
|
|
1288
|
+
},
|
|
1289
|
+
peekExact() {
|
|
1290
|
+
return exact;
|
|
1291
|
+
},
|
|
1292
|
+
pendingExact() {
|
|
1293
|
+
return exactPromise;
|
|
1294
|
+
},
|
|
1295
|
+
};
|
|
1296
|
+
}
|
|
1148
1297
|
function forensicStageLabel(stage) {
|
|
1149
1298
|
if (stage === "reading-transcripts")
|
|
1150
1299
|
return "Reading transcript files";
|
|
@@ -1166,7 +1315,9 @@ export function buildForensicReportOffThread(onProgress, options = {}) {
|
|
|
1166
1315
|
try {
|
|
1167
1316
|
const report = await buildForensicReport({
|
|
1168
1317
|
includeLegacyGoldenStandard: false,
|
|
1169
|
-
onProgress: (done, total, stage, detail, overall) => parentPort?.postMessage({
|
|
1318
|
+
onProgress: (done, total, stage, detail, overall, stageDone, stageTotal) => parentPort?.postMessage({
|
|
1319
|
+
progress: { done, total, stage, detail, overall, stageDone, stageTotal },
|
|
1320
|
+
}),
|
|
1170
1321
|
});
|
|
1171
1322
|
parentPort?.postMessage({ ok: true, report });
|
|
1172
1323
|
} catch (error) {
|
|
@@ -1259,6 +1410,7 @@ function publicRunningForensicProgress(value) {
|
|
|
1259
1410
|
? Math.min(1, Math.max(0, candidate))
|
|
1260
1411
|
: 0);
|
|
1261
1412
|
const total = safeCount(progress.total);
|
|
1413
|
+
const stageTotal = safeCount(progress.stageTotal);
|
|
1262
1414
|
const rawStage = typeof progress.stage === "string" ? progress.stage : "starting";
|
|
1263
1415
|
const stage = ["starting", "reading-transcripts", "building-summary", "classifying-repeated-context", "finalizing-report"].includes(rawStage)
|
|
1264
1416
|
? rawStage
|
|
@@ -1269,6 +1421,8 @@ function publicRunningForensicProgress(value) {
|
|
|
1269
1421
|
total,
|
|
1270
1422
|
stage,
|
|
1271
1423
|
label: forensicStageLabel(stage),
|
|
1424
|
+
stageDone: stageTotal > 0 ? Math.min(safeCount(progress.stageDone), stageTotal) : 0,
|
|
1425
|
+
stageTotal,
|
|
1272
1426
|
overall: safeFraction(progress.overall),
|
|
1273
1427
|
elapsedMs: safeDuration(progress.elapsedMs),
|
|
1274
1428
|
stageElapsedMs: safeDuration(progress.stageElapsedMs),
|
|
@@ -1303,6 +1457,7 @@ export function startCallbackServer(opts = {}) {
|
|
|
1303
1457
|
let switchAccountUrl = "";
|
|
1304
1458
|
let connected = Boolean(opts.initialToken?.token);
|
|
1305
1459
|
let activeDeviceToken = opts.initialToken?.token || "";
|
|
1460
|
+
let activeAccountEmail = "";
|
|
1306
1461
|
let pendingLocalAuth = null;
|
|
1307
1462
|
let reportConsentGranted = !requiresReportConsent;
|
|
1308
1463
|
let progress = { status: "idle", total: 0, completed: 0, running: 0, queued: 0, failed: 0, extracted: 0 };
|
|
@@ -1337,8 +1492,8 @@ export function startCallbackServer(opts = {}) {
|
|
|
1337
1492
|
return false;
|
|
1338
1493
|
return revokeDeviceToken(pending.token);
|
|
1339
1494
|
};
|
|
1340
|
-
const completeDeviceLogin = async (token) => {
|
|
1341
|
-
await authedAxios(token).post("/api/extension/mcp/local-auth/complete-device", {}, { timeout: 10_000 });
|
|
1495
|
+
const completeDeviceLogin = async (token, encryptionPreference = "maximum") => {
|
|
1496
|
+
await authedAxios(token).post("/api/extension/mcp/local-auth/complete-device", { encryptionPreference }, { timeout: 10_000 });
|
|
1342
1497
|
};
|
|
1343
1498
|
const close = () => {
|
|
1344
1499
|
if (timer)
|
|
@@ -1414,6 +1569,7 @@ export function startCallbackServer(opts = {}) {
|
|
|
1414
1569
|
const firstToken = !onToken.settled();
|
|
1415
1570
|
connected = true;
|
|
1416
1571
|
activeDeviceToken = token;
|
|
1572
|
+
activeAccountEmail = pendingLocalAuth?.email || activeAccountEmail;
|
|
1417
1573
|
pendingLocalAuth = null;
|
|
1418
1574
|
const callbackToken = { token, key };
|
|
1419
1575
|
if (firstToken) {
|
|
@@ -1571,6 +1727,38 @@ export function startCallbackServer(opts = {}) {
|
|
|
1571
1727
|
json(res, detail.status, { ok: false, error: detail.message });
|
|
1572
1728
|
}
|
|
1573
1729
|
};
|
|
1730
|
+
const handleLocalSkipEncryption = async (res, body) => {
|
|
1731
|
+
if (rejectIfLocalAuthBlocked(res, asString(body.nonce)))
|
|
1732
|
+
return;
|
|
1733
|
+
if (!pendingLocalAuth || pendingLocalAuth.expiresAtMs <= Date.now()) {
|
|
1734
|
+
await revokePendingLocalAuth();
|
|
1735
|
+
return void json(res, 409, {
|
|
1736
|
+
ok: false,
|
|
1737
|
+
error: "This local login expired. Send a new verification code.",
|
|
1738
|
+
reset: true,
|
|
1739
|
+
});
|
|
1740
|
+
}
|
|
1741
|
+
if (pendingLocalAuth.mode !== "setup") {
|
|
1742
|
+
return void json(res, 409, {
|
|
1743
|
+
ok: false,
|
|
1744
|
+
error: "This account already uses encrypted memory. Enter the vault passphrase to continue.",
|
|
1745
|
+
});
|
|
1746
|
+
}
|
|
1747
|
+
try {
|
|
1748
|
+
await completeDeviceLogin(pendingLocalAuth.token, "standard");
|
|
1749
|
+
resolveLocalToken(pendingLocalAuth.token, undefined);
|
|
1750
|
+
json(res, 200, {
|
|
1751
|
+
ok: true,
|
|
1752
|
+
connected: true,
|
|
1753
|
+
encryptionEnabled: false,
|
|
1754
|
+
encryptionPreference: "standard",
|
|
1755
|
+
});
|
|
1756
|
+
}
|
|
1757
|
+
catch (error) {
|
|
1758
|
+
const detail = publicAxiosError(error, "Failed to continue without encryption");
|
|
1759
|
+
json(res, detail.status, { ok: false, error: detail.message });
|
|
1760
|
+
}
|
|
1761
|
+
};
|
|
1574
1762
|
server = http.createServer((req, res) => {
|
|
1575
1763
|
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
1576
1764
|
res.setHeader("Access-Control-Allow-Headers", "Content-Type");
|
|
@@ -1635,6 +1823,18 @@ export function startCallbackServer(opts = {}) {
|
|
|
1635
1823
|
});
|
|
1636
1824
|
return;
|
|
1637
1825
|
}
|
|
1826
|
+
if (route === "/local-auth/skip-encryption" && req.method === "POST") {
|
|
1827
|
+
let body;
|
|
1828
|
+
try {
|
|
1829
|
+
body = await readJsonBody(req);
|
|
1830
|
+
}
|
|
1831
|
+
catch {
|
|
1832
|
+
text(res, 400, "bad json");
|
|
1833
|
+
return;
|
|
1834
|
+
}
|
|
1835
|
+
await handleLocalSkipEncryption(res, body);
|
|
1836
|
+
return;
|
|
1837
|
+
}
|
|
1638
1838
|
if (route === "/launch-agent" && req.method === "POST") {
|
|
1639
1839
|
let body;
|
|
1640
1840
|
try {
|
|
@@ -1771,6 +1971,9 @@ export function startCallbackServer(opts = {}) {
|
|
|
1771
1971
|
const plan = (asString(response.data?.plan) || "free").toLowerCase();
|
|
1772
1972
|
const trialUsed = response.data?.billing?.trialUsed === true;
|
|
1773
1973
|
const trialAvailable = response.data?.billing?.trialAvailable !== false;
|
|
1974
|
+
const accountEmail = asString(profileResponse?.data?.email) || activeAccountEmail;
|
|
1975
|
+
if (accountEmail)
|
|
1976
|
+
activeAccountEmail = accountEmail;
|
|
1774
1977
|
json(res, 200, {
|
|
1775
1978
|
plan,
|
|
1776
1979
|
paid: ["pro", "power", "team", "enterprise"].includes(plan),
|
|
@@ -1785,10 +1988,9 @@ export function startCallbackServer(opts = {}) {
|
|
|
1785
1988
|
memoryProcessingQuota: response.data?.memoryProcessingQuota ?? null,
|
|
1786
1989
|
memorySearchQuota: response.data?.memorySearchQuota ?? null,
|
|
1787
1990
|
activation: response.data?.activation ?? null,
|
|
1788
|
-
account:
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
avatarUrl: asString(profileResponse.data?.avatarUrl) || "",
|
|
1991
|
+
account: accountEmail ? {
|
|
1992
|
+
email: accountEmail,
|
|
1993
|
+
avatarUrl: asString(profileResponse?.data?.avatarUrl) || "",
|
|
1792
1994
|
} : null,
|
|
1793
1995
|
pricingUrl,
|
|
1794
1996
|
});
|
|
@@ -2083,22 +2285,25 @@ export function startCallbackServer(opts = {}) {
|
|
|
2083
2285
|
}
|
|
2084
2286
|
connected = false;
|
|
2085
2287
|
activeDeviceToken = "";
|
|
2288
|
+
activeAccountEmail = "";
|
|
2086
2289
|
const revokedPendingCredential = await revokePendingLocalAuth();
|
|
2087
2290
|
stats = null;
|
|
2088
2291
|
migrateStarted = false;
|
|
2089
2292
|
progress = { status: "idle", total: 0, completed: 0, running: 0, queued: 0, failed: 0, extracted: 0 };
|
|
2293
|
+
try {
|
|
2294
|
+
await logoutHandler?.();
|
|
2295
|
+
}
|
|
2296
|
+
catch (e) {
|
|
2297
|
+
console.error(`Could not reset local login state: ${e instanceof Error ? e.message : String(e)}`);
|
|
2298
|
+
}
|
|
2090
2299
|
json(res, 200, {
|
|
2091
2300
|
ok: true,
|
|
2301
|
+
connected: false,
|
|
2092
2302
|
authUrl,
|
|
2093
2303
|
switchAccountUrl: switchAccountUrl || authUrl,
|
|
2094
2304
|
localOnly: true,
|
|
2095
2305
|
revokedPendingCredential,
|
|
2096
2306
|
});
|
|
2097
|
-
Promise.resolve()
|
|
2098
|
-
.then(() => logoutHandler?.())
|
|
2099
|
-
.catch((e) => {
|
|
2100
|
-
console.error(`Could not reset local login state: ${e instanceof Error ? e.message : String(e)}`);
|
|
2101
|
-
});
|
|
2102
2307
|
return;
|
|
2103
2308
|
}
|
|
2104
2309
|
if (route === "/migrate" && req.method === "POST") {
|
|
@@ -2665,6 +2870,8 @@ async function cmdOnboarding(flags) {
|
|
|
2665
2870
|
total: 0,
|
|
2666
2871
|
stage: forensicStage,
|
|
2667
2872
|
label: forensicStageLabel(forensicStage),
|
|
2873
|
+
stageDone: 0,
|
|
2874
|
+
stageTotal: 0,
|
|
2668
2875
|
overall: 0,
|
|
2669
2876
|
elapsedMs: 0,
|
|
2670
2877
|
stageElapsedMs: 0,
|
|
@@ -2678,7 +2885,18 @@ async function cmdOnboarding(flags) {
|
|
|
2678
2885
|
requireReportConsent: true,
|
|
2679
2886
|
getStats: () => stats,
|
|
2680
2887
|
getReport: () => forensicReport,
|
|
2681
|
-
getReportProgress: () =>
|
|
2888
|
+
getReportProgress: () => {
|
|
2889
|
+
if (forensicProgress.status !== "running")
|
|
2890
|
+
return forensicProgress;
|
|
2891
|
+
const now = Date.now();
|
|
2892
|
+
const sinceWorkerUpdate = Math.max(0, now - forensicProgress.updatedAt);
|
|
2893
|
+
return {
|
|
2894
|
+
...forensicProgress,
|
|
2895
|
+
elapsedMs: forensicProgress.elapsedMs + sinceWorkerUpdate,
|
|
2896
|
+
stageElapsedMs: forensicProgress.stageElapsedMs + sinceWorkerUpdate,
|
|
2897
|
+
updatedAt: now,
|
|
2898
|
+
};
|
|
2899
|
+
},
|
|
2682
2900
|
onReportConsent: (allowed) => {
|
|
2683
2901
|
if (!allowed) {
|
|
2684
2902
|
forensicConsent = "declined";
|
|
@@ -2688,6 +2906,8 @@ async function cmdOnboarding(flags) {
|
|
|
2688
2906
|
total: 0,
|
|
2689
2907
|
stage: "failed",
|
|
2690
2908
|
label: "Local scan skipped",
|
|
2909
|
+
stageDone: 0,
|
|
2910
|
+
stageTotal: 0,
|
|
2691
2911
|
overall: 0,
|
|
2692
2912
|
elapsedMs: Date.now() - forensicStartedAt,
|
|
2693
2913
|
stageElapsedMs: Date.now() - forensicStageStartedAt,
|
|
@@ -2710,6 +2930,8 @@ async function cmdOnboarding(flags) {
|
|
|
2710
2930
|
total: 0,
|
|
2711
2931
|
stage: forensicStage,
|
|
2712
2932
|
label: forensicStageLabel(forensicStage),
|
|
2933
|
+
stageDone: 0,
|
|
2934
|
+
stageTotal: 0,
|
|
2713
2935
|
overall: 0,
|
|
2714
2936
|
elapsedMs: now - forensicStartedAt,
|
|
2715
2937
|
stageElapsedMs: 0,
|
|
@@ -2744,6 +2966,12 @@ async function cmdOnboarding(flags) {
|
|
|
2744
2966
|
stage: forensicStage,
|
|
2745
2967
|
label: forensicStageLabel(forensicStage),
|
|
2746
2968
|
detail: progress.detail,
|
|
2969
|
+
stageDone: typeof progress.stageDone === "number" && Number.isFinite(progress.stageDone)
|
|
2970
|
+
? Math.max(0, Math.floor(progress.stageDone))
|
|
2971
|
+
: 0,
|
|
2972
|
+
stageTotal: typeof progress.stageTotal === "number" && Number.isFinite(progress.stageTotal)
|
|
2973
|
+
? Math.max(0, Math.floor(progress.stageTotal))
|
|
2974
|
+
: 0,
|
|
2747
2975
|
overall: forensicOverall,
|
|
2748
2976
|
elapsedMs: now - forensicStartedAt,
|
|
2749
2977
|
stageElapsedMs: now - forensicStageStartedAt,
|
|
@@ -2761,6 +2989,8 @@ async function cmdOnboarding(flags) {
|
|
|
2761
2989
|
total: forensicProgress.total,
|
|
2762
2990
|
stage: "failed",
|
|
2763
2991
|
label: "Local scan failed",
|
|
2992
|
+
stageDone: forensicProgress.stageDone,
|
|
2993
|
+
stageTotal: forensicProgress.stageTotal,
|
|
2764
2994
|
overall: forensicOverall,
|
|
2765
2995
|
elapsedMs: now - forensicStartedAt,
|
|
2766
2996
|
stageElapsedMs: now - forensicStageStartedAt,
|
|
@@ -2799,13 +3029,22 @@ async function cmdOnboarding(flags) {
|
|
|
2799
3029
|
let exactDiscovery = Promise.resolve(null);
|
|
2800
3030
|
let refreshGeneration = 0;
|
|
2801
3031
|
let latestPendingEstimate = 0;
|
|
3032
|
+
// Local discovery is account-independent. Keep one snapshot for this onboarding process and
|
|
3033
|
+
// overlay each connected account's processed keys instead of rereading every JSONL on switch.
|
|
3034
|
+
const localDiscovery = createLocalDiscoveryCache({
|
|
3035
|
+
loadExact: async () => {
|
|
3036
|
+
// Publish the quick metadata result before starting the heavier exact worker.
|
|
3037
|
+
await delay(250);
|
|
3038
|
+
return discoverMigratableSessionsOffThread();
|
|
3039
|
+
},
|
|
3040
|
+
});
|
|
2802
3041
|
// The account's already-imported keys, shared so the /migrate sizing can assemble ONLY pending sessions.
|
|
2803
3042
|
let lastProcessedImportKeys = null;
|
|
2804
3043
|
const resetLocalLoginState = () => {
|
|
2805
3044
|
refreshGeneration++;
|
|
2806
3045
|
stats = null;
|
|
2807
3046
|
disc = null;
|
|
2808
|
-
exactDiscovery = Promise.resolve(
|
|
3047
|
+
exactDiscovery = localDiscovery.pendingExact() ?? Promise.resolve(localDiscovery.peekExact());
|
|
2809
3048
|
latestPendingEstimate = 0;
|
|
2810
3049
|
lastProcessedImportKeys = null;
|
|
2811
3050
|
srv.setStats(null);
|
|
@@ -2823,7 +3062,9 @@ async function cmdOnboarding(flags) {
|
|
|
2823
3062
|
const generation = ++refreshGeneration;
|
|
2824
3063
|
lastProcessedImportKeys = null; // shared with /migrate so it can assemble only the pending sessions
|
|
2825
3064
|
let importStatusUnavailable = false;
|
|
2826
|
-
const quickDiscovery =
|
|
3065
|
+
const quickDiscovery = await localDiscovery.getQuick();
|
|
3066
|
+
if (generation !== refreshGeneration)
|
|
3067
|
+
return;
|
|
2827
3068
|
const quick = summarizeFastMigratableDiscovery(quickDiscovery);
|
|
2828
3069
|
let migratable = migratableFromFastSummary(quick);
|
|
2829
3070
|
latestPendingEstimate = migratable.pending;
|
|
@@ -2931,23 +3172,7 @@ async function cmdOnboarding(flags) {
|
|
|
2931
3172
|
console.error(`Could not check this EchoMem account's import status quickly: ${e instanceof Error ? e.message : String(e)}`);
|
|
2932
3173
|
}
|
|
2933
3174
|
});
|
|
2934
|
-
exactDiscovery =
|
|
2935
|
-
const timer = setTimeout(() => {
|
|
2936
|
-
void (async () => {
|
|
2937
|
-
try {
|
|
2938
|
-
// Start the local sizing pass without waiting on cloud/account status. The
|
|
2939
|
-
// account check is useful for tighter counts, but extraction can safely start
|
|
2940
|
-
// from local candidates because the import path skips true duplicates.
|
|
2941
|
-
await delay(250);
|
|
2942
|
-
resolve(await discoverMigratableSessionsOffThread());
|
|
2943
|
-
}
|
|
2944
|
-
catch (e) {
|
|
2945
|
-
reject(e instanceof Error ? e : new Error(String(e)));
|
|
2946
|
-
}
|
|
2947
|
-
})();
|
|
2948
|
-
}, 250);
|
|
2949
|
-
timer.unref?.();
|
|
2950
|
-
}).then(async (exact) => {
|
|
3175
|
+
exactDiscovery = localDiscovery.getExact().then(async (exact) => {
|
|
2951
3176
|
if (generation !== refreshGeneration)
|
|
2952
3177
|
return disc;
|
|
2953
3178
|
const initialExact = lastProcessedImportKeys
|
|
@@ -3029,7 +3254,7 @@ async function cmdOnboarding(flags) {
|
|
|
3029
3254
|
failed: 0,
|
|
3030
3255
|
extracted: 0,
|
|
3031
3256
|
});
|
|
3032
|
-
const fullPayload = withCandidateSessions(await
|
|
3257
|
+
const fullPayload = withCandidateSessions(await buildCollectedStatsPayloadOffThread({
|
|
3033
3258
|
sessions: sessionSummary,
|
|
3034
3259
|
migratable,
|
|
3035
3260
|
discovery: { phase: "full", exact: true },
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@echomem/mcp",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.28",
|
|
4
4
|
"description": "EchoMem MCP bridge: cloud-first memory tools, local context HUD, and the Agent Doctor workspace forensics report (cost ledger + 3D repo city)",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|