@echomem/mcp 1.4.17 → 1.4.19
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/context-analysis/claude-canonical-adapter.js +315 -0
- package/dist/hud/cli.js +0 -0
- package/dist/hud/efficiency.js +447 -0
- package/dist/hud/electron-main.js +7 -2
- package/dist/hud/monitor.js +164 -50
- package/dist/hud/server.js +96 -16
- package/dist/hud/session-launcher.js +395 -0
- package/dist/hud/web.js +540 -177
- package/dist/index.js +54 -22
- package/dist/migrate.js +47 -11
- package/dist/setup-page/client-core.js +31 -1
- package/dist/setup-page/client-extraction.js +533 -149
- package/dist/setup-page/client-lifecycle.js +64 -43
- package/dist/setup-page/styles-extraction.js +1107 -148
- package/dist/setup-page/styles-foundation.js +20 -15
- package/dist/setup-page/styles-website-alignment.js +635 -0
- package/dist/setup-page/styles.js +2 -0
- package/dist/setup-preview.js +49 -0
- package/dist/setup.js +240 -21
- package/dist/v1-contract.js +8 -8
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -126,7 +126,7 @@ function formatUpgradeRequiredResult(error) {
|
|
|
126
126
|
pricingUrl,
|
|
127
127
|
});
|
|
128
128
|
return [
|
|
129
|
-
"ACTION REQUIRED: tell the user to start a
|
|
129
|
+
"ACTION REQUIRED: tell the user to start a 14-day Pro trial or choose a subscription at this exact URL:",
|
|
130
130
|
pricingUrl,
|
|
131
131
|
"When replying, include the exact URL above. Do not reply only with \"connect\" or \"upgrade\".",
|
|
132
132
|
"",
|
|
@@ -545,10 +545,16 @@ class EchoMemApiClient {
|
|
|
545
545
|
async fetchMemoryMap() {
|
|
546
546
|
try {
|
|
547
547
|
const enc = await this.encState(); // unencrypted → {enabled:false}; encrypted+locked → throws
|
|
548
|
-
|
|
549
|
-
|
|
548
|
+
// Tool-list decoration is not a user-initiated search and must not spend
|
|
549
|
+
// one of the account's weekly searches. Build the best-effort map from
|
|
550
|
+
// the ordinary paginated memory listing instead.
|
|
551
|
+
const response = await this.axios.get("/api/extension/memories?limit=200", { timeout: 6000 });
|
|
550
552
|
const data = enc.enabled ? await this.decryptResult(response.data, enc.key) : response.data;
|
|
551
|
-
const memories = Array.isArray(data?.
|
|
553
|
+
const memories = Array.isArray(data?.data)
|
|
554
|
+
? data.data
|
|
555
|
+
: Array.isArray(data?.memories)
|
|
556
|
+
? data.memories
|
|
557
|
+
: [];
|
|
552
558
|
const keys = [...new Set(memories.map((m) => String(m?.keys || "").trim()).filter(Boolean))];
|
|
553
559
|
if (!keys.length)
|
|
554
560
|
return undefined;
|
|
@@ -789,6 +795,7 @@ class EchoMemApiClient {
|
|
|
789
795
|
startDate,
|
|
790
796
|
endDate,
|
|
791
797
|
limit,
|
|
798
|
+
requestId: randomUUID(),
|
|
792
799
|
});
|
|
793
800
|
return enc.enabled ? await this.decryptResult(response.data, enc.key) : response.data;
|
|
794
801
|
}
|
|
@@ -833,21 +840,41 @@ class EchoMemApiClient {
|
|
|
833
840
|
// For an encrypted account, hand the server the key transiently in the X-Encryption-Key header
|
|
834
841
|
// so it encrypts at rest (mirrors the extension's write path, spec §3.1a). Locked → LockedError.
|
|
835
842
|
const enc = await this.encState();
|
|
836
|
-
const config =
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
843
|
+
const config = {
|
|
844
|
+
headers: {
|
|
845
|
+
"X-EchoMem-Request-Id": randomUUID(),
|
|
846
|
+
...(enc.enabled && enc.key ? { "X-Encryption-Key": enc.key } : {}),
|
|
847
|
+
},
|
|
848
|
+
};
|
|
849
|
+
try {
|
|
850
|
+
const response = await this.axios.post("/api/extension/memories/ingest", {
|
|
851
|
+
rawData,
|
|
852
|
+
sourceUrl: parsed.url,
|
|
853
|
+
source: parsed.source || "mcp_server",
|
|
854
|
+
title: parsed.title,
|
|
855
|
+
// Stable per-session id so multiple saves in this coding session group under one context.
|
|
856
|
+
conversationKey: this.sessionId,
|
|
857
|
+
passthrough: parsed.passthrough || false,
|
|
858
|
+
triggerMessage: parsed.triggerMessage ||
|
|
859
|
+
lastUserMessageFromMessages(parsed.messages) ||
|
|
860
|
+
lastUserMessageFromConversationText(parsed.conversation),
|
|
861
|
+
triggerMessageRole: parsed.triggerMessageRole || "user",
|
|
862
|
+
}, config);
|
|
863
|
+
return response.data;
|
|
864
|
+
}
|
|
865
|
+
catch (error) {
|
|
866
|
+
if (axios.isAxiosError(error) && error.response?.status === 429) {
|
|
867
|
+
const data = error.response.data;
|
|
868
|
+
if (data?.error === "MEMORY_PROCESSING_QUOTA_EXCEEDED") {
|
|
869
|
+
const message = typeof data.message === "string"
|
|
870
|
+
? data.message
|
|
871
|
+
: "Weekly memory-processing limit reached. This conversation was not saved.";
|
|
872
|
+
const upgrade = typeof data.upgradeUrl === "string" ? ` Upgrade: ${data.upgradeUrl}` : "";
|
|
873
|
+
throw new McpError(ErrorCode.InvalidRequest, `${message}${upgrade}`);
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
throw error;
|
|
877
|
+
}
|
|
851
878
|
}
|
|
852
879
|
async deleteMemory(args) {
|
|
853
880
|
const parsed = deleteMemorySchema.parse(args ?? {});
|
|
@@ -900,6 +927,7 @@ class EchoMemApiClient {
|
|
|
900
927
|
startDate: parsed.startDate,
|
|
901
928
|
endDate: parsed.endDate,
|
|
902
929
|
limit: parsed.limit,
|
|
930
|
+
requestId: randomUUID(),
|
|
903
931
|
});
|
|
904
932
|
return enc.enabled ? await this.decryptResult(response.data, enc.key) : response.data;
|
|
905
933
|
}
|
|
@@ -918,6 +946,7 @@ class EchoMemApiClient {
|
|
|
918
946
|
const response = await this.axios.post("/api/extension/memories/keywords", {
|
|
919
947
|
keywords: parsed.keywords,
|
|
920
948
|
limit: parsed.limit,
|
|
949
|
+
requestId: randomUUID(),
|
|
921
950
|
});
|
|
922
951
|
return enc.enabled ? await this.decryptResult(response.data, enc.key) : response.data;
|
|
923
952
|
}
|
|
@@ -1362,7 +1391,7 @@ Details: ${m.details || "N/A"}`)
|
|
|
1362
1391
|
rec.conversation_chars = text.length;
|
|
1363
1392
|
rec.save_source = typeof a?.source === "string" ? a.source : sourceFallback;
|
|
1364
1393
|
}
|
|
1365
|
-
const { success, memoriesExtracted, extractedMemories, contextId, capsuleId, passthrough: isPassthrough, error } = await this.client.saveConversation(enrichedArgs);
|
|
1394
|
+
const { success, memoriesExtracted, memoriesDiscarded, extractedMemories, contextId, capsuleId, passthrough: isPassthrough, error } = await this.client.saveConversation(enrichedArgs);
|
|
1366
1395
|
if (!success)
|
|
1367
1396
|
throw new Error(`EchoMem API Error: ${error}`);
|
|
1368
1397
|
if (rec)
|
|
@@ -1374,7 +1403,7 @@ Details: ${m.details || "N/A"}`)
|
|
|
1374
1403
|
typeof capsuleId === "string" && capsuleId ? `Capsule ID: ${capsuleId}` : "",
|
|
1375
1404
|
typeof contextId === "string" && contextId ? `Context: ${contextId}` : "",
|
|
1376
1405
|
"",
|
|
1377
|
-
`To reload this capsule in a fresh session
|
|
1406
|
+
`To reload this capsule in a fresh session: get_memories_by_context({ contextId: "${contextId}" })`,
|
|
1378
1407
|
].filter(Boolean).join("\n");
|
|
1379
1408
|
return { content: [{ type: "text", text }] };
|
|
1380
1409
|
}
|
|
@@ -1396,9 +1425,12 @@ Details: ${m.details || "N/A"}`)
|
|
|
1396
1425
|
.join("\n\n");
|
|
1397
1426
|
const text = [
|
|
1398
1427
|
`Successfully ingested conversation. Extracted ${memoriesExtracted} memory distinct events.`,
|
|
1428
|
+
typeof memoriesDiscarded === "number" && memoriesDiscarded > 0
|
|
1429
|
+
? `${memoriesDiscarded} additional memories were not stored because your active-memory limit was reached.`
|
|
1430
|
+
: "",
|
|
1399
1431
|
list,
|
|
1400
1432
|
saved.length
|
|
1401
|
-
? `Verify these captured the key facts.
|
|
1433
|
+
? `Verify these captured the key facts. Re-fetch this exact batch later by searching the ids above${typeof contextId === "string" && contextId ? ` (context ${contextId})` : ""}.`
|
|
1402
1434
|
: "",
|
|
1403
1435
|
].filter(Boolean).join("\n\n");
|
|
1404
1436
|
return { content: [{ type: "text", text }] };
|
package/dist/migrate.js
CHANGED
|
@@ -499,6 +499,11 @@ function parsePositiveIntFlag(value, name) {
|
|
|
499
499
|
throw new Error(`${name} must be a positive integer.`);
|
|
500
500
|
return n;
|
|
501
501
|
}
|
|
502
|
+
export async function cancelQueuedImportSession(client, sessionId) {
|
|
503
|
+
if (!sessionId)
|
|
504
|
+
throw new Error("IMPORT_SESSION_ID_REQUIRED");
|
|
505
|
+
await client.patch(`/api/extension/import-sessions/${encodeURIComponent(sessionId)}`, { action: "cancel" });
|
|
506
|
+
}
|
|
502
507
|
function isRecord(value) {
|
|
503
508
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
504
509
|
}
|
|
@@ -984,29 +989,51 @@ export async function startMigration(opts) {
|
|
|
984
989
|
userTz: tz,
|
|
985
990
|
encKey,
|
|
986
991
|
onProgress: opts.onProgress,
|
|
992
|
+
shouldStop: opts.shouldStop,
|
|
987
993
|
runId,
|
|
988
994
|
metricsFile,
|
|
989
995
|
selection: opts.selection,
|
|
990
996
|
});
|
|
991
|
-
|
|
997
|
+
const cancelQueued = async () => {
|
|
998
|
+
await cancelQueuedImportSession(client, session.id);
|
|
999
|
+
};
|
|
1000
|
+
return {
|
|
1001
|
+
sessionId: session.id,
|
|
1002
|
+
runId,
|
|
1003
|
+
jobCount: session.jobs.length,
|
|
1004
|
+
metricsFile,
|
|
1005
|
+
...(capped ? { capped } : {}),
|
|
1006
|
+
done,
|
|
1007
|
+
cancelQueued,
|
|
1008
|
+
};
|
|
992
1009
|
}
|
|
993
1010
|
async function runJobs(args) {
|
|
994
1011
|
const ledger = loadLedger();
|
|
995
1012
|
const reqTimes = [];
|
|
1013
|
+
let migrated = 0, extracted = 0, failed = 0, done = 0;
|
|
1014
|
+
let stoppedReason;
|
|
1015
|
+
const stopIfRequested = () => {
|
|
1016
|
+
if (args.shouldStop?.()) {
|
|
1017
|
+
stoppedReason = "paused";
|
|
1018
|
+
return true;
|
|
1019
|
+
}
|
|
1020
|
+
return false;
|
|
1021
|
+
};
|
|
996
1022
|
const throttle = async () => {
|
|
997
1023
|
for (;;) {
|
|
1024
|
+
if (stopIfRequested())
|
|
1025
|
+
return false;
|
|
998
1026
|
const now = Date.now();
|
|
999
1027
|
while (reqTimes.length && now - reqTimes[0] > RATE_WINDOW_MS)
|
|
1000
1028
|
reqTimes.shift();
|
|
1001
1029
|
if (reqTimes.length < RATE_MAX) {
|
|
1002
1030
|
reqTimes.push(Date.now());
|
|
1003
|
-
return;
|
|
1031
|
+
return true;
|
|
1004
1032
|
}
|
|
1005
|
-
|
|
1033
|
+
// Check a pause request promptly instead of making the user wait for the rate window.
|
|
1034
|
+
await sleep(Math.min(250, RATE_WINDOW_MS - (now - reqTimes[0]) + 100));
|
|
1006
1035
|
}
|
|
1007
1036
|
};
|
|
1008
|
-
let migrated = 0, extracted = 0, failed = 0, done = 0;
|
|
1009
|
-
let stoppedReason;
|
|
1010
1037
|
const total = args.session.jobs.length;
|
|
1011
1038
|
const recordMetric = (metric) => {
|
|
1012
1039
|
try {
|
|
@@ -1021,8 +1048,9 @@ async function runJobs(args) {
|
|
|
1021
1048
|
const runOne = async (job, s) => {
|
|
1022
1049
|
const jobStartedAt = Date.now();
|
|
1023
1050
|
try {
|
|
1024
|
-
await throttle()
|
|
1025
|
-
|
|
1051
|
+
if (!await throttle())
|
|
1052
|
+
return;
|
|
1053
|
+
if (stoppedReason || stopIfRequested())
|
|
1026
1054
|
return; // an earlier job hit an unrecoverable stop — don't start more work
|
|
1027
1055
|
const r = await runImportJob(args.client, job.id, s, args.userTz, args.encKey);
|
|
1028
1056
|
extracted += r.memories;
|
|
@@ -1071,7 +1099,7 @@ async function runJobs(args) {
|
|
|
1071
1099
|
let next = 0;
|
|
1072
1100
|
const worker = async () => {
|
|
1073
1101
|
for (;;) {
|
|
1074
|
-
if (stoppedReason)
|
|
1102
|
+
if (stoppedReason || stopIfRequested())
|
|
1075
1103
|
return;
|
|
1076
1104
|
const myIdx = next++;
|
|
1077
1105
|
if (myIdx >= total)
|
|
@@ -1084,7 +1112,8 @@ async function runJobs(args) {
|
|
|
1084
1112
|
}
|
|
1085
1113
|
};
|
|
1086
1114
|
try {
|
|
1087
|
-
|
|
1115
|
+
const workerCount = Math.min(MIGRATE_CONCURRENCY, Math.max(1, args.session.concurrency), Math.max(1, total));
|
|
1116
|
+
await Promise.all(Array.from({ length: workerCount }, () => worker()));
|
|
1088
1117
|
}
|
|
1089
1118
|
catch {
|
|
1090
1119
|
failed++;
|
|
@@ -1224,7 +1253,7 @@ export async function cmdMigrate(flags) {
|
|
|
1224
1253
|
},
|
|
1225
1254
|
});
|
|
1226
1255
|
if (h.capped)
|
|
1227
|
-
console.log(c.yellow(`Your plan
|
|
1256
|
+
console.log(c.yellow(`Your plan has ${h.capped} historical slots remaining — importing the newest ${h.capped}. Upgrade to import more.`));
|
|
1228
1257
|
console.log(c.dim(`Import session ${h.sessionId} — ${h.jobCount} jobs queued (the web dashboard can watch this live).`));
|
|
1229
1258
|
console.log(c.dim(`Metrics: ${h.metricsFile}`));
|
|
1230
1259
|
const r = await h.done;
|
|
@@ -1271,7 +1300,14 @@ async function createImportSession(client, sessions, bareId, userTz, signal) {
|
|
|
1271
1300
|
}));
|
|
1272
1301
|
const res = await client.post("/api/extension/import-sessions", { items }, signal ? { signal } : undefined);
|
|
1273
1302
|
const data = res.data || {};
|
|
1274
|
-
|
|
1303
|
+
const serverConcurrency = Number(data.session?.concurrency);
|
|
1304
|
+
return {
|
|
1305
|
+
id: String(data.session?.id || ""),
|
|
1306
|
+
concurrency: Number.isFinite(serverConcurrency) && serverConcurrency > 0
|
|
1307
|
+
? Math.min(MIGRATE_CONCURRENCY, Math.floor(serverConcurrency))
|
|
1308
|
+
: MIGRATE_CONCURRENCY,
|
|
1309
|
+
jobs: Array.isArray(data.jobs) ? data.jobs : [],
|
|
1310
|
+
};
|
|
1275
1311
|
}
|
|
1276
1312
|
/** Run one queued job: stream its transcript to the server, which extracts it. Retries transient locks/rate limits. */
|
|
1277
1313
|
async function runImportJob(client, jobId, s, userTz, encKey) {
|
|
@@ -13,16 +13,27 @@ export const SETUP_PAGE_CLIENT_CORE = String.raw ` var params = new URLSear
|
|
|
13
13
|
var statsSlow = false;
|
|
14
14
|
var decisionMade = false;
|
|
15
15
|
var connected = false;
|
|
16
|
+
var localHistoryConsentGranted = false;
|
|
16
17
|
var extractMounted = false; // WebGL extraction-plate iframe mounted once; persists across /progress polls
|
|
18
|
+
var lastExtractionProgress = null; // restores the live extraction view if ending fails
|
|
19
|
+
var extractionRecoveryMessage = ""; // persists pause failures across resumed progress updates
|
|
17
20
|
var exStartAt = 0; // extraction start (ms) — drives the countdown
|
|
18
21
|
var exDeadline = 0; // first honest ETA deadline; passing it while running → "taking longer" banner
|
|
19
22
|
var extractionEnded = false; // user chose to leave the extraction flow before natural completion
|
|
23
|
+
var progressPollRunId = 0; // invalidates older progress loops when extraction resumes
|
|
20
24
|
var dashMounted = false; // dashboard shell (incl. plate iframe) mounted once; persists across /stats polls
|
|
21
25
|
var reportMounted = false; // report shell (city iframe) mounted once; loading is an overlay on it, not a separate page
|
|
22
26
|
var authUrl = "";
|
|
27
|
+
var switchAccountUrl = "";
|
|
23
28
|
var workspacePath = "";
|
|
24
29
|
var billingStatus = null;
|
|
25
|
-
var
|
|
30
|
+
var billingStatusLoading = false;
|
|
31
|
+
var setupPlanChoice = "";
|
|
32
|
+
var selectedSessionKeys = Object.create(null);
|
|
33
|
+
var sessionSelectionTouched = false;
|
|
34
|
+
var sessionPickerQuery = "";
|
|
35
|
+
var sessionPickerSource = "all";
|
|
36
|
+
var billingPollTimer = null;
|
|
26
37
|
var authWindow = null;
|
|
27
38
|
var connectionPollStarted = false;
|
|
28
39
|
var statsPollStarted = false;
|
|
@@ -34,6 +45,20 @@ export const SETUP_PAGE_CLIENT_CORE = String.raw ` var params = new URLSear
|
|
|
34
45
|
return { "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[ch];
|
|
35
46
|
});
|
|
36
47
|
}
|
|
48
|
+
function setupIcon(name) {
|
|
49
|
+
var paths = {
|
|
50
|
+
"shield-check": '<path d="M12 3 5 6v5c0 4.6 2.9 8.1 7 10 4.1-1.9 7-5.4 7-10V6l-7-3Z"/><path d="m8.8 12.1 2 2 4.4-4.5"/>',
|
|
51
|
+
"folder-read": '<path d="M3 7.5h6l2-2h4.5A2.5 2.5 0 0 1 18 8v1"/><path d="M3 7.5V18a2 2 0 0 0 2 2h7"/><circle cx="17" cy="15" r="3.5"/><path d="m19.5 17.5 2 2"/>',
|
|
52
|
+
"report": '<path d="M5 20V10"/><path d="M12 20V4"/><path d="M19 20v-7"/><path d="M3 20h18"/>',
|
|
53
|
+
"cloud-off": '<path d="m3 3 18 18"/><path d="M6.7 6.7A5.5 5.5 0 0 0 6.5 17H17"/><path d="M10.7 5.2A7 7 0 0 1 19 12.1 4 4 0 0 1 19.5 19"/>',
|
|
54
|
+
"list-check": '<rect x="4" y="4" width="16" height="16" rx="3"/><path d="m7.5 9 1.4 1.4L11.5 8"/><path d="M13.5 9h3"/><path d="m7.5 14 1.4 1.4 2.6-2.4"/><path d="M13.5 14h3"/>',
|
|
55
|
+
"pause": '<path d="M9 7v10"/><path d="M15 7v10"/>',
|
|
56
|
+
"check": '<path d="m6.5 12.2 3.5 3.5 7.5-7.5"/>',
|
|
57
|
+
"copy": '<rect x="8" y="8" width="11" height="11" rx="2"/><path d="M16 8V6a2 2 0 0 0-2-2H6a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h2"/>',
|
|
58
|
+
"arrow-right": '<path d="M5 12h14"/><path d="m14 7 5 5-5 5"/>'
|
|
59
|
+
};
|
|
60
|
+
return '<svg class="setupGlyph" viewBox="0 0 24 24" fill="none" aria-hidden="true" focusable="false"><g stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">' + (paths[name] || "") + '</g></svg>';
|
|
61
|
+
}
|
|
37
62
|
function setHead(nextTitle, nextStatus) {
|
|
38
63
|
title.textContent = nextTitle;
|
|
39
64
|
status.textContent = nextStatus;
|
|
@@ -151,6 +176,11 @@ export const SETUP_PAGE_CLIENT_CORE = String.raw ` var params = new URLSear
|
|
|
151
176
|
});
|
|
152
177
|
}
|
|
153
178
|
function onConnectClick() {
|
|
179
|
+
if (!localHistoryConsentGranted) {
|
|
180
|
+
renderLocalScanConsent();
|
|
181
|
+
setConsentStatus("Local history access is required before you can connect EchoMem or continue setup.", "required");
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
154
184
|
if (connected) { void waitForStats(); return; }
|
|
155
185
|
if (!openAuthWindow(authUrl)) showPopupFallback("", authUrl);
|
|
156
186
|
startConnectionPoll();
|