@echomem/mcp 1.4.18 → 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/server.js +17 -13
- package/dist/hud/web.js +204 -13
- package/dist/index.js +54 -22
- package/dist/migrate.js +28 -4
- package/dist/setup-page/client-core.js +31 -1
- package/dist/setup-page/client-extraction.js +528 -156
- 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 +162 -25
- package/dist/v1-contract.js +8 -8
- package/package.json +1 -1
package/dist/hud/web.js
CHANGED
|
@@ -678,6 +678,85 @@ export const HUD_HTML = String.raw `<!doctype html>
|
|
|
678
678
|
box-shadow: 0 12px 20px -16px rgba(122, 36, 30, 0.95);
|
|
679
679
|
}
|
|
680
680
|
.billing-link:hover { filter: brightness(0.96); }
|
|
681
|
+
.quota-card {
|
|
682
|
+
display: none;
|
|
683
|
+
margin: 0 0 10px;
|
|
684
|
+
padding: 12px 13px 13px;
|
|
685
|
+
border: 1px solid rgba(26, 58, 143, 0.18);
|
|
686
|
+
border-radius: 12px;
|
|
687
|
+
background: rgba(255, 255, 255, 0.72);
|
|
688
|
+
box-shadow: 0 1px 5px rgba(26, 58, 143, 0.05);
|
|
689
|
+
}
|
|
690
|
+
.quota-head {
|
|
691
|
+
display: flex;
|
|
692
|
+
align-items: center;
|
|
693
|
+
gap: 7px;
|
|
694
|
+
margin-bottom: 9px;
|
|
695
|
+
}
|
|
696
|
+
.quota-title {
|
|
697
|
+
color: var(--ink);
|
|
698
|
+
font-size: 10.5px;
|
|
699
|
+
font-weight: 850;
|
|
700
|
+
letter-spacing: 0.015em;
|
|
701
|
+
}
|
|
702
|
+
.quota-plan {
|
|
703
|
+
border-radius: 999px;
|
|
704
|
+
background: rgba(26, 58, 143, 0.07);
|
|
705
|
+
padding: 2px 7px;
|
|
706
|
+
color: var(--blue);
|
|
707
|
+
font-size: 9px;
|
|
708
|
+
font-weight: 850;
|
|
709
|
+
text-transform: uppercase;
|
|
710
|
+
}
|
|
711
|
+
.quota-manage {
|
|
712
|
+
margin-left: auto;
|
|
713
|
+
border: 0;
|
|
714
|
+
padding: 0;
|
|
715
|
+
background: transparent;
|
|
716
|
+
color: var(--faint);
|
|
717
|
+
font-family: inherit;
|
|
718
|
+
font-size: 9.5px;
|
|
719
|
+
font-weight: 750;
|
|
720
|
+
cursor: pointer;
|
|
721
|
+
}
|
|
722
|
+
.quota-manage:hover { color: var(--blue); }
|
|
723
|
+
.quota-list { display: grid; gap: 9px; }
|
|
724
|
+
.quota-row { --quota-color: var(--blue); }
|
|
725
|
+
.quota-row.is-warn { --quota-color: #c98712; }
|
|
726
|
+
.quota-row.is-limit { --quota-color: #c7372f; }
|
|
727
|
+
.quota-line, .quota-subline {
|
|
728
|
+
display: flex;
|
|
729
|
+
align-items: baseline;
|
|
730
|
+
justify-content: space-between;
|
|
731
|
+
gap: 10px;
|
|
732
|
+
}
|
|
733
|
+
.quota-name { color: var(--muted); font-size: 10px; font-weight: 720; }
|
|
734
|
+
.quota-value {
|
|
735
|
+
color: var(--ink);
|
|
736
|
+
font-family: "JetBrains Mono", ui-monospace, monospace;
|
|
737
|
+
font-size: 9.5px;
|
|
738
|
+
font-weight: 760;
|
|
739
|
+
white-space: nowrap;
|
|
740
|
+
}
|
|
741
|
+
.quota-track {
|
|
742
|
+
height: 4px;
|
|
743
|
+
margin-top: 4px;
|
|
744
|
+
overflow: hidden;
|
|
745
|
+
border-radius: 999px;
|
|
746
|
+
background: rgba(26, 58, 143, 0.11);
|
|
747
|
+
}
|
|
748
|
+
.quota-fill {
|
|
749
|
+
width: 0;
|
|
750
|
+
height: 100%;
|
|
751
|
+
border-radius: inherit;
|
|
752
|
+
background: var(--quota-color);
|
|
753
|
+
transition: width .28s ease, background .2s ease;
|
|
754
|
+
}
|
|
755
|
+
.quota-subline { margin-top: 3px; }
|
|
756
|
+
.quota-reset, .quota-percent { color: var(--faint); font-size: 8.5px; font-weight: 650; }
|
|
757
|
+
.quota-row.is-limit .quota-value,
|
|
758
|
+
.quota-row.is-limit .quota-percent { color: #9d2b25; font-weight: 850; }
|
|
759
|
+
.quota-row.is-unavailable .quota-fill { background: rgba(26, 58, 143, 0.14); }
|
|
681
760
|
button.renew-btn {
|
|
682
761
|
display: none;
|
|
683
762
|
width: 100%;
|
|
@@ -838,6 +917,30 @@ export const HUD_HTML = String.raw `<!doctype html>
|
|
|
838
917
|
</div>
|
|
839
918
|
<div class="details">
|
|
840
919
|
<div class="timeline-scrollbar hidden" id="timelineScrollbar" aria-hidden="true"><span class="timeline-thumb" id="timelineThumb"></span></div>
|
|
920
|
+
<section class="quota-card" id="quotacard" aria-label="Plan usage">
|
|
921
|
+
<div class="quota-head">
|
|
922
|
+
<span class="quota-title">Plan usage</span>
|
|
923
|
+
<span class="quota-plan" id="quotaplan">Free</span>
|
|
924
|
+
<button class="quota-manage" id="quotamanage" type="button">Manage</button>
|
|
925
|
+
</div>
|
|
926
|
+
<div class="quota-list">
|
|
927
|
+
<div class="quota-row" data-quota="processing">
|
|
928
|
+
<div class="quota-line"><span class="quota-name">Processing this week</span><span class="quota-value"></span></div>
|
|
929
|
+
<div class="quota-track"><div class="quota-fill"></div></div>
|
|
930
|
+
<div class="quota-subline"><span class="quota-reset"></span><span class="quota-percent"></span></div>
|
|
931
|
+
</div>
|
|
932
|
+
<div class="quota-row" data-quota="searches">
|
|
933
|
+
<div class="quota-line"><span class="quota-name">Memory searches</span><span class="quota-value"></span></div>
|
|
934
|
+
<div class="quota-track"><div class="quota-fill"></div></div>
|
|
935
|
+
<div class="quota-subline"><span class="quota-reset"></span><span class="quota-percent"></span></div>
|
|
936
|
+
</div>
|
|
937
|
+
<div class="quota-row" data-quota="historical">
|
|
938
|
+
<div class="quota-line"><span class="quota-name">Bulk history imports</span><span class="quota-value"></span></div>
|
|
939
|
+
<div class="quota-track"><div class="quota-fill"></div></div>
|
|
940
|
+
<div class="quota-subline"><span class="quota-reset">Lifetime limit</span><span class="quota-percent"></span></div>
|
|
941
|
+
</div>
|
|
942
|
+
</div>
|
|
943
|
+
</section>
|
|
841
944
|
<div class="now-card" id="nowcard">
|
|
842
945
|
<div class="now-sess">
|
|
843
946
|
<img class="now-icon" id="nowicon" src="/assets/hud/codex.svg" alt="" />
|
|
@@ -903,6 +1006,9 @@ export const HUD_HTML = String.raw `<!doctype html>
|
|
|
903
1006
|
const billingTitle = document.getElementById("billingtitle");
|
|
904
1007
|
const billingCopy = document.getElementById("billingcopy");
|
|
905
1008
|
const billingLink = document.getElementById("billinglink");
|
|
1009
|
+
const quotaCard = document.getElementById("quotacard");
|
|
1010
|
+
const quotaPlan = document.getElementById("quotaplan");
|
|
1011
|
+
const quotaManage = document.getElementById("quotamanage");
|
|
906
1012
|
const renewBtn = document.getElementById("renewbtn");
|
|
907
1013
|
const renewResult = document.getElementById("renewresult");
|
|
908
1014
|
const renewMsg = document.getElementById("renewmsg");
|
|
@@ -1134,7 +1240,7 @@ export const HUD_HTML = String.raw `<!doctype html>
|
|
|
1134
1240
|
if (!id) return;
|
|
1135
1241
|
fetch("/jump?id=" + encodeURIComponent(id)).catch(() => {});
|
|
1136
1242
|
}
|
|
1137
|
-
|
|
1243
|
+
function openPricing(e) {
|
|
1138
1244
|
e.stopPropagation();
|
|
1139
1245
|
const url = billingStatus && billingStatus.pricingUrl;
|
|
1140
1246
|
if (!url) return;
|
|
@@ -1146,7 +1252,9 @@ export const HUD_HTML = String.raw `<!doctype html>
|
|
|
1146
1252
|
.catch(() => {
|
|
1147
1253
|
try { window.open(url, "_blank", "noopener"); } catch {}
|
|
1148
1254
|
});
|
|
1149
|
-
}
|
|
1255
|
+
}
|
|
1256
|
+
if (billingLink) billingLink.addEventListener("click", openPricing);
|
|
1257
|
+
if (quotaManage) quotaManage.addEventListener("click", openPricing);
|
|
1150
1258
|
function toggleSessionDetails(id) {
|
|
1151
1259
|
if (!id) return;
|
|
1152
1260
|
const nextId = lastState && lastState.expandedId === id ? "auto" : id;
|
|
@@ -1614,6 +1722,18 @@ export const HUD_HTML = String.raw `<!doctype html>
|
|
|
1614
1722
|
const previewExpanded = previewParams.get("expanded") === "1" || previewParams.get("pinned") === "1";
|
|
1615
1723
|
if (previewParams.get("capture") === "1") hud.classList.add("capture-preview");
|
|
1616
1724
|
if (previewColor === "green" || previewColor === "amber" || previewColor === "red") {
|
|
1725
|
+
const previewProcessingUsed = previewColor === "red" ? 200000 : previewColor === "amber" ? 163000 : 84000;
|
|
1726
|
+
const previewSearchUsed = previewColor === "red" ? 100 : previewColor === "amber" ? 82 : 32;
|
|
1727
|
+
billingStatus = {
|
|
1728
|
+
ok: true,
|
|
1729
|
+
state: "ok",
|
|
1730
|
+
plan: "pro",
|
|
1731
|
+
paid: true,
|
|
1732
|
+
pricingUrl: "https://yeahecho.com/pricing?source=hud_preview",
|
|
1733
|
+
memoryProcessingQuota: { used: previewProcessingUsed, limit: 200000, remaining: 200000 - previewProcessingUsed, resetAt: new Date(Date.now() + 31 * 60 * 60 * 1000).toISOString() },
|
|
1734
|
+
memorySearchQuota: { used: previewSearchUsed, limit: 100, remaining: 100 - previewSearchUsed, resetAt: new Date(Date.now() + 31 * 60 * 60 * 1000).toISOString() },
|
|
1735
|
+
historicalConversationQuota: { used: 191, limit: 250, remaining: 59 },
|
|
1736
|
+
};
|
|
1617
1737
|
const sample = {
|
|
1618
1738
|
client: "codex",
|
|
1619
1739
|
label: "EchoMem-Chrome",
|
|
@@ -1824,25 +1944,96 @@ export const HUD_HTML = String.raw `<!doctype html>
|
|
|
1824
1944
|
|
|
1825
1945
|
function renderBillingStatus() {
|
|
1826
1946
|
if (!billingBanner) return;
|
|
1947
|
+
renderQuotaUsage();
|
|
1827
1948
|
const state = billingStatus && billingStatus.state;
|
|
1828
|
-
const blocked = state === "
|
|
1949
|
+
const blocked = state === "quota_exceeded" || state === "billing_attention";
|
|
1829
1950
|
billingBanner.style.display = blocked ? "block" : "none";
|
|
1830
1951
|
billingBanner.classList.toggle("limit", state === "quota_exceeded");
|
|
1831
1952
|
if (!blocked) return;
|
|
1832
|
-
const title = state === "quota_exceeded"
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
: billingStatus.message || "Billing needs attention";
|
|
1837
|
-
const detail = state === "quota_exceeded"
|
|
1838
|
-
? "You used all Pro searches this month. Upgrade or wait for reset."
|
|
1839
|
-
: state === "plan_required"
|
|
1840
|
-
? "Search memory requires Pro or Power. Saving still works."
|
|
1841
|
-
: billingStatus.detail || "Open pricing to restore memory search.";
|
|
1953
|
+
const title = billingStatus.message || (state === "quota_exceeded" ? "Plan limit reached" : "Billing needs attention");
|
|
1954
|
+
const detail = billingStatus.detail || (state === "quota_exceeded"
|
|
1955
|
+
? "A weekly limit has been reached. Wait for reset or change your plan."
|
|
1956
|
+
: "Open pricing to review your plan.");
|
|
1842
1957
|
if (billingTitle) billingTitle.textContent = title;
|
|
1843
1958
|
if (billingCopy) billingCopy.textContent = detail;
|
|
1844
1959
|
}
|
|
1845
1960
|
|
|
1961
|
+
function renderQuotaUsage() {
|
|
1962
|
+
if (!quotaCard) return;
|
|
1963
|
+
const status = billingStatus || {};
|
|
1964
|
+
const quotaRows = [
|
|
1965
|
+
["processing", status.memoryProcessingQuota, false],
|
|
1966
|
+
["searches", status.memorySearchQuota, false],
|
|
1967
|
+
["historical", status.historicalConversationQuota, true],
|
|
1968
|
+
];
|
|
1969
|
+
const hasQuota = quotaRows.some((entry) => entry[1] && typeof entry[1] === "object");
|
|
1970
|
+
const visible = hasQuota && status.state !== "not_connected" && status.plan !== "unknown";
|
|
1971
|
+
quotaCard.style.display = visible ? "block" : "none";
|
|
1972
|
+
if (!visible) return;
|
|
1973
|
+
if (quotaPlan) quotaPlan.textContent = planLabel(status.plan || "free");
|
|
1974
|
+
for (const entry of quotaRows) renderQuotaRow(entry[0], entry[1], entry[2]);
|
|
1975
|
+
}
|
|
1976
|
+
|
|
1977
|
+
function renderQuotaRow(key, snapshot, lifetime) {
|
|
1978
|
+
if (!quotaCard) return;
|
|
1979
|
+
const row = quotaCard.querySelector('[data-quota="' + key + '"]');
|
|
1980
|
+
if (!row) return;
|
|
1981
|
+
const valueEl = row.querySelector(".quota-value");
|
|
1982
|
+
const fillEl = row.querySelector(".quota-fill");
|
|
1983
|
+
const resetEl = row.querySelector(".quota-reset");
|
|
1984
|
+
const percentEl = row.querySelector(".quota-percent");
|
|
1985
|
+
const used = quotaNumber(snapshot && snapshot.used);
|
|
1986
|
+
const limit = quotaNumber(snapshot && snapshot.limit);
|
|
1987
|
+
const available = used !== null && limit !== null;
|
|
1988
|
+
const unlimited = available && limit < 0;
|
|
1989
|
+
const percent = available && limit > 0 ? clamp(Math.round(used / limit * 100), 0, 100) : 0;
|
|
1990
|
+
|
|
1991
|
+
row.classList.toggle("is-unavailable", !available);
|
|
1992
|
+
row.classList.toggle("is-warn", available && !unlimited && percent >= 80 && percent < 100);
|
|
1993
|
+
row.classList.toggle("is-limit", available && !unlimited && percent >= 100);
|
|
1994
|
+
if (valueEl) {
|
|
1995
|
+
valueEl.textContent = unlimited
|
|
1996
|
+
? formatQuotaNumber(used) + " / Unlimited"
|
|
1997
|
+
: (used === null ? "—" : formatQuotaNumber(used)) + " / " + (limit === null ? "—" : formatQuotaNumber(limit));
|
|
1998
|
+
}
|
|
1999
|
+
if (fillEl) fillEl.style.width = unlimited ? "0%" : percent + "%";
|
|
2000
|
+
if (percentEl) percentEl.textContent = !available
|
|
2001
|
+
? "Usage unavailable"
|
|
2002
|
+
: unlimited
|
|
2003
|
+
? "Unlimited"
|
|
2004
|
+
: percent >= 100
|
|
2005
|
+
? "Limit reached"
|
|
2006
|
+
: percent + "%";
|
|
2007
|
+
if (resetEl) resetEl.textContent = lifetime ? "Lifetime limit" : quotaResetLabel(snapshot && snapshot.resetAt);
|
|
2008
|
+
row.setAttribute("aria-label", key + " usage " + (percentEl ? percentEl.textContent : ""));
|
|
2009
|
+
}
|
|
2010
|
+
|
|
2011
|
+
function quotaNumber(value) {
|
|
2012
|
+
return typeof value === "number" && isFinite(value) ? value : null;
|
|
2013
|
+
}
|
|
2014
|
+
|
|
2015
|
+
function formatQuotaNumber(value) {
|
|
2016
|
+
const n = Math.max(0, Number(value) || 0);
|
|
2017
|
+
if (n >= 1000000) return (n / 1000000).toFixed(n < 10000000 ? 1 : 0).replace(/\.0$/, "") + "M";
|
|
2018
|
+
if (n >= 1000) return (n / 1000).toFixed(n < 100000 ? 1 : 0).replace(/\.0$/, "") + "K";
|
|
2019
|
+
return Math.round(n).toLocaleString("en-US");
|
|
2020
|
+
}
|
|
2021
|
+
|
|
2022
|
+
function quotaResetLabel(value) {
|
|
2023
|
+
if (!value) return "Weekly reset";
|
|
2024
|
+
const resetAt = new Date(value);
|
|
2025
|
+
if (!isFinite(resetAt.getTime())) return "Weekly reset";
|
|
2026
|
+
if (resetAt.getTime() <= Date.now()) return "Resetting now";
|
|
2027
|
+
return "Resets " + resetAt.toLocaleString(undefined, {
|
|
2028
|
+
weekday: "short",
|
|
2029
|
+
month: "short",
|
|
2030
|
+
day: "numeric",
|
|
2031
|
+
hour: "numeric",
|
|
2032
|
+
minute: "2-digit",
|
|
2033
|
+
timeZoneName: "short",
|
|
2034
|
+
});
|
|
2035
|
+
}
|
|
2036
|
+
|
|
1846
2037
|
function renderWhy(score, oldPct, repeatCount, sat, signal, color) {
|
|
1847
2038
|
const rows = [];
|
|
1848
2039
|
for (const row of junkBreakdown(score)) rows.push(row);
|
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
|
}
|
|
@@ -989,7 +994,18 @@ export async function startMigration(opts) {
|
|
|
989
994
|
metricsFile,
|
|
990
995
|
selection: opts.selection,
|
|
991
996
|
});
|
|
992
|
-
|
|
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
|
+
};
|
|
993
1009
|
}
|
|
994
1010
|
async function runJobs(args) {
|
|
995
1011
|
const ledger = loadLedger();
|
|
@@ -1096,7 +1112,8 @@ async function runJobs(args) {
|
|
|
1096
1112
|
}
|
|
1097
1113
|
};
|
|
1098
1114
|
try {
|
|
1099
|
-
|
|
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()));
|
|
1100
1117
|
}
|
|
1101
1118
|
catch {
|
|
1102
1119
|
failed++;
|
|
@@ -1236,7 +1253,7 @@ export async function cmdMigrate(flags) {
|
|
|
1236
1253
|
},
|
|
1237
1254
|
});
|
|
1238
1255
|
if (h.capped)
|
|
1239
|
-
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.`));
|
|
1240
1257
|
console.log(c.dim(`Import session ${h.sessionId} — ${h.jobCount} jobs queued (the web dashboard can watch this live).`));
|
|
1241
1258
|
console.log(c.dim(`Metrics: ${h.metricsFile}`));
|
|
1242
1259
|
const r = await h.done;
|
|
@@ -1283,7 +1300,14 @@ async function createImportSession(client, sessions, bareId, userTz, signal) {
|
|
|
1283
1300
|
}));
|
|
1284
1301
|
const res = await client.post("/api/extension/import-sessions", { items }, signal ? { signal } : undefined);
|
|
1285
1302
|
const data = res.data || {};
|
|
1286
|
-
|
|
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
|
+
};
|
|
1287
1311
|
}
|
|
1288
1312
|
/** Run one queued job: stream its transcript to the server, which extracts it. Retries transient locks/rate limits. */
|
|
1289
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();
|