@echomem/mcp 1.4.16 → 1.4.18
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/hud/electron-main.js +7 -2
- package/dist/hud/monitor.js +164 -50
- package/dist/hud/server.js +79 -3
- package/dist/hud/session-launcher.js +395 -0
- package/dist/hud/web.js +336 -164
- package/dist/index.js +4 -1
- package/dist/migrate.js +19 -7
- package/dist/setup-page/client-extraction.js +16 -4
- package/dist/setup.js +83 -1
- package/dist/v1-contract.js +3 -2
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -126,10 +126,13 @@ function formatUpgradeRequiredResult(error) {
|
|
|
126
126
|
pricingUrl,
|
|
127
127
|
});
|
|
128
128
|
return [
|
|
129
|
+
"ACTION REQUIRED: tell the user to start a 7-day Pro trial or choose a subscription at this exact URL:",
|
|
130
|
+
pricingUrl,
|
|
131
|
+
"When replying, include the exact URL above. Do not reply only with \"connect\" or \"upgrade\".",
|
|
132
|
+
"",
|
|
129
133
|
message,
|
|
130
134
|
"",
|
|
131
135
|
code ? `Reason: ${code}.` : "",
|
|
132
|
-
`Open pricing: ${pricingUrl}`,
|
|
133
136
|
].filter(Boolean).join("\n");
|
|
134
137
|
}
|
|
135
138
|
/** Map a thrown error to the telemetry error_kind taxonomy. */
|
package/dist/migrate.js
CHANGED
|
@@ -984,6 +984,7 @@ export async function startMigration(opts) {
|
|
|
984
984
|
userTz: tz,
|
|
985
985
|
encKey,
|
|
986
986
|
onProgress: opts.onProgress,
|
|
987
|
+
shouldStop: opts.shouldStop,
|
|
987
988
|
runId,
|
|
988
989
|
metricsFile,
|
|
989
990
|
selection: opts.selection,
|
|
@@ -993,20 +994,30 @@ export async function startMigration(opts) {
|
|
|
993
994
|
async function runJobs(args) {
|
|
994
995
|
const ledger = loadLedger();
|
|
995
996
|
const reqTimes = [];
|
|
997
|
+
let migrated = 0, extracted = 0, failed = 0, done = 0;
|
|
998
|
+
let stoppedReason;
|
|
999
|
+
const stopIfRequested = () => {
|
|
1000
|
+
if (args.shouldStop?.()) {
|
|
1001
|
+
stoppedReason = "paused";
|
|
1002
|
+
return true;
|
|
1003
|
+
}
|
|
1004
|
+
return false;
|
|
1005
|
+
};
|
|
996
1006
|
const throttle = async () => {
|
|
997
1007
|
for (;;) {
|
|
1008
|
+
if (stopIfRequested())
|
|
1009
|
+
return false;
|
|
998
1010
|
const now = Date.now();
|
|
999
1011
|
while (reqTimes.length && now - reqTimes[0] > RATE_WINDOW_MS)
|
|
1000
1012
|
reqTimes.shift();
|
|
1001
1013
|
if (reqTimes.length < RATE_MAX) {
|
|
1002
1014
|
reqTimes.push(Date.now());
|
|
1003
|
-
return;
|
|
1015
|
+
return true;
|
|
1004
1016
|
}
|
|
1005
|
-
|
|
1017
|
+
// Check a pause request promptly instead of making the user wait for the rate window.
|
|
1018
|
+
await sleep(Math.min(250, RATE_WINDOW_MS - (now - reqTimes[0]) + 100));
|
|
1006
1019
|
}
|
|
1007
1020
|
};
|
|
1008
|
-
let migrated = 0, extracted = 0, failed = 0, done = 0;
|
|
1009
|
-
let stoppedReason;
|
|
1010
1021
|
const total = args.session.jobs.length;
|
|
1011
1022
|
const recordMetric = (metric) => {
|
|
1012
1023
|
try {
|
|
@@ -1021,8 +1032,9 @@ async function runJobs(args) {
|
|
|
1021
1032
|
const runOne = async (job, s) => {
|
|
1022
1033
|
const jobStartedAt = Date.now();
|
|
1023
1034
|
try {
|
|
1024
|
-
await throttle()
|
|
1025
|
-
|
|
1035
|
+
if (!await throttle())
|
|
1036
|
+
return;
|
|
1037
|
+
if (stoppedReason || stopIfRequested())
|
|
1026
1038
|
return; // an earlier job hit an unrecoverable stop — don't start more work
|
|
1027
1039
|
const r = await runImportJob(args.client, job.id, s, args.userTz, args.encKey);
|
|
1028
1040
|
extracted += r.memories;
|
|
@@ -1071,7 +1083,7 @@ async function runJobs(args) {
|
|
|
1071
1083
|
let next = 0;
|
|
1072
1084
|
const worker = async () => {
|
|
1073
1085
|
for (;;) {
|
|
1074
|
-
if (stoppedReason)
|
|
1086
|
+
if (stoppedReason || stopIfRequested())
|
|
1075
1087
|
return;
|
|
1076
1088
|
const myIdx = next++;
|
|
1077
1089
|
if (myIdx >= total)
|
|
@@ -610,11 +610,23 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
610
610
|
renderEnding();
|
|
611
611
|
try {
|
|
612
612
|
await postJson("/skip", {});
|
|
613
|
-
|
|
614
|
-
|
|
613
|
+
await sleep(250);
|
|
614
|
+
renderSkipped();
|
|
615
|
+
} catch (error) {
|
|
616
|
+
// A paused screen is a promise: do not show it until the bridge confirms that no more
|
|
617
|
+
// conversations will be scheduled. Keep the active view visible when pausing fails.
|
|
618
|
+
extractionEnded = false;
|
|
619
|
+
var message = error && error.message ? error.message : "Could not pause extraction. It is still running.";
|
|
620
|
+
try {
|
|
621
|
+
renderProgress(await getJson("/progress"));
|
|
622
|
+
var notice = document.getElementById("exText");
|
|
623
|
+
if (notice) notice.insertAdjacentHTML("afterbegin", '<div class="error">' + esc(message) + '</div>');
|
|
624
|
+
} catch (_) {
|
|
625
|
+
setHead("Could not pause extraction", "Still extracting");
|
|
626
|
+
app.className = "notice";
|
|
627
|
+
app.innerHTML = '<h2>Extraction may still be running.</h2><p>' + esc(message) + '</p><p>Keep this page open and try ending it again in a moment.</p>';
|
|
628
|
+
}
|
|
615
629
|
}
|
|
616
|
-
await sleep(650);
|
|
617
|
-
renderSkipped();
|
|
618
630
|
}
|
|
619
631
|
async function skip() {
|
|
620
632
|
if (decisionMade) return;
|
package/dist/setup.js
CHANGED
|
@@ -1088,6 +1088,7 @@ export function startCallbackServer(opts = {}) {
|
|
|
1088
1088
|
let migrateStarted = false;
|
|
1089
1089
|
let tokenRefreshHandler = null;
|
|
1090
1090
|
let logoutHandler = null;
|
|
1091
|
+
let migrationPauseHandler = null;
|
|
1091
1092
|
let timer;
|
|
1092
1093
|
let closed = false;
|
|
1093
1094
|
let server;
|
|
@@ -1463,6 +1464,23 @@ export function startCallbackServer(opts = {}) {
|
|
|
1463
1464
|
}
|
|
1464
1465
|
if (!checkNonce(asString(body.nonce)))
|
|
1465
1466
|
return void text(res, 403, "bad nonce");
|
|
1467
|
+
if (migrateStarted) {
|
|
1468
|
+
if (!migrationPauseHandler) {
|
|
1469
|
+
json(res, 409, { error: "MIGRATION_PAUSE_UNAVAILABLE", message: "Extraction is still starting. Try again in a moment." });
|
|
1470
|
+
return;
|
|
1471
|
+
}
|
|
1472
|
+
try {
|
|
1473
|
+
// Do not claim the work is paused until the import loop has stopped scheduling jobs.
|
|
1474
|
+
await migrationPauseHandler();
|
|
1475
|
+
}
|
|
1476
|
+
catch (error) {
|
|
1477
|
+
json(res, 500, {
|
|
1478
|
+
error: "MIGRATION_PAUSE_FAILED",
|
|
1479
|
+
message: error instanceof Error ? error.message : "Could not pause extraction.",
|
|
1480
|
+
});
|
|
1481
|
+
return;
|
|
1482
|
+
}
|
|
1483
|
+
}
|
|
1466
1484
|
json(res, 200, { ok: true });
|
|
1467
1485
|
decision.resolve("skip");
|
|
1468
1486
|
close();
|
|
@@ -1496,6 +1514,9 @@ export function startCallbackServer(opts = {}) {
|
|
|
1496
1514
|
setLogoutHandler: (handler) => {
|
|
1497
1515
|
logoutHandler = handler;
|
|
1498
1516
|
},
|
|
1517
|
+
setMigrationPauseHandler: (handler) => {
|
|
1518
|
+
migrationPauseHandler = handler;
|
|
1519
|
+
},
|
|
1499
1520
|
setAuthUrl: (url, nextSwitchAccountUrl) => {
|
|
1500
1521
|
authUrl = url;
|
|
1501
1522
|
switchAccountUrl = nextSwitchAccountUrl || url;
|
|
@@ -2199,6 +2220,15 @@ async function cmdLogin(flags) {
|
|
|
2199
2220
|
await refreshLocalStatsForToken(nextToken);
|
|
2200
2221
|
});
|
|
2201
2222
|
await refreshLocalStatsForToken(token);
|
|
2223
|
+
// A pause is deliberately transient. Completed conversations remain recorded in the normal
|
|
2224
|
+
// import ledger; unfinished conversations are rediscovered and offered again by the next init.
|
|
2225
|
+
let pauseRequested = false;
|
|
2226
|
+
let pauseCompletion = null;
|
|
2227
|
+
srv.setMigrationPauseHandler(async () => {
|
|
2228
|
+
pauseRequested = true;
|
|
2229
|
+
if (pauseCompletion)
|
|
2230
|
+
await pauseCompletion;
|
|
2231
|
+
});
|
|
2202
2232
|
const choice = await srv.decision;
|
|
2203
2233
|
if (choice === "migrate") {
|
|
2204
2234
|
const { res } = await srv.migrateRequest;
|
|
@@ -2214,6 +2244,14 @@ async function cmdLogin(flags) {
|
|
|
2214
2244
|
let progressDone = 0;
|
|
2215
2245
|
let progressFailed = 0;
|
|
2216
2246
|
let progressExtracted = 0;
|
|
2247
|
+
let resolvePauseCompletion = null;
|
|
2248
|
+
pauseCompletion = new Promise((resolve) => {
|
|
2249
|
+
resolvePauseCompletion = resolve;
|
|
2250
|
+
});
|
|
2251
|
+
const completePause = () => {
|
|
2252
|
+
resolvePauseCompletion?.();
|
|
2253
|
+
resolvePauseCompletion = null;
|
|
2254
|
+
};
|
|
2217
2255
|
if (!disc) {
|
|
2218
2256
|
srv.setProgress({
|
|
2219
2257
|
status: "starting",
|
|
@@ -2334,7 +2372,12 @@ async function cmdLogin(flags) {
|
|
|
2334
2372
|
let h;
|
|
2335
2373
|
try {
|
|
2336
2374
|
const controller = new AbortController();
|
|
2337
|
-
h = await withTimeout(startMigration({
|
|
2375
|
+
h = await withTimeout(startMigration({
|
|
2376
|
+
pending: remaining,
|
|
2377
|
+
signal: controller.signal,
|
|
2378
|
+
onProgress: onBatchProgress,
|
|
2379
|
+
shouldStop: () => pauseRequested,
|
|
2380
|
+
}), 30_000, "IMPORT_START_TIMEOUT", () => controller.abort());
|
|
2338
2381
|
}
|
|
2339
2382
|
catch (batchError) {
|
|
2340
2383
|
if (batchIndex === 1)
|
|
@@ -2353,6 +2396,10 @@ async function cmdLogin(flags) {
|
|
|
2353
2396
|
}
|
|
2354
2397
|
console.log(`Migration metrics (batch ${batchIndex}): ${h.metricsFile}`);
|
|
2355
2398
|
const r = await h.done;
|
|
2399
|
+
if (r.stoppedReason === "paused" || pauseRequested) {
|
|
2400
|
+
stoppedReason = "paused";
|
|
2401
|
+
break;
|
|
2402
|
+
}
|
|
2356
2403
|
if (r.stoppedReason) {
|
|
2357
2404
|
stoppedReason = r.stoppedReason;
|
|
2358
2405
|
break;
|
|
@@ -2364,6 +2411,25 @@ async function cmdLogin(flags) {
|
|
|
2364
2411
|
}
|
|
2365
2412
|
break;
|
|
2366
2413
|
}
|
|
2414
|
+
if (stoppedReason === "paused") {
|
|
2415
|
+
srv.setProgress({
|
|
2416
|
+
status: "paused",
|
|
2417
|
+
sessionId: activeSessionId || undefined,
|
|
2418
|
+
jobCount: activeJobCount,
|
|
2419
|
+
total: activeJobCount,
|
|
2420
|
+
completed: progressDone,
|
|
2421
|
+
running: 0,
|
|
2422
|
+
queued: Math.max(0, activeJobCount - progressDone - progressFailed),
|
|
2423
|
+
failed: progressFailed,
|
|
2424
|
+
extracted: progressExtracted,
|
|
2425
|
+
latest: "Paused by user. Re-run setup to continue the remaining conversations.",
|
|
2426
|
+
});
|
|
2427
|
+
console.log(`Import paused: ${progressDone} imported, ${progressExtracted} memories, ${progressFailed} failed.`);
|
|
2428
|
+
completePause();
|
|
2429
|
+
// /skip sends the browser acknowledgement and closes the localhost bridge after this
|
|
2430
|
+
// safe pause boundary. Do not close it here first or the page can claim success early.
|
|
2431
|
+
return true;
|
|
2432
|
+
}
|
|
2367
2433
|
srv.setProgress({
|
|
2368
2434
|
status: stoppedReason ? "failed" : "completed",
|
|
2369
2435
|
sessionId: activeSessionId || undefined,
|
|
@@ -2384,6 +2450,22 @@ async function cmdLogin(flags) {
|
|
|
2384
2450
|
srv.close();
|
|
2385
2451
|
}
|
|
2386
2452
|
catch (e) {
|
|
2453
|
+
if (pauseRequested) {
|
|
2454
|
+
srv.setProgress({
|
|
2455
|
+
status: "paused",
|
|
2456
|
+
sessionId: activeSessionId || undefined,
|
|
2457
|
+
jobCount: activeJobCount,
|
|
2458
|
+
total: activeJobCount,
|
|
2459
|
+
completed: progressDone,
|
|
2460
|
+
running: 0,
|
|
2461
|
+
queued: Math.max(0, activeJobCount - progressDone - progressFailed),
|
|
2462
|
+
failed: progressFailed,
|
|
2463
|
+
extracted: progressExtracted,
|
|
2464
|
+
latest: "Paused by user. Re-run setup to continue the remaining conversations.",
|
|
2465
|
+
});
|
|
2466
|
+
completePause();
|
|
2467
|
+
return true;
|
|
2468
|
+
}
|
|
2387
2469
|
srv.setProgress({
|
|
2388
2470
|
status: "failed",
|
|
2389
2471
|
sessionId: activeSessionId || undefined,
|
package/dist/v1-contract.js
CHANGED
|
@@ -109,6 +109,7 @@ export function listToolSpecs(opts = {}) {
|
|
|
109
109
|
const map = opts.map?.trim();
|
|
110
110
|
const updateNotice = opts.updateNotice?.trim();
|
|
111
111
|
const paidRecallNote = "Requires Echo Pro or Power; saving conversations and memory extraction remain included.";
|
|
112
|
+
const searchBillingReplyInstruction = "If search returns an ACTION REQUIRED subscription message, tell the user to start their trial or subscription and include the exact URL from that result verbatim. Do not respond only with \"connect\" or \"upgrade\".";
|
|
112
113
|
const updateSection = updateNotice ? `\n\nUPDATE NOTICE: ${updateNotice}` : "";
|
|
113
114
|
const mapSection = map
|
|
114
115
|
? `\n\nThis user's EchoMem currently covers these topics (a relevance guide — recall when the task relates to one of them):\n${map}\n`
|
|
@@ -116,7 +117,7 @@ export function listToolSpecs(opts = {}) {
|
|
|
116
117
|
return [
|
|
117
118
|
{
|
|
118
119
|
name: canonicalToolNames.search,
|
|
119
|
-
description: withMcpVersion(`Recall the user's prior decisions, preferences, and project context from EchoMem — their long-term memory across ALL their AI tools, not just this session. Use it instead of re-deriving or re-asking what the user already settled. ${paidRecallNote}${mapSection}\nReturns the ranked memories; set includeAnswer=true only if you need the legacy synthesized answer. Current time: ${currentTime}.${updateSection}`),
|
|
120
|
+
description: withMcpVersion(`Recall the user's prior decisions, preferences, and project context from EchoMem — their long-term memory across ALL their AI tools, not just this session. Use it instead of re-deriving or re-asking what the user already settled. ${paidRecallNote} ${searchBillingReplyInstruction}${mapSection}\nReturns the ranked memories; set includeAnswer=true only if you need the legacy synthesized answer. Current time: ${currentTime}.${updateSection}`),
|
|
120
121
|
inputSchema: {
|
|
121
122
|
type: "object",
|
|
122
123
|
properties: {
|
|
@@ -139,7 +140,7 @@ export function listToolSpecs(opts = {}) {
|
|
|
139
140
|
},
|
|
140
141
|
{
|
|
141
142
|
name: "search_memories_by_description_semantic",
|
|
142
|
-
description: `Legacy alias for search_memories. ${paidRecallNote}`,
|
|
143
|
+
description: `Legacy alias for search_memories. ${paidRecallNote} ${searchBillingReplyInstruction}`,
|
|
143
144
|
inputSchema: {
|
|
144
145
|
type: "object",
|
|
145
146
|
properties: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@echomem/mcp",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.18",
|
|
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",
|