@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
|
@@ -77,6 +77,32 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
77
77
|
function renderBridgeIssue() {
|
|
78
78
|
renderReportIssue("BRIDGE_UNREACHABLE", "The local bridge stopped answering before your report was ready. Your terminal may have closed or the process may have stopped.");
|
|
79
79
|
}
|
|
80
|
+
function renderDiscoveryCheck(slow) {
|
|
81
|
+
void slow;
|
|
82
|
+
setCityMode(false);
|
|
83
|
+
setExtractMode(false);
|
|
84
|
+
setReadyMode(true);
|
|
85
|
+
setHead("Loading", "Working");
|
|
86
|
+
app.className = "discoveryLoader";
|
|
87
|
+
app.innerHTML = '<div class="discoverySpinner" role="status" aria-label="Loading"><span aria-hidden="true"></span></div>';
|
|
88
|
+
}
|
|
89
|
+
function renderDiscoveryCheckIssue(error) {
|
|
90
|
+
setCityMode(false);
|
|
91
|
+
setExtractMode(false);
|
|
92
|
+
setReadyMode(true);
|
|
93
|
+
setHead("Session refresh paused", "Needs attention");
|
|
94
|
+
app.className = "loading-panel";
|
|
95
|
+
app.innerHTML =
|
|
96
|
+
'<div style="padding:24px"><h2>Echo could not refresh session status</h2>' +
|
|
97
|
+
'<p class="helper">Keep the terminal open and try the local check again.</p>' +
|
|
98
|
+
(error ? '<details class="reportTechnical"><summary>Technical details</summary><code>' + esc(error) + '</code></details>' : '') +
|
|
99
|
+
'<div class="actions"><button id="retryDiscoveryCheck" class="primary">Try again</button></div></div>';
|
|
100
|
+
var retry = document.getElementById("retryDiscoveryCheck");
|
|
101
|
+
if (retry) retry.onclick = function () {
|
|
102
|
+
statsPollStarted = false;
|
|
103
|
+
void waitForStats();
|
|
104
|
+
};
|
|
105
|
+
}
|
|
80
106
|
function renderLogoutPending() {
|
|
81
107
|
setCityMode(false);
|
|
82
108
|
setHead("Signing out locally", "Working");
|
|
@@ -96,30 +122,36 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
96
122
|
}
|
|
97
123
|
async function waitForStats() {
|
|
98
124
|
if (statsPollStarted) return;
|
|
125
|
+
var statsEpoch = accountStateEpoch;
|
|
99
126
|
statsPollStarted = true;
|
|
100
127
|
setCityMode(false);
|
|
101
128
|
setExtractMode(false);
|
|
102
129
|
setReadyMode(true);
|
|
103
|
-
|
|
104
|
-
app.className = "loading-panel";
|
|
105
|
-
app.innerHTML = '<div style="padding:24px"><h2>Checking for active work</h2><p class="helper">Echo is reconnecting to any extraction already running on this machine.</p></div>';
|
|
130
|
+
renderDiscoveryCheck(false);
|
|
106
131
|
try {
|
|
107
132
|
if (await resumeExistingExtraction()) return;
|
|
133
|
+
if (statsEpoch !== accountStateEpoch || !connected) return;
|
|
108
134
|
var started = Date.now();
|
|
109
135
|
var lastError = "";
|
|
110
136
|
for (;;) {
|
|
111
|
-
if (decisionMade) return;
|
|
137
|
+
if (decisionMade || statsEpoch !== accountStateEpoch || !connected) return;
|
|
112
138
|
try {
|
|
113
139
|
var res = await fetchWithTimeout("/stats?nonce=" + encodeURIComponent(nonce), { credentials: "omit" }, 3000);
|
|
140
|
+
if (statsEpoch !== accountStateEpoch || !connected) return;
|
|
114
141
|
if (res.status === 200) {
|
|
115
|
-
|
|
116
|
-
if (decisionMade) return;
|
|
142
|
+
var nextStats = await res.json();
|
|
143
|
+
if (decisionMade || statsEpoch !== accountStateEpoch || !connected) return;
|
|
144
|
+
stats = nextStats;
|
|
117
145
|
if (connected && !billingStatus && !billingStatusLoading) {
|
|
118
146
|
try { await refreshBillingStatus(); } catch (_) {}
|
|
119
|
-
if (decisionMade) return;
|
|
147
|
+
if (decisionMade || statsEpoch !== accountStateEpoch || !connected) return;
|
|
120
148
|
}
|
|
121
149
|
renderDashboard();
|
|
122
|
-
if (stats && stats.partial) {
|
|
150
|
+
if (stats && stats.partial) {
|
|
151
|
+
await sleep(1200);
|
|
152
|
+
if (statsEpoch !== accountStateEpoch || !connected) return;
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
123
155
|
return;
|
|
124
156
|
}
|
|
125
157
|
if (res.status !== 202) throw new Error(await res.text() || ("HTTP " + res.status));
|
|
@@ -172,7 +204,6 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
172
204
|
var discovery = stats && stats.discovery ? stats.discovery : {};
|
|
173
205
|
var isPartial = !!(stats && stats.partial);
|
|
174
206
|
var localScanReady = !isPartial || discovery.phase === "exact" || discovery.phase === "full";
|
|
175
|
-
var eta = migratable && migratable.eta ? migratable.eta : {};
|
|
176
207
|
var skippedActive = typeof migratable.skippedActive === "number" ? migratable.skippedActive : 0;
|
|
177
208
|
var sessions = stats && stats.sessions ? stats.sessions : {};
|
|
178
209
|
// The "quick" phase counts only this device's local ledger; it does not know what the account
|
|
@@ -183,25 +214,36 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
183
214
|
var knownDone = pendingTrusted && pending === 0;
|
|
184
215
|
var canExtract = pendingTrusted && pending > 0;
|
|
185
216
|
var pendN = hasPendingCount ? pending : 0;
|
|
186
|
-
var etaLabel = eta.estimatedLabel || migratable.estimatedLabel || "a couple of minutes";
|
|
187
217
|
var pendingCodex = typeof migratable.pendingCodex === "number" ? migratable.pendingCodex : null;
|
|
188
218
|
var pendingClaudeCode = typeof migratable.pendingClaudeCode === "number" ? migratable.pendingClaudeCode : null;
|
|
189
219
|
if (connected && !billingStatusLoading && !setupPlanConfirmed()) {
|
|
190
220
|
renderPlanGate();
|
|
191
221
|
return;
|
|
192
222
|
}
|
|
223
|
+
var importableNow = canExtract ? Math.min(pendN, historicalSelectionLimit()) : 0;
|
|
224
|
+
var planLimited = canExtract && importableNow < pendN;
|
|
225
|
+
var deferredByPlan = planLimited ? pendN - importableNow : 0;
|
|
226
|
+
var currentPlan = String(billingStatus && billingStatus.plan || "").toLowerCase();
|
|
227
|
+
var currentPlanLabel = paidRecallPlan(currentPlan)
|
|
228
|
+
? currentPlan.charAt(0).toUpperCase() + currentPlan.slice(1) + " Echo"
|
|
229
|
+
: "Original Echo";
|
|
193
230
|
setHead("Turn coding history into memory", pendingTrusted ? (knownDone ? "Done" : "Ready") : (localScanReady ? "Ready" : "Scanning"));
|
|
194
231
|
var headlineHtml = !pendingTrusted
|
|
195
232
|
? "Counting your <strong>new conversations…</strong>"
|
|
196
233
|
: (knownDone
|
|
197
234
|
? "You are <strong>all caught up.</strong>"
|
|
198
|
-
:
|
|
235
|
+
: (planLimited
|
|
236
|
+
? "<strong>" + esc(number(pendN)) + " sessions</strong> found."
|
|
237
|
+
: "<strong>" + esc(number(pendN)) + " sessions</strong> are ready to review."));
|
|
199
238
|
var sub = !pendingTrusted
|
|
200
239
|
? "Echo is matching your local history against what is already in memory."
|
|
201
|
-
|
|
240
|
+
: (knownDone
|
|
202
241
|
? (skippedActive ? number(skippedActive) + " active conversation" + (skippedActive === 1 ? " is" : "s are") + " still changing, so Echo will pick them up later." : "Your history is already in EchoMem. Nothing new to extract.")
|
|
203
|
-
:
|
|
204
|
-
|
|
242
|
+
: (planLimited
|
|
243
|
+
? (importableNow > 0
|
|
244
|
+
? currentPlanLabel + " can import " + number(importableNow) + " of them. Choose which ones to bring into memory; the other " + number(deferredByPlan) + " stay on this Mac."
|
|
245
|
+
: currentPlanLabel + " has no coding-session imports remaining. These sessions stay on this Mac.")
|
|
246
|
+
: ""));
|
|
205
247
|
// Mount the dashboard shell ONCE so the WebGL plate iframe is never re-created across /stats polls.
|
|
206
248
|
// (Rebuilding app.innerHTML on every partial-scan poll reloaded the iframe → the city flickered.)
|
|
207
249
|
// Later polls only patch the text/counts in place; the iframe stays mounted.
|
|
@@ -217,6 +259,10 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
217
259
|
// not a display-size headline.
|
|
218
260
|
app.innerHTML =
|
|
219
261
|
setupJourney(4) +
|
|
262
|
+
'<aside class="readyUtilityNav" aria-label="Account and plan settings">' +
|
|
263
|
+
'<div class="planGateAccount readyAccountNav" aria-label="Connected account"><div class="setupAccountSlot" id="setupAccount"></div></div>' +
|
|
264
|
+
'<div class="readyPlanNav" aria-label="Current plan"><span class="readyPlanNavLabel">Plan</span><div class="setupPlanSlot" id="setupPlan"></div></div>' +
|
|
265
|
+
'</aside>' +
|
|
220
266
|
'<div id="exBanner"></div>' +
|
|
221
267
|
'<section class="readyDecision extractionReady">' +
|
|
222
268
|
'<div class="readyHero">' +
|
|
@@ -234,10 +280,6 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
234
280
|
'</div>' +
|
|
235
281
|
'<p class="ctaMeta" id="exEta"></p>' +
|
|
236
282
|
'</div>' +
|
|
237
|
-
'<aside class="readySettings" aria-label="Account and plan settings">' +
|
|
238
|
-
'<div class="readySetting readyAccountSetting"><span class="readySettingLabel">Account</span><div class="setupAccountSlot" id="setupAccount"></div></div>' +
|
|
239
|
-
'<div class="readySetting readyPlanSetting"><span class="readySettingLabel">Plan</span><div class="setupPlanSlot" id="setupPlan"></div></div>' +
|
|
240
|
-
'</aside>' +
|
|
241
283
|
'</section>' +
|
|
242
284
|
'<dialog class="sessionPicker" id="sessionPicker" aria-labelledby="sessionPickerTitle"></dialog>';
|
|
243
285
|
document.getElementById("migrate").onclick = openSessionPicker;
|
|
@@ -266,7 +308,7 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
266
308
|
// Reassurances live at the moment of commitment — right under the button.
|
|
267
309
|
document.getElementById("exEta").innerHTML = pendingTrusted
|
|
268
310
|
? (canExtract
|
|
269
|
-
? '
|
|
311
|
+
? ''
|
|
270
312
|
: 'Nothing new to extract right now.')
|
|
271
313
|
: 'Echo is checking local history against saved memory…';
|
|
272
314
|
renderSetupPlan(canExtract);
|
|
@@ -277,9 +319,25 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
277
319
|
var migrateBtn = document.getElementById("migrate");
|
|
278
320
|
if (pendingTrusted) {
|
|
279
321
|
migrateBtn.style.display = "";
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
322
|
+
var candidatesReady = !canExtract || candidateSessions().length > 0;
|
|
323
|
+
if (canExtract && candidatesReady) ensureSessionSelection();
|
|
324
|
+
// This button opens the picker; zero selected belongs to the picker's Start button.
|
|
325
|
+
migrateBtn.disabled = canExtract && (
|
|
326
|
+
!setupPlanConfirmed() ||
|
|
327
|
+
!candidatesReady ||
|
|
328
|
+
historicalSelectionLimit() === 0
|
|
329
|
+
);
|
|
330
|
+
migrateBtn.innerHTML = '<span>' + esc(
|
|
331
|
+
canExtract
|
|
332
|
+
? (candidatesReady
|
|
333
|
+
? (importableNow === 0
|
|
334
|
+
? "No imports available"
|
|
335
|
+
: (planLimited
|
|
336
|
+
? "Choose " + number(importableNow) + " of " + number(pendN) + " sessions"
|
|
337
|
+
: "Review " + number(pendN) + " sessions"))
|
|
338
|
+
: "Preparing session list…")
|
|
339
|
+
: "Finish setup"
|
|
340
|
+
) + '</span>' + (canExtract && candidatesReady ? setupIcon("arrow-right") : "");
|
|
283
341
|
migrateBtn.onclick = canExtract ? openSessionPicker : skip;
|
|
284
342
|
} else {
|
|
285
343
|
migrateBtn.style.display = "none";
|
|
@@ -311,9 +369,14 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
311
369
|
decisionMade = true;
|
|
312
370
|
extractionEnded = false;
|
|
313
371
|
setCityMode(false);
|
|
372
|
+
setScanMode(false);
|
|
373
|
+
setReadyMode(false);
|
|
374
|
+
setExtractMode(true);
|
|
314
375
|
setHead("Extracting", "Starting");
|
|
315
|
-
app.className = "";
|
|
316
|
-
app.innerHTML =
|
|
376
|
+
app.className = "extractionStartStage";
|
|
377
|
+
app.innerHTML =
|
|
378
|
+
setupJourney(4) +
|
|
379
|
+
'<div class="discoverySpinner" role="status" aria-label="Starting import"><span aria-hidden="true"></span></div>';
|
|
317
380
|
var started;
|
|
318
381
|
try {
|
|
319
382
|
started = await postJson("/migrate", { conversationKeys: selectedKeys });
|
|
@@ -327,8 +390,34 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
327
390
|
await pollProgress();
|
|
328
391
|
}
|
|
329
392
|
async function resumeExistingExtraction() {
|
|
393
|
+
var resumeEpoch = accountStateEpoch;
|
|
394
|
+
var existing = null;
|
|
395
|
+
var lastError = "";
|
|
396
|
+
for (var attempt = 0; attempt < 3; attempt++) {
|
|
397
|
+
try {
|
|
398
|
+
var progressResponse = await fetchWithTimeout(
|
|
399
|
+
"/progress?nonce=" + encodeURIComponent(nonce),
|
|
400
|
+
{ credentials: "omit", cache: "no-store" },
|
|
401
|
+
3000
|
|
402
|
+
);
|
|
403
|
+
if (!progressResponse.ok) throw new Error(await progressResponse.text() || ("HTTP " + progressResponse.status));
|
|
404
|
+
existing = await progressResponse.json();
|
|
405
|
+
break;
|
|
406
|
+
} catch (error) {
|
|
407
|
+
if (resumeEpoch !== accountStateEpoch || !connected) return false;
|
|
408
|
+
lastError = error && error.message ? error.message : String(error);
|
|
409
|
+
if (attempt < 2) {
|
|
410
|
+
renderDiscoveryCheck(true);
|
|
411
|
+
await sleep(500 * (attempt + 1));
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
if (!existing) {
|
|
416
|
+
renderDiscoveryCheckIssue(lastError);
|
|
417
|
+
return true;
|
|
418
|
+
}
|
|
330
419
|
try {
|
|
331
|
-
|
|
420
|
+
if (resumeEpoch !== accountStateEpoch || !connected) return false;
|
|
332
421
|
if (!existing || existing.status === "idle") return false;
|
|
333
422
|
decisionMade = true;
|
|
334
423
|
extractionEnded = false;
|
|
@@ -338,21 +427,8 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
338
427
|
void pollProgress();
|
|
339
428
|
}
|
|
340
429
|
return true;
|
|
341
|
-
} catch (
|
|
342
|
-
|
|
343
|
-
// the authoritative progress endpoint answers instead of exposing a second start.
|
|
344
|
-
decisionMade = true;
|
|
345
|
-
extractionEnded = false;
|
|
346
|
-
extractMounted = false;
|
|
347
|
-
renderProgress({
|
|
348
|
-
status: "running",
|
|
349
|
-
total: 0,
|
|
350
|
-
completed: 0,
|
|
351
|
-
failed: 0,
|
|
352
|
-
extracted: 0,
|
|
353
|
-
latest: "Reconnecting to the active extraction..."
|
|
354
|
-
});
|
|
355
|
-
void pollProgress();
|
|
430
|
+
} catch (error) {
|
|
431
|
+
renderDiscoveryCheckIssue(error && error.message ? error.message : String(error));
|
|
356
432
|
return true;
|
|
357
433
|
}
|
|
358
434
|
}
|
|
@@ -434,11 +510,11 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
434
510
|
? '<p class="exSub">' + esc(progress.latest) + '</p>'
|
|
435
511
|
: "";
|
|
436
512
|
t.innerHTML =
|
|
437
|
-
'<div class="resultStage ' + (done ? "ok" : "warn") + '">' +
|
|
513
|
+
'<div class="resultStage ' + (done ? "firstRecallStage ok" : "warn") + '">' +
|
|
438
514
|
'<div class="resultTop">' +
|
|
439
515
|
'<div>' +
|
|
440
516
|
(done
|
|
441
|
-
? '<h2 class="savedHeading
|
|
517
|
+
? '<h2 class="savedHeading"><span class="savedMain"><strong>' + esc(number(extracted)) + '</strong> ' + esc(savedMemoryLabel) + '</span></h2>' + completionNote
|
|
442
518
|
: '<h2 class="siteHeadline">Some conversations need <strong>another pass.</strong></h2>' +
|
|
443
519
|
'<p class="exSub">' + stopDetail + '</p>') +
|
|
444
520
|
'</div>' +
|
|
@@ -714,7 +790,10 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
714
790
|
var edit = document.getElementById("editSessions");
|
|
715
791
|
if (edit) edit.onclick = openSessionPicker;
|
|
716
792
|
var migrateBtn = document.getElementById("migrate");
|
|
717
|
-
if (migrateBtn) migrateBtn.disabled =
|
|
793
|
+
if (migrateBtn) migrateBtn.disabled =
|
|
794
|
+
candidates.length === 0 ||
|
|
795
|
+
!setupPlanConfirmed() ||
|
|
796
|
+
historicalSelectionLimit() === 0;
|
|
718
797
|
}
|
|
719
798
|
function updateSessionPickerSelectionUi(dialog, input, limit) {
|
|
720
799
|
var selectedCount = selectedSessionKeyList().length;
|
|
@@ -740,13 +819,46 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
740
819
|
'<span class="sessionPickerLimitEyebrow">Plan limit</span>' +
|
|
741
820
|
'<h3>Plan limit reached.</h3>' +
|
|
742
821
|
'<p>Upgrade your plan to select and import more conversations.</p>' +
|
|
743
|
-
'<div class="sessionPickerLimitActions"><
|
|
822
|
+
'<div class="sessionPickerLimitActions"><button type="button" id="upgradeSessionPlan" class="button primary">' + linkLabel + '</button>' +
|
|
744
823
|
(canDismiss ? '<button type="button" class="textButton" id="dismissSessionLimit">Keep editing</button>' : "") + '</div>' +
|
|
745
824
|
'</div>';
|
|
746
825
|
}
|
|
826
|
+
async function openSessionPickerPlanAction() {
|
|
827
|
+
var currentPlan = String(billingStatus && billingStatus.plan || "").toLowerCase();
|
|
828
|
+
if (currentPlan === "power" || currentPlan === "team" || currentPlan === "enterprise") {
|
|
829
|
+
setSessionPickerStatus("Opening secure plan options…");
|
|
830
|
+
var portal = await openHostedPlanOptions();
|
|
831
|
+
setSessionPickerStatus(portal
|
|
832
|
+
? "Plan options opened in Stripe. This page will update automatically."
|
|
833
|
+
: "Could not open plan options. Try again.");
|
|
834
|
+
return;
|
|
835
|
+
}
|
|
836
|
+
var targetPlan = currentPlan === "pro" ? "power" : "pro";
|
|
837
|
+
setSessionPickerStatus("Opening secure Stripe checkout…");
|
|
838
|
+
var checkout = await openHostedBilling(
|
|
839
|
+
"/billing-checkout",
|
|
840
|
+
{ plan: targetPlan, trial: !billingStatus || billingStatus.trialAvailable !== false },
|
|
841
|
+
"Creating your secure checkout…",
|
|
842
|
+
"Checkout opened in Stripe. Keep this local page open; Echo will confirm here automatically."
|
|
843
|
+
);
|
|
844
|
+
if (!checkout) {
|
|
845
|
+
setSessionPickerStatus("Could not open Stripe checkout. Try again.");
|
|
846
|
+
return;
|
|
847
|
+
}
|
|
848
|
+
billingActivationPendingPlan = targetPlan;
|
|
849
|
+
if (typeof checkout.sessionId === "string") {
|
|
850
|
+
rememberCheckoutSessionId(checkout.sessionId);
|
|
851
|
+
billingSyncAttempts = 0;
|
|
852
|
+
billingSyncLastAttemptAt = 0;
|
|
853
|
+
}
|
|
854
|
+
document.title = "Finish checkout — Echo setup is waiting";
|
|
855
|
+
setSessionPickerStatus("Stripe checkout opened. Return here when you finish; this page updates automatically.");
|
|
856
|
+
}
|
|
747
857
|
function wireSessionPickerUpgrade() {
|
|
748
|
-
|
|
749
|
-
|
|
858
|
+
["upgradeSessionPlan", "upgradeSessionPlanInline"].forEach(function (id) {
|
|
859
|
+
var upgrade = document.getElementById(id);
|
|
860
|
+
if (upgrade) upgrade.onclick = function () { void openSessionPickerPlanAction(); };
|
|
861
|
+
});
|
|
750
862
|
var dismiss = document.getElementById("dismissSessionLimit");
|
|
751
863
|
if (dismiss) dismiss.onclick = hideSessionPickerLimitNotice;
|
|
752
864
|
}
|
|
@@ -780,6 +892,10 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
780
892
|
var all = candidateSessions();
|
|
781
893
|
var selected = selectedSessionKeyList();
|
|
782
894
|
var limit = Math.min(all.length, historicalSelectionLimit());
|
|
895
|
+
var planLimited = all.length > limit;
|
|
896
|
+
var inlineUpgrade = planLimited && limit > 0
|
|
897
|
+
? '<button type="button" id="upgradeSessionPlanInline" class="sessionPickerUpgrade">Upgrade</button>'
|
|
898
|
+
: "";
|
|
783
899
|
var query = sessionPickerQuery.trim().toLowerCase();
|
|
784
900
|
var filtered = all.filter(function (session) {
|
|
785
901
|
if (sessionPickerSource !== "all" && session.source !== sessionPickerSource) return false;
|
|
@@ -805,7 +921,7 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
805
921
|
: "";
|
|
806
922
|
dialog.innerHTML =
|
|
807
923
|
'<div class="sessionPickerShell">' +
|
|
808
|
-
'<header class="sessionPickerHead"><div><h2 id="sessionPickerTitle">Choose conversations</h2><p>Up to ' + esc(number(limit)) + '
|
|
924
|
+
'<header class="sessionPickerHead"><div><h2 id="sessionPickerTitle">Choose conversations</h2><p class="sessionPickerSummary"><span class="sessionPickerQuota">Up to <strong>' + esc(number(limit)) + '</strong></span>' + inlineUpgrade + '<span class="sessionPickerOrder">· newest selected first</span></p></div><button type="button" class="sessionPickerClose" id="closeSessionPicker" aria-label="Close">×</button></header>' +
|
|
809
925
|
'<div class="sessionPickerToolbar"><label class="sessionSearch"><span>Search</span><input type="search" id="sessionPickerSearch" placeholder="Title or project" value="' + esc(sessionPickerQuery) + '" /></label>' +
|
|
810
926
|
'<label class="sessionSourceFilter"><span>Source</span><select id="sessionPickerSource"><option value="all"' + (sessionPickerSource === "all" ? " selected" : "") + '>All sources</option><option value="codex"' + (sessionPickerSource === "codex" ? " selected" : "") + '>Codex</option><option value="claude-code"' + (sessionPickerSource === "claude-code" ? " selected" : "") + '>Claude Code</option></select></label>' +
|
|
811
927
|
'</div>' +
|
|
@@ -866,16 +982,15 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
866
982
|
else dialog.setAttribute("open", "");
|
|
867
983
|
}
|
|
868
984
|
function accountInitials(account) {
|
|
869
|
-
var
|
|
870
|
-
|
|
985
|
+
var email = String(account && account.email || "").trim();
|
|
986
|
+
var label = email ? email.split("@")[0] : "EchoMem";
|
|
987
|
+
return label.split(/[\s._-]+/).slice(0, 2).map(function (part) { return part.charAt(0).toUpperCase(); }).join("") || "E";
|
|
871
988
|
}
|
|
872
989
|
function renderAccountChoice() {
|
|
873
990
|
var slot = document.getElementById("setupAccount");
|
|
874
991
|
if (!slot) return;
|
|
875
992
|
var account = billingStatus && billingStatus.account;
|
|
876
|
-
var
|
|
877
|
-
var email = account && account.email ? account.email : "This import will be saved to the account connected on this device.";
|
|
878
|
-
var accountLabel = account && account.email ? account.email : name;
|
|
993
|
+
var accountLabel = account && account.email ? account.email : "Connected account";
|
|
879
994
|
var avatarUrl = account && account.avatarUrl ? String(account.avatarUrl) : "";
|
|
880
995
|
var avatar = '<span class="setupAccountAvatar" aria-hidden="true">' +
|
|
881
996
|
'<span>' + esc(accountInitials(account)) + '</span>' +
|
|
@@ -884,7 +999,7 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
884
999
|
slot.innerHTML =
|
|
885
1000
|
'<section class="setupAccountCard" aria-label="Connected EchoMem account">' +
|
|
886
1001
|
avatar +
|
|
887
|
-
'<span class="setupAccountCopy"><small>Logged in as</small><strong>' + esc(accountLabel) + '</strong
|
|
1002
|
+
'<span class="setupAccountCopy"><small>Logged in as</small><strong>' + esc(accountLabel) + '</strong></span>' +
|
|
888
1003
|
'<button type="button" class="textButton setupAccountChange" id="changeSetupAccount">Switch</button>' +
|
|
889
1004
|
'</section>';
|
|
890
1005
|
var change = document.getElementById("changeSetupAccount");
|
|
@@ -892,8 +1007,8 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
892
1007
|
}
|
|
893
1008
|
function renderAccountSwitchConfirm() {
|
|
894
1009
|
var account = billingStatus && billingStatus.account;
|
|
895
|
-
var accountLabel = account &&
|
|
896
|
-
?
|
|
1010
|
+
var accountLabel = account && account.email
|
|
1011
|
+
? account.email
|
|
897
1012
|
: "the connected account";
|
|
898
1013
|
accountSwitchPending = true;
|
|
899
1014
|
setCityMode(false);
|
|
@@ -921,19 +1036,29 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
921
1036
|
async function switchSetupAccount() {
|
|
922
1037
|
var button = document.getElementById("confirmAccountSwitch");
|
|
923
1038
|
if (button) { button.disabled = true; button.textContent = "Signing out..."; }
|
|
1039
|
+
accountStateEpoch++;
|
|
1040
|
+
stopBillingPoll();
|
|
1041
|
+
progressPollRunId++;
|
|
924
1042
|
try {
|
|
925
|
-
await postJson("/logout", {}, 12000);
|
|
1043
|
+
var logoutResult = await postJson("/logout", {}, 12000);
|
|
1044
|
+
if (!logoutResult || logoutResult.connected !== false) {
|
|
1045
|
+
throw new Error("Echo did not confirm that this device signed out.");
|
|
1046
|
+
}
|
|
926
1047
|
accountSwitchPending = false;
|
|
927
1048
|
stats = null;
|
|
1049
|
+
statsSlow = false;
|
|
928
1050
|
billingStatus = null;
|
|
1051
|
+
billingStatusLoading = false;
|
|
929
1052
|
connected = false;
|
|
1053
|
+
rememberSetupPlanChoice("");
|
|
930
1054
|
selectedSessionKeys = Object.create(null);
|
|
931
1055
|
sessionSelectionTouched = false;
|
|
932
1056
|
localAuthEmail = "";
|
|
933
1057
|
localAuthTermsAccepted = false;
|
|
934
1058
|
localAuthAgeConfirmed = false;
|
|
935
|
-
renderLocalLogin("");
|
|
1059
|
+
renderLocalLogin("Signed out. Enter the email for the account you want to use.");
|
|
936
1060
|
} catch (error) {
|
|
1061
|
+
if (connected) startBillingPoll();
|
|
937
1062
|
if (button) { button.disabled = false; button.textContent = "Sign out & switch"; }
|
|
938
1063
|
setLocalAuthStatus(error && error.message ? error.message : "Could not switch accounts.", "error");
|
|
939
1064
|
}
|
|
@@ -1017,12 +1142,6 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
1017
1142
|
'<p>' + esc(lifecycle || "Your paid plan is active.") + '</p>' +
|
|
1018
1143
|
'</section>';
|
|
1019
1144
|
}
|
|
1020
|
-
function setupPricingUrl(plan, startTrial) {
|
|
1021
|
-
var paidPlan = plan === "power" ? "power" : "pro";
|
|
1022
|
-
return (billingStatus && billingStatus.pricingUrl)
|
|
1023
|
-
? billingStatus.pricingUrl + (startTrial ? "&start=" + paidPlan + "&trial=" + (billingStatus.trialAvailable === false ? "0" : "1") : "")
|
|
1024
|
-
: "https://echoknows.com/account?source=mcp_onboarding" + (startTrial ? "&start=" + paidPlan + "&trial=1" : "");
|
|
1025
|
-
}
|
|
1026
1145
|
function setupPlanConfirmed() {
|
|
1027
1146
|
return setupPlanChoice === "free" || paidRecallPlan(billingStatus && billingStatus.plan);
|
|
1028
1147
|
}
|
|
@@ -1038,7 +1157,7 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
1038
1157
|
sticker: "/hud-assets/echo-pricing-power-sticker.png",
|
|
1039
1158
|
stickerAlt: "Power Echo arrives with a gold key and a crew of notebook helpers.",
|
|
1040
1159
|
features: [
|
|
1041
|
-
"2,000
|
|
1160
|
+
"2,000 past chats",
|
|
1042
1161
|
"agent-heavy memory",
|
|
1043
1162
|
"2,000 memory recalls / week"
|
|
1044
1163
|
]
|
|
@@ -1054,7 +1173,7 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
1054
1173
|
stickerAlt: "Pro Echo organizes tabbed notebooks and a daily refresh control.",
|
|
1055
1174
|
popular: true,
|
|
1056
1175
|
features: [
|
|
1057
|
-
"500
|
|
1176
|
+
"500 past chats",
|
|
1058
1177
|
"daily memory updates",
|
|
1059
1178
|
"500 memory recalls / week"
|
|
1060
1179
|
]
|
|
@@ -1272,23 +1391,37 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
1272
1391
|
}
|
|
1273
1392
|
}
|
|
1274
1393
|
async function refreshBillingStatus() {
|
|
1275
|
-
if (billingStatusLoading) return;
|
|
1394
|
+
if (billingStatusLoading || !connected) return;
|
|
1395
|
+
var billingEpoch = accountStateEpoch;
|
|
1276
1396
|
billingStatusLoading = true;
|
|
1277
1397
|
try {
|
|
1278
1398
|
await maybeSyncPendingCheckout();
|
|
1279
|
-
|
|
1399
|
+
if (billingEpoch !== accountStateEpoch || !connected) return;
|
|
1400
|
+
var nextBillingStatus = await getJson("/billing-status");
|
|
1401
|
+
if (billingEpoch !== accountStateEpoch || !connected) return;
|
|
1402
|
+
billingStatus = nextBillingStatus;
|
|
1280
1403
|
billingLastCheckedAt = Date.now();
|
|
1281
1404
|
billingCheckMessage = "";
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1405
|
+
if (readyStage === "sessions" && setupPlanConfirmed()) {
|
|
1406
|
+
// Keep the visible promise in sync with the authoritative plan/quota response.
|
|
1407
|
+
// renderDashboard patches the mounted shell without recreating the page. If the
|
|
1408
|
+
// picker is already open after an upgrade, refresh its quota/action in place too.
|
|
1409
|
+
renderDashboard();
|
|
1410
|
+
var openPicker = document.getElementById("sessionPicker");
|
|
1411
|
+
if (openPicker && openPicker.open) renderSessionPicker();
|
|
1412
|
+
} else {
|
|
1413
|
+
renderSetupPlan(true);
|
|
1414
|
+
renderAccountChoice();
|
|
1415
|
+
renderSessionSelection(true);
|
|
1416
|
+
}
|
|
1285
1417
|
if (billingStatus && billingStatus.plan === "unknown") setSetupPlanStatus("Plan check unavailable. Original still works.");
|
|
1286
1418
|
} catch (_) {
|
|
1419
|
+
if (billingEpoch !== accountStateEpoch || !connected) return;
|
|
1287
1420
|
billingLastCheckedAt = Date.now();
|
|
1288
1421
|
billingCheckMessage = "Could not check payment yet. Echo will keep trying automatically.";
|
|
1289
1422
|
setSetupPlanStatus("Plan check unavailable. Original still works.");
|
|
1290
1423
|
} finally {
|
|
1291
|
-
billingStatusLoading = false;
|
|
1424
|
+
if (billingEpoch === accountStateEpoch) billingStatusLoading = false;
|
|
1292
1425
|
}
|
|
1293
1426
|
}
|
|
1294
1427
|
function refreshBillingOnFocus() { void refreshBillingStatus(); }
|
|
@@ -1348,7 +1481,7 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
1348
1481
|
}
|
|
1349
1482
|
}
|
|
1350
1483
|
async function openHostedPlanOptions() {
|
|
1351
|
-
await openHostedBilling(
|
|
1484
|
+
return await openHostedBilling(
|
|
1352
1485
|
"/billing-portal",
|
|
1353
1486
|
{},
|
|
1354
1487
|
"Opening secure plan management…",
|
|
@@ -1359,9 +1492,9 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
1359
1492
|
var questions = recallQuestions();
|
|
1360
1493
|
var primary = questions[0];
|
|
1361
1494
|
return '<div class="tryCard">' +
|
|
1362
|
-
'<h3 class="tryTitle">
|
|
1495
|
+
'<h3 class="tryTitle">Take it into a fresh session.</h3>' +
|
|
1363
1496
|
'<article class="recallPrompt recallPromptPrimary" data-recall-prompt="' + esc(primary) + '">' +
|
|
1364
|
-
'<div class="recallPromptHead"><span>
|
|
1497
|
+
'<div class="recallPromptHead"><span>Try this</span><button type="button" class="recallCopy" data-recall-copy aria-label="Copy your first recall question">' + setupIcon("copy") + '<span>Copy</span></button></div>' +
|
|
1365
1498
|
'<p class="recallPromptText">' + esc(primary) + '</p>' +
|
|
1366
1499
|
'<div class="recallPromptActions" aria-label="Try your first memory recall">' +
|
|
1367
1500
|
'<button type="button" class="recallAgent recallAgentPrimary" data-recall-codex><span class="recallAgentIcon"><img src="/hud-assets/codex.svg" alt="" /></span><span>Ask in Codex</span></button>' +
|