@echomem/mcp 1.4.42 → 1.4.44
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/README.md +25 -28
- package/dist/config-files.js +63 -0
- package/dist/context-analysis/claude-native-canonical.js +2 -2
- package/dist/context-analysis/vendored-canonical.js +2 -2
- package/dist/context-analysis/workspace-report.js +3 -3
- package/dist/hud/hooks.js +43 -31
- package/dist/index.js +5 -8
- package/dist/local-jsonl.js +87 -0
- package/dist/migrate.js +6 -4
- package/dist/onboarding-stats.js +16 -0
- package/dist/save-checkpoint-hook.js +1 -1
- package/dist/setup-page/client-core.js +18 -372
- package/dist/setup-page/client-extraction.js +19 -134
- package/dist/setup-page/client-lifecycle.js +31 -101
- package/dist/setup-page/client.js +0 -2
- package/dist/setup-page/styles-extraction.js +1 -31
- package/dist/setup-page/styles-foundation.js +0 -89
- package/dist/setup-page/styles-mvp.js +10 -155
- package/dist/setup-page/styles-website-alignment.js +0 -204
- package/dist/setup-page/styles.js +0 -4
- package/dist/setup-page.js +4 -4
- package/dist/setup-preview.js +4 -212
- package/dist/setup.js +378 -668
- package/dist/v1-contract.js +0 -8
- package/package.json +9 -7
- package/dist/city/README.md +0 -9
- package/dist/city/echo-ai-city-only.html +0 -2232
- package/dist/city/echo-extraction-plate.html +0 -330
- package/dist/city/echo-face-cutout.png +0 -0
- package/dist/city/personality_stickers/bossy.png +0 -0
- package/dist/city/personality_stickers/ghosty.png +0 -0
- package/dist/city/personality_stickers/loopy.png +0 -0
- package/dist/city/personality_stickers/lusty.png +0 -0
- package/dist/city/personality_stickers/maxxy.png +0 -0
- package/dist/city/personality_stickers/tabby.png +0 -0
- package/dist/city/vendor/OrbitControls.js +0 -1417
- package/dist/city/vendor/RoundedBoxGeometry.js +0 -155
- package/dist/city/vendor/echo_general-file-21.riv +0 -0
- package/dist/city/vendor/rive.js +0 -8139
- package/dist/city/vendor/rive.wasm +0 -0
- package/dist/city/vendor/three.module.min.js +0 -6
- package/dist/forensics.js +0 -1531
- package/dist/report.js +0 -721
- package/dist/setup-page/client-report-audit.js +0 -819
- package/dist/setup-page/client-report-city.js +0 -356
- package/dist/setup-page/client-report.js +0 -6
- package/dist/setup-page/styles-city-report.js +0 -880
- package/dist/setup-page/styles-context-audit.js +0 -470
|
@@ -1,97 +1,7 @@
|
|
|
1
1
|
// Generated by splitting the previous inline setup page. Keep this source readable: it is emitted into one browser IIFE.
|
|
2
2
|
export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dashboard (post-auth extraction) ---------- */
|
|
3
|
-
function acceptReportScanId(envelope) {
|
|
4
|
-
var scanId = envelope && typeof envelope.scanId === "string" ? envelope.scanId : "";
|
|
5
|
-
if (!scanId) throw new Error("Report response is missing its scan identity.");
|
|
6
|
-
if (activeScanId && activeScanId !== scanId) throw new Error("The local scan identity changed before the report completed.");
|
|
7
|
-
activeScanId = scanId;
|
|
8
|
-
}
|
|
9
|
-
function validateProductionReportEnvelope(envelope) {
|
|
10
|
-
if (!envelope || typeof envelope !== "object" || envelope.schemaVersion !== 1 || envelope.mode !== "production") {
|
|
11
|
-
throw new Error("Report response has an unsupported production schema.");
|
|
12
|
-
}
|
|
13
|
-
acceptReportScanId(envelope);
|
|
14
|
-
if (["scanning", "ready", "empty", "failed"].indexOf(envelope.kind) === -1) {
|
|
15
|
-
throw new Error("Report response has an unknown state.");
|
|
16
|
-
}
|
|
17
|
-
if (envelope.kind === "scanning") return envelope;
|
|
18
|
-
if (envelope.kind === "failed") {
|
|
19
|
-
if (!envelope.error || typeof envelope.error.code !== "string" || typeof envelope.error.message !== "string") {
|
|
20
|
-
throw new Error("Report failure response is malformed.");
|
|
21
|
-
}
|
|
22
|
-
return envelope;
|
|
23
|
-
}
|
|
24
|
-
var r = envelope.report;
|
|
25
|
-
if (!r || typeof r !== "object" || r.schemaVersion !== 1 || r.dataOrigin !== "local-workspace-scan") {
|
|
26
|
-
throw new Error("Report did not prove that it came from this local workspace scan.");
|
|
27
|
-
}
|
|
28
|
-
var scale = r.scale || {};
|
|
29
|
-
var sessions = Number(scale.sessionCount);
|
|
30
|
-
var gs = gsClean(r.canonicalGoldenStandard);
|
|
31
|
-
var summary = (gs && gs.summary) || {};
|
|
32
|
-
var canonicalSessions = Number(summary.sessionsAnalyzed || 0);
|
|
33
|
-
var canonicalInput = Number(summary.officialInputTokens || 0);
|
|
34
|
-
var skipped = Number((gs && gs.diagnostics && gs.diagnostics.skippedSessions) || 0);
|
|
35
|
-
if (envelope.kind === "empty") {
|
|
36
|
-
if (sessions !== 0 || canonicalSessions !== 0 || canonicalInput !== 0 || Number(scale.totalInputTokens || 0) !== 0) {
|
|
37
|
-
throw new Error("An empty report contained non-empty usage data.");
|
|
38
|
-
}
|
|
39
|
-
return envelope;
|
|
40
|
-
}
|
|
41
|
-
if (
|
|
42
|
-
!gs ||
|
|
43
|
-
!Number.isFinite(sessions) || sessions <= 0 ||
|
|
44
|
-
!Number.isFinite(canonicalSessions) || canonicalSessions <= 0 || canonicalSessions > sessions ||
|
|
45
|
-
!Number.isFinite(skipped) || skipped < 0 || canonicalSessions + skipped !== sessions ||
|
|
46
|
-
!Number.isFinite(canonicalInput) || canonicalInput <= 0
|
|
47
|
-
) {
|
|
48
|
-
throw new Error("Canonical analysis is incomplete or does not match the provider session cohort.");
|
|
49
|
-
}
|
|
50
|
-
if (!Array.isArray(r.repos) || r.repos.length === 0) {
|
|
51
|
-
throw new Error("The local report has no workspace rows to render.");
|
|
52
|
-
}
|
|
53
|
-
gsBillingProjection(r, gs);
|
|
54
|
-
return envelope;
|
|
55
|
-
}
|
|
56
|
-
function renderReportIssue(code, message) {
|
|
57
|
-
void message;
|
|
58
|
-
report = null;
|
|
59
|
-
reportEnvelope = null;
|
|
60
|
-
resetReportSurface();
|
|
61
|
-
var canContinue = String(code || "").indexOf("REPORT_") === 0 || code === "RENDER_FAILED";
|
|
62
|
-
setHead("We couldn't finish this scan", "Needs attention");
|
|
63
|
-
app.className = "reportMessageStage";
|
|
64
|
-
app.innerHTML =
|
|
65
|
-
'<section class="reportMessage" data-report-state="failed">' +
|
|
66
|
-
'<h2>We couldn’t finish this scan.</h2>' +
|
|
67
|
-
'<p>Your coding history is unchanged.</p>' +
|
|
68
|
-
(canContinue
|
|
69
|
-
? '<p>This optional report can be retried later.</p><div class="actions"><button type="button" class="primary" data-connect-echo>Continue without report</button></div>'
|
|
70
|
-
: '<p>Close this tab and run <code>echomem-mcp init</code> again.</p>') +
|
|
71
|
-
'<details class="reportTechnical"><summary>Technical details</summary><code>' + esc(code || "REPORT_FAILED") + '</code></details>' +
|
|
72
|
-
'</section>';
|
|
73
|
-
if (canContinue) bindConnect();
|
|
74
|
-
}
|
|
75
|
-
function renderEmptyReport() {
|
|
76
|
-
report = null;
|
|
77
|
-
reportEnvelope = null;
|
|
78
|
-
resetReportSurface();
|
|
79
|
-
setHead("No local coding history found", "Local scan complete");
|
|
80
|
-
app.className = "reportMessageStage";
|
|
81
|
-
app.innerHTML =
|
|
82
|
-
'<section class="reportMessage" data-report-state="empty">' +
|
|
83
|
-
'<h2>No coding history found.</h2>' +
|
|
84
|
-
'<p>You can continue now and import local coding history later.</p>' +
|
|
85
|
-
'<div class="actions"><button type="button" class="primary" data-connect-echo>Continue setup</button></div>' +
|
|
86
|
-
'</section>';
|
|
87
|
-
bindConnect();
|
|
88
|
-
}
|
|
89
|
-
function renderBridgeIssue() {
|
|
90
|
-
renderReportIssue("BRIDGE_UNREACHABLE", "The local bridge stopped answering before your report was ready. Your terminal may have closed or the process may have stopped.");
|
|
91
|
-
}
|
|
92
3
|
function renderDiscoveryCheck(slow) {
|
|
93
4
|
void slow;
|
|
94
|
-
setCityMode(false);
|
|
95
5
|
setExtractMode(false);
|
|
96
6
|
setReadyMode(true);
|
|
97
7
|
setHead("Loading", "Working");
|
|
@@ -99,7 +9,6 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
99
9
|
app.innerHTML = '<div class="discoverySpinner" role="status" aria-label="Loading"><span aria-hidden="true"></span></div>';
|
|
100
10
|
}
|
|
101
11
|
function renderDiscoveryCheckIssue(error) {
|
|
102
|
-
setCityMode(false);
|
|
103
12
|
setExtractMode(false);
|
|
104
13
|
setReadyMode(true);
|
|
105
14
|
setHead("Session refresh paused", "Needs attention");
|
|
@@ -107,7 +16,7 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
107
16
|
app.innerHTML =
|
|
108
17
|
'<div style="padding:24px"><h2>Echo could not refresh session status</h2>' +
|
|
109
18
|
'<p class="helper">Keep the terminal open and try the local check again.</p>' +
|
|
110
|
-
(error ? '<details class="
|
|
19
|
+
(error ? '<details class="technicalDetails"><summary>Technical details</summary><code>' + esc(error) + '</code></details>' : '') +
|
|
111
20
|
'<div class="actions"><button id="retryDiscoveryCheck" class="primary">Try again</button></div></div>';
|
|
112
21
|
var retry = document.getElementById("retryDiscoveryCheck");
|
|
113
22
|
if (retry) retry.onclick = function () {
|
|
@@ -116,13 +25,11 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
116
25
|
};
|
|
117
26
|
}
|
|
118
27
|
function renderLogoutPending() {
|
|
119
|
-
setCityMode(false);
|
|
120
28
|
setHead("Signing out locally", "Working");
|
|
121
29
|
app.className = "loading-panel";
|
|
122
30
|
app.innerHTML = '<div style="padding:24px"><h2>Signing this device out</h2><p class="helper">If Echo is scanning local files, this can take a few seconds to answer.</p></div>';
|
|
123
31
|
}
|
|
124
32
|
function renderLogoutIssue(error) {
|
|
125
|
-
setCityMode(false);
|
|
126
33
|
setHead("Sign out did not finish", "Needs attention");
|
|
127
34
|
app.className = "loading-panel";
|
|
128
35
|
app.innerHTML =
|
|
@@ -136,7 +43,6 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
136
43
|
if (statsPollStarted) return;
|
|
137
44
|
var statsEpoch = accountStateEpoch;
|
|
138
45
|
statsPollStarted = true;
|
|
139
|
-
setCityMode(false);
|
|
140
46
|
setExtractMode(false);
|
|
141
47
|
setReadyMode(true);
|
|
142
48
|
renderDiscoveryCheck(false);
|
|
@@ -169,23 +75,14 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
169
75
|
if (res.status !== 202) throw new Error(await res.text() || ("HTTP " + res.status));
|
|
170
76
|
} catch (e) {
|
|
171
77
|
lastError = e && e.message ? e.message : String(e);
|
|
172
|
-
if (!stats && Date.now() - started > 25000)
|
|
78
|
+
if (!stats && Date.now() - started > 25000) renderDiscoveryCheckIssue(lastError);
|
|
173
79
|
if (Date.now() - started > 20000) { statsSlow = true; if (stats) renderDashboard(); }
|
|
174
80
|
}
|
|
175
81
|
await sleep(statsSlow ? 1500 : 500);
|
|
176
82
|
}
|
|
177
83
|
} finally { statsPollStarted = false; }
|
|
178
84
|
}
|
|
179
|
-
// The plate slot: ONLY the WebGL clay city (echo-extraction-plate.html). It self-fetches /report
|
|
180
|
-
// (your repos) and polls /progress (extraction state), growing as memories are created. The old SVG
|
|
181
|
-
// fallback board was removed — it was bleeding through behind the transparent canvas (the double board).
|
|
182
|
-
function plateBlock() {
|
|
183
|
-
return '<div class="exPlate"><div class="plateStack">' +
|
|
184
|
-
'<iframe class="plateGl" title="Extraction plate" src="/city/echo-extraction-plate.html?nonce=' + encodeURIComponent(nonce) + '"></iframe>' +
|
|
185
|
-
'</div></div>';
|
|
186
|
-
}
|
|
187
85
|
function renderPlanGate() {
|
|
188
|
-
setCityMode(false);
|
|
189
86
|
setExtractMode(false);
|
|
190
87
|
setReadyMode(true);
|
|
191
88
|
setHead("Choose your Echo", "Plan");
|
|
@@ -198,7 +95,7 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
198
95
|
readyStage = "plan";
|
|
199
96
|
app.className = "planGateStage";
|
|
200
97
|
app.innerHTML =
|
|
201
|
-
setupJourney(
|
|
98
|
+
setupJourney(2) +
|
|
202
99
|
'<section class="planGateDecision">' +
|
|
203
100
|
'<div class="setupPlanSlot" id="setupPlan"></div>' +
|
|
204
101
|
'<aside class="planGateAccount" aria-label="Connected account"><div class="setupAccountSlot" id="setupAccount"></div></aside>' +
|
|
@@ -207,7 +104,6 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
207
104
|
renderAccountChoice();
|
|
208
105
|
}
|
|
209
106
|
function renderDashboard(error) {
|
|
210
|
-
setCityMode(false);
|
|
211
107
|
setExtractMode(false);
|
|
212
108
|
setReadyMode(true);
|
|
213
109
|
var migratable = stats && stats.migratable ? stats.migratable : {};
|
|
@@ -253,19 +149,17 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
253
149
|
? "<strong>" + esc(number(pendN)) + " sessions</strong> found."
|
|
254
150
|
: "<strong>" + esc(number(pendN)) + " sessions</strong> are ready to review."));
|
|
255
151
|
var sub = degradedWithoutCounts
|
|
256
|
-
? "You can finish setup now. Your conversations stay on this
|
|
152
|
+
? "You can finish setup now. Your conversations stay on this computer, and you can retry the history import later."
|
|
257
153
|
: !pendingTrusted
|
|
258
154
|
? "Echo is matching your local history against what is already in memory."
|
|
259
155
|
: (knownDone
|
|
260
156
|
? (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.")
|
|
261
157
|
: (planLimited
|
|
262
158
|
? (importableNow > 0
|
|
263
|
-
? currentPlanLabel + " can import " + number(importableNow) + " of them. Choose which ones to bring into memory; the other " + number(deferredByPlan) + " stay on this
|
|
264
|
-
: currentPlanLabel + " has no coding-session imports remaining. These sessions stay on this
|
|
159
|
+
? currentPlanLabel + " can import " + number(importableNow) + " of them. Choose which ones to bring into memory; the other " + number(deferredByPlan) + " stay on this computer."
|
|
160
|
+
: currentPlanLabel + " has no coding-session imports remaining. These sessions stay on this computer.")
|
|
265
161
|
: ""));
|
|
266
|
-
// Mount the dashboard shell
|
|
267
|
-
// (Rebuilding app.innerHTML on every partial-scan poll reloaded the iframe → the city flickered.)
|
|
268
|
-
// Later polls only patch the text/counts in place; the iframe stays mounted.
|
|
162
|
+
// Mount the dashboard shell once; later polls only patch its text and counts.
|
|
269
163
|
var live = dashMounted && document.getElementById("exHeadline");
|
|
270
164
|
if (!live) {
|
|
271
165
|
extractMounted = false;
|
|
@@ -277,7 +171,7 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
277
171
|
// one-line strip, not stat cards; the ETA is a commitment-moment note under the button,
|
|
278
172
|
// not a display-size headline.
|
|
279
173
|
app.innerHTML =
|
|
280
|
-
setupJourney(
|
|
174
|
+
setupJourney(3) +
|
|
281
175
|
'<aside class="readyUtilityNav" aria-label="Account and plan settings">' +
|
|
282
176
|
'<div class="planGateAccount readyAccountNav" aria-label="Connected account"><div class="setupAccountSlot" id="setupAccount"></div></div>' +
|
|
283
177
|
'<div class="readyPlanNav" aria-label="Current plan"><span class="readyPlanNavLabel">Plan</span><div class="setupPlanSlot" id="setupPlan"></div></div>' +
|
|
@@ -325,7 +219,7 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
325
219
|
? '<span class="pf">' + srcIcon("codex", "CX") + '<strong>' + esc(number(codexN)) + '</strong> Codex</span>' +
|
|
326
220
|
'<span class="pfDot">·</span>' +
|
|
327
221
|
'<span class="pf">' + srcIcon("claude-desktop", "CL") + '<strong>' + esc(number(claudeN)) + '</strong> Claude Code</span>' +
|
|
328
|
-
'<span class="pfNote">found on this
|
|
222
|
+
'<span class="pfNote">found on this computer</span>'
|
|
329
223
|
: "");
|
|
330
224
|
// Reassurances live at the moment of commitment — right under the button.
|
|
331
225
|
document.getElementById("exEta").innerHTML = degradedWithoutCounts
|
|
@@ -392,14 +286,12 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
392
286
|
stopBillingPoll();
|
|
393
287
|
decisionMade = true;
|
|
394
288
|
extractionEnded = false;
|
|
395
|
-
setCityMode(false);
|
|
396
|
-
setScanMode(false);
|
|
397
289
|
setReadyMode(false);
|
|
398
290
|
setExtractMode(true);
|
|
399
291
|
setHead("Extracting", "Starting");
|
|
400
292
|
app.className = "extractionStartStage";
|
|
401
293
|
app.innerHTML =
|
|
402
|
-
setupJourney(
|
|
294
|
+
setupJourney(3) +
|
|
403
295
|
'<div class="discoverySpinner" role="status" aria-label="Starting import"><span aria-hidden="true"></span></div>';
|
|
404
296
|
var started;
|
|
405
297
|
try {
|
|
@@ -491,7 +383,6 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
491
383
|
function renderProgress(progress) {
|
|
492
384
|
if (extractionEnded) return;
|
|
493
385
|
lastExtractionProgress = progress;
|
|
494
|
-
setCityMode(false);
|
|
495
386
|
setExtractMode(true);
|
|
496
387
|
var status = progress.status;
|
|
497
388
|
var done = status === "completed";
|
|
@@ -503,18 +394,15 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
503
394
|
var pct = total > 0 ? Math.round((completed / total) * 100) : (done ? 100 : 0);
|
|
504
395
|
var preparing = (status === "starting" || status === "preparing");
|
|
505
396
|
app.className = "";
|
|
506
|
-
// Mount the shell
|
|
507
|
-
// each poll only rebuilds the text column.
|
|
397
|
+
// Mount the shell once; each poll only rebuilds the text column.
|
|
508
398
|
if (!extractMounted) {
|
|
509
399
|
app.innerHTML =
|
|
510
|
-
'<div id="setupJourneySlot">' + setupJourney(
|
|
511
|
-
'<section class="exHero"><div class="exCopy" id="exText"></div>'
|
|
512
|
-
plateBlock() +
|
|
513
|
-
'</section>';
|
|
400
|
+
'<div id="setupJourneySlot">' + setupJourney(3) + '</div>' +
|
|
401
|
+
'<section class="exHero"><div class="exCopy" id="exText"></div></section>';
|
|
514
402
|
extractMounted = true;
|
|
515
403
|
}
|
|
516
404
|
var journeySlot = document.getElementById("setupJourneySlot");
|
|
517
|
-
if (journeySlot) journeySlot.innerHTML = setupJourney(done ?
|
|
405
|
+
if (journeySlot) journeySlot.innerHTML = setupJourney(done ? 4 : 3);
|
|
518
406
|
var shell = app.querySelector(".exHero");
|
|
519
407
|
if (shell) {
|
|
520
408
|
shell.classList.toggle("exHeroResult", done || stopped);
|
|
@@ -1035,13 +923,12 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
1035
923
|
? account.email
|
|
1036
924
|
: "the connected account";
|
|
1037
925
|
accountSwitchPending = true;
|
|
1038
|
-
setCityMode(false);
|
|
1039
926
|
setExtractMode(false);
|
|
1040
927
|
setReadyMode(true);
|
|
1041
928
|
setHead("Switch EchoMem account", "Confirm");
|
|
1042
929
|
app.className = "accountSwitchStage";
|
|
1043
930
|
app.innerHTML =
|
|
1044
|
-
setupJourney(
|
|
931
|
+
setupJourney(3) +
|
|
1045
932
|
'<section class="accountSwitchConfirm" aria-labelledby="accountSwitchTitle">' +
|
|
1046
933
|
'<button type="button" class="flowBack" id="cancelAccountSwitch">← Back</button>' +
|
|
1047
934
|
'<h2 id="accountSwitchTitle">Switch account?</h2>' +
|
|
@@ -1536,7 +1423,7 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
1536
1423
|
'<p class="recallPromptText">' + esc(primary) + '</p>' +
|
|
1537
1424
|
'<div class="recallPromptActions" aria-label="Try your first memory recall">' +
|
|
1538
1425
|
'<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>' +
|
|
1539
|
-
'<button type="button" class="recallAgent is-claude" data-recall-claude><span class="recallAgentIcon"><img src="/hud-assets/claude.svg" alt="" /></span><span>Use Claude instead</span></button>' +
|
|
1426
|
+
(canOpenClaudeDesktop ? '<button type="button" class="recallAgent is-claude" data-recall-claude><span class="recallAgentIcon"><img src="/hud-assets/claude.svg" alt="" /></span><span>Use Claude instead</span></button>' : '') +
|
|
1540
1427
|
'</div>' +
|
|
1541
1428
|
'</article>' +
|
|
1542
1429
|
'<p class="launchStatus" id="launchStatus"></p>' +
|
|
@@ -1554,7 +1441,6 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
1554
1441
|
});
|
|
1555
1442
|
}
|
|
1556
1443
|
function renderEnding() {
|
|
1557
|
-
setCityMode(false);
|
|
1558
1444
|
setExtractMode(true);
|
|
1559
1445
|
// This view replaces the live extraction shell. Mark it unmounted so a failed
|
|
1560
1446
|
// pause can rebuild the progress page instead of trying to patch missing DOM.
|
|
@@ -1562,7 +1448,7 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
1562
1448
|
setHead("Ending extraction", "Ending");
|
|
1563
1449
|
app.className = "";
|
|
1564
1450
|
app.innerHTML =
|
|
1565
|
-
setupJourney(
|
|
1451
|
+
setupJourney(3) +
|
|
1566
1452
|
'<section class="lifecycleState lifecyclePending">' +
|
|
1567
1453
|
'<h2>Stopping the remaining queued work…</h2>' +
|
|
1568
1454
|
'<p>Saved memories stay intact.</p>' +
|
|
@@ -1570,13 +1456,12 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
1570
1456
|
'</section>';
|
|
1571
1457
|
}
|
|
1572
1458
|
function renderSkipped() {
|
|
1573
|
-
setCityMode(false);
|
|
1574
1459
|
setExtractMode(true);
|
|
1575
1460
|
setHead("Extraction ended for now", "Ended");
|
|
1576
1461
|
app.className = "";
|
|
1577
1462
|
var savedCount = Math.max(0, Number(lastExtractionProgress && lastExtractionProgress.extracted || 0));
|
|
1578
1463
|
app.innerHTML =
|
|
1579
|
-
setupJourney(savedCount > 0 ?
|
|
1464
|
+
setupJourney(savedCount > 0 ? 4 : 3) +
|
|
1580
1465
|
'<section class="lifecycleState lifecyclePaused">' +
|
|
1581
1466
|
'<header class="lifecycleIntro">' +
|
|
1582
1467
|
'<h2>' + (savedCount > 0 ? esc(number(savedCount)) + ' memories are ready to use.' : 'Import ended safely.') + '</h2>' +
|
|
@@ -1640,7 +1525,7 @@ export const SETUP_PAGE_CLIENT_EXTRACTION = String.raw ` /* ---------- dash
|
|
|
1640
1525
|
statsSlow = false;
|
|
1641
1526
|
connected = false;
|
|
1642
1527
|
decisionMade = false;
|
|
1643
|
-
|
|
1528
|
+
renderLocalLogin("Signed out locally. Connect EchoMem again to extract.");
|
|
1644
1529
|
} catch (e) {
|
|
1645
1530
|
decisionMade = false;
|
|
1646
1531
|
renderLogoutIssue(e && e.message ? e.message : String(e));
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
// Generated by splitting the previous inline setup page. Keep this source readable: it is emitted into one browser IIFE.
|
|
2
|
-
export const SETUP_PAGE_CLIENT_LIFECYCLE = String.raw ` /* ----------
|
|
3
|
-
function
|
|
4
|
-
setScanMode(false);
|
|
5
|
-
setCityMode(false);
|
|
2
|
+
export const SETUP_PAGE_CLIENT_LIFECYCLE = String.raw ` /* ---------- local-history permission and setup routing ---------- */
|
|
3
|
+
function renderLocalHistoryConsent() {
|
|
6
4
|
setExtractMode(false);
|
|
7
5
|
setReadyMode(true);
|
|
8
6
|
setHead("Local file access", "Consent");
|
|
@@ -14,15 +12,15 @@ export const SETUP_PAGE_CLIENT_LIFECYCLE = String.raw ` /* ---------- scan-
|
|
|
14
12
|
'<img class="mvpConsentEcho" src="/hud-assets/echo-face-cutout.png" alt="" />' +
|
|
15
13
|
'<div class="mvpConsentPitch">' +
|
|
16
14
|
'<h2 id="mvpConsentTitle">Start with the context you’ve already built.</h2>' +
|
|
17
|
-
'<p class="mvpConsentIntro">
|
|
15
|
+
'<p class="mvpConsentIntro">Choose which local coding conversations to bring into EchoMem.</p>' +
|
|
18
16
|
'</div>' +
|
|
19
17
|
'</div>' +
|
|
20
|
-
'<p class="mvpConsentTrustLine">Your history stays on
|
|
21
|
-
'<button type="button" class="primary mvpPrimary" id="
|
|
18
|
+
'<p class="mvpConsentTrustLine">Your history stays on this computer until you choose what to import. ' + dataHandlingLink("Data handling") + '</p>' +
|
|
19
|
+
'<button type="button" class="primary mvpPrimary" id="allowLocalHistory">Continue <span aria-hidden="true">→</span></button>' +
|
|
22
20
|
'<p class="consentFine" id="consentStatus" role="status" aria-live="polite"></p>' +
|
|
23
21
|
'</section>';
|
|
24
|
-
var allow = document.getElementById("
|
|
25
|
-
if (allow) allow.onclick =
|
|
22
|
+
var allow = document.getElementById("allowLocalHistory");
|
|
23
|
+
if (allow) allow.onclick = allowLocalHistory;
|
|
26
24
|
}
|
|
27
25
|
function setConsentStatus(message, tone) {
|
|
28
26
|
var node = document.getElementById("consentStatus");
|
|
@@ -31,18 +29,26 @@ export const SETUP_PAGE_CLIENT_LIFECYCLE = String.raw ` /* ---------- scan-
|
|
|
31
29
|
node.classList.toggle("is-required", tone === "required");
|
|
32
30
|
}
|
|
33
31
|
}
|
|
34
|
-
|
|
35
|
-
|
|
32
|
+
function continueAfterLocalHistoryConsent() {
|
|
33
|
+
if (connected) {
|
|
34
|
+
try { void refreshBillingStatus(); } catch (_) {}
|
|
35
|
+
void waitForStats();
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
rememberSetupPlanChoice("");
|
|
39
|
+
renderLocalLogin("");
|
|
40
|
+
}
|
|
41
|
+
async function allowLocalHistory() {
|
|
42
|
+
var allow = document.getElementById("allowLocalHistory");
|
|
36
43
|
if (allow) allow.disabled = true;
|
|
37
|
-
setConsentStatus("
|
|
44
|
+
setConsentStatus("Saving your choice...");
|
|
38
45
|
try {
|
|
39
|
-
await postJson("/
|
|
46
|
+
await postJson("/local-history-consent", { allowed: true }, 5000);
|
|
40
47
|
localHistoryConsentGranted = true;
|
|
41
|
-
|
|
48
|
+
continueAfterLocalHistoryConsent();
|
|
42
49
|
} catch (error) {
|
|
43
50
|
// The localhost bridge may have accepted consent even if the browser loses the POST
|
|
44
|
-
// response while it swaps connections. Recover from the server's authoritative state
|
|
45
|
-
// instead of leaving the user forever on "Starting local scan...".
|
|
51
|
+
// response while it swaps connections. Recover from the server's authoritative state.
|
|
46
52
|
try {
|
|
47
53
|
var configResponse = await fetchWithTimeout(
|
|
48
54
|
"/config?nonce=" + encodeURIComponent(nonce),
|
|
@@ -52,80 +58,14 @@ export const SETUP_PAGE_CLIENT_LIFECYCLE = String.raw ` /* ---------- scan-
|
|
|
52
58
|
var config = configResponse.ok ? await configResponse.json() : null;
|
|
53
59
|
if (config && config.consentGranted === true) {
|
|
54
60
|
localHistoryConsentGranted = true;
|
|
55
|
-
|
|
61
|
+
continueAfterLocalHistoryConsent();
|
|
56
62
|
return;
|
|
57
63
|
}
|
|
58
64
|
} catch (_) {}
|
|
59
|
-
|
|
65
|
+
if (allow) allow.disabled = false;
|
|
66
|
+
setConsentStatus(error && error.message ? error.message : "Could not save local-history access.", "required");
|
|
60
67
|
}
|
|
61
68
|
}
|
|
62
|
-
async function showReport() {
|
|
63
|
-
if (reportPollStarted) return;
|
|
64
|
-
reportPollStarted = true;
|
|
65
|
-
if (!report) renderLoading();
|
|
66
|
-
try {
|
|
67
|
-
var fails = 0;
|
|
68
|
-
for (;;) {
|
|
69
|
-
if (decisionMade) return;
|
|
70
|
-
var res;
|
|
71
|
-
try {
|
|
72
|
-
res = await fetchWithTimeout("/report?nonce=" + encodeURIComponent(nonce), { credentials: "omit", cache: "no-store" }, 12000);
|
|
73
|
-
} catch (_) {
|
|
74
|
-
// Only a RUN of consecutive failures is a real bridge problem. A long scan that keeps
|
|
75
|
-
// answering valid 202 envelopes must never trip this.
|
|
76
|
-
fails++;
|
|
77
|
-
if (fails >= 8) { renderBridgeIssue(); return; }
|
|
78
|
-
await sleep(700);
|
|
79
|
-
continue;
|
|
80
|
-
}
|
|
81
|
-
if (res.status === 403) {
|
|
82
|
-
renderReportIssue("SETUP_SESSION_INVALID", "This setup link is missing or has an invalid local session nonce.");
|
|
83
|
-
return;
|
|
84
|
-
}
|
|
85
|
-
var envelope;
|
|
86
|
-
try {
|
|
87
|
-
envelope = await res.json();
|
|
88
|
-
} catch (_) {
|
|
89
|
-
renderReportIssue("REPORT_RESPONSE_INVALID", "The local bridge returned a report response that could not be decoded.");
|
|
90
|
-
return;
|
|
91
|
-
}
|
|
92
|
-
try {
|
|
93
|
-
validateProductionReportEnvelope(envelope);
|
|
94
|
-
} catch (_) {
|
|
95
|
-
renderReportIssue("REPORT_SCHEMA_INVALID", "The local report failed its provenance or ledger consistency checks.");
|
|
96
|
-
return;
|
|
97
|
-
}
|
|
98
|
-
if (res.status === 202 && envelope.kind === "scanning") {
|
|
99
|
-
fails = 0;
|
|
100
|
-
reportEnvelope = envelope;
|
|
101
|
-
scheduleReportToCity();
|
|
102
|
-
renderLoading();
|
|
103
|
-
await sleep(700);
|
|
104
|
-
continue;
|
|
105
|
-
}
|
|
106
|
-
if (res.status === 200 && envelope.kind === "empty") {
|
|
107
|
-
renderEmptyReport();
|
|
108
|
-
return;
|
|
109
|
-
}
|
|
110
|
-
if (res.status === 200 && envelope.kind === "ready") {
|
|
111
|
-
reportEnvelope = envelope;
|
|
112
|
-
report = envelope.report;
|
|
113
|
-
try {
|
|
114
|
-
renderForensicReport();
|
|
115
|
-
} catch (_) {
|
|
116
|
-
renderReportIssue("RENDER_FAILED", "Your validated local report could not be rendered safely.");
|
|
117
|
-
}
|
|
118
|
-
return;
|
|
119
|
-
}
|
|
120
|
-
if (envelope.kind === "failed") {
|
|
121
|
-
renderReportIssue(envelope.error.code, envelope.error.message);
|
|
122
|
-
return;
|
|
123
|
-
}
|
|
124
|
-
renderReportIssue("REPORT_STATE_MISMATCH", "The local bridge returned a report state that did not match its HTTP status.");
|
|
125
|
-
return;
|
|
126
|
-
}
|
|
127
|
-
} finally { reportPollStarted = false; }
|
|
128
|
-
}
|
|
129
69
|
|
|
130
70
|
window.addEventListener("message", function (event) {
|
|
131
71
|
if (event.origin !== location.origin) return;
|
|
@@ -140,27 +80,22 @@ export const SETUP_PAGE_CLIENT_LIFECYCLE = String.raw ` /* ---------- scan-
|
|
|
140
80
|
rememberSetupPlanChoice("");
|
|
141
81
|
try { void refreshBillingStatus(); } catch (_) {}
|
|
142
82
|
void waitForStats();
|
|
143
|
-
} else if (data.type === "echomem:city-ready" && data.nonce === nonce) {
|
|
144
|
-
scheduleReportToCity();
|
|
145
|
-
} else if (data.type === "echomem:connect-click") {
|
|
146
|
-
onConnectClick();
|
|
147
83
|
}
|
|
148
84
|
});
|
|
149
85
|
async function init() {
|
|
150
86
|
if (!nonce) throw new Error("Missing setup nonce. Re-run echomem-mcp init.");
|
|
151
|
-
// Older builds cached a report under the nonce. It is never an authority now; the current
|
|
152
|
-
// server envelope and scan identity are the only inputs allowed to unlock personalized UI.
|
|
153
|
-
try { sessionStorage.removeItem("echomem:setup-report:" + nonce); } catch (_) {}
|
|
154
87
|
var configLoaded = false;
|
|
155
88
|
var consentRequired = true;
|
|
156
89
|
var consentGranted = false;
|
|
157
|
-
// Learn the connection state and server-side consent gate before routing.
|
|
158
90
|
try {
|
|
159
91
|
var cfg = await getJson("/config");
|
|
160
92
|
configLoaded = true;
|
|
161
93
|
connected = !!cfg.connected;
|
|
162
94
|
setupFlow = cfg.flow === "login" ? "login" : "onboarding";
|
|
163
95
|
workspacePath = cfg.workspacePath || workspacePath;
|
|
96
|
+
setupPlatform = cfg.platform || setupPlatform;
|
|
97
|
+
canOpenClaudeDesktop = !!(cfg.capabilities && cfg.capabilities.openClaudeDesktop);
|
|
98
|
+
canOpenAgentSessions = !!(cfg.capabilities && cfg.capabilities.openAgentSessions);
|
|
164
99
|
consentRequired = cfg.consentRequired !== false;
|
|
165
100
|
consentGranted = cfg.consentGranted === true;
|
|
166
101
|
localHistoryConsentGranted = consentGranted;
|
|
@@ -173,32 +108,27 @@ export const SETUP_PAGE_CLIENT_LIFECYCLE = String.raw ` /* ---------- scan-
|
|
|
173
108
|
if (!connected) rememberSetupPlanChoice("");
|
|
174
109
|
if (params.get("connected") === "1") {
|
|
175
110
|
if (!configLoaded || (consentRequired && !consentGranted)) {
|
|
176
|
-
|
|
111
|
+
renderLocalHistoryConsent();
|
|
177
112
|
setConsentStatus("Local history access is required before you can connect EchoMem or continue setup.", "required");
|
|
178
113
|
return;
|
|
179
114
|
}
|
|
180
115
|
connected = true;
|
|
181
116
|
if (notifyOpenerConnected()) {
|
|
182
|
-
// Popup flow: hand the main tab back control, then this popup closes.
|
|
183
117
|
setHead("EchoMem connected", "Done");
|
|
184
118
|
app.className = "notice";
|
|
185
119
|
app.innerHTML = '<h2 style="color:#203820">This device is connected</h2><p style="margin-top:10px">Returning to the local setup page...</p>';
|
|
186
|
-
// Fallback: if window.close() was blocked (auth opened as a tab, or the browser refused),
|
|
187
|
-
// this window would otherwise hang on "Returning..." forever. After a beat, take over here
|
|
188
|
-
// so the user always lands on the dashboard instead of a dead page.
|
|
189
120
|
setTimeout(function () { if (!window.closed) void waitForStats(); }, 1500);
|
|
190
121
|
} else {
|
|
191
|
-
// Same-tab fallback (popup was blocked): go straight to the extraction dashboard.
|
|
192
122
|
try { void refreshBillingStatus(); } catch (_) {}
|
|
193
123
|
void waitForStats();
|
|
194
124
|
}
|
|
195
125
|
return;
|
|
196
126
|
}
|
|
197
127
|
if (consentGranted) {
|
|
198
|
-
|
|
128
|
+
continueAfterLocalHistoryConsent();
|
|
199
129
|
return;
|
|
200
130
|
}
|
|
201
|
-
|
|
131
|
+
renderLocalHistoryConsent();
|
|
202
132
|
}
|
|
203
133
|
__ECHO_SETUP_STARTUP__;
|
|
204
134
|
`;
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import { SETUP_PAGE_CLIENT_CORE } from "./client-core.js";
|
|
2
|
-
import { SETUP_PAGE_CLIENT_REPORT } from "./client-report.js";
|
|
3
2
|
import { SETUP_PAGE_CLIENT_EXTRACTION } from "./client-extraction.js";
|
|
4
3
|
import { SETUP_PAGE_CLIENT_LIFECYCLE } from "./client-lifecycle.js";
|
|
5
4
|
export function renderSetupPageClient(previewState, startupScript) {
|
|
6
5
|
const body = [
|
|
7
6
|
SETUP_PAGE_CLIENT_CORE,
|
|
8
|
-
SETUP_PAGE_CLIENT_REPORT,
|
|
9
7
|
SETUP_PAGE_CLIENT_EXTRACTION,
|
|
10
8
|
SETUP_PAGE_CLIENT_LIFECYCLE,
|
|
11
9
|
].join("\n")
|
|
@@ -598,13 +598,8 @@ export const SETUP_PAGE_STYLES_EXTRACTION = String.raw ` /* ---- dashboard (p
|
|
|
598
598
|
.consentActions .primary { width: 100%; }
|
|
599
599
|
.localAuthActions .primary,
|
|
600
600
|
.localAuthForm > .primary { width: 100%; }
|
|
601
|
-
.cityHero { min-height: auto; }
|
|
602
|
-
.cityShell {
|
|
603
|
-
width: 100vw;
|
|
604
|
-
height: 100vh;
|
|
605
|
-
}
|
|
606
601
|
}
|
|
607
|
-
/* extraction dashboard
|
|
602
|
+
/* extraction dashboard */
|
|
608
603
|
.exHero { display: grid; grid-template-columns: minmax(0, 1fr) minmax(300px, 0.95fr); gap: clamp(20px, 3vw, 40px); align-items: center; margin-top: 8px; }
|
|
609
604
|
/* Centered composition: mascot on top, then everything stacked and centered on one axis. */
|
|
610
605
|
.readyDecision {
|
|
@@ -674,7 +669,6 @@ export const SETUP_PAGE_STYLES_EXTRACTION = String.raw ` /* ---- dashboard (p
|
|
|
674
669
|
justify-items: stretch;
|
|
675
670
|
margin: 0 auto;
|
|
676
671
|
}
|
|
677
|
-
.exHeroResult .exPlate { display: none; }
|
|
678
672
|
.exHeroResult .exCopy { width: 100%; margin: 0 auto; }
|
|
679
673
|
.exCopy h2 { max-width: 620px; margin: 8px 0 0; font-size: clamp(34px, 4.8vw, 58px); line-height: 1.01; }
|
|
680
674
|
.exCopy .exSub { margin: 14px 0 0; max-width: 590px; font-size: clamp(15px, 1.3vw, 17px); line-height: 1.6; color: var(--echo-ink-mute); }
|
|
@@ -896,19 +890,6 @@ export const SETUP_PAGE_STYLES_EXTRACTION = String.raw ` /* ---- dashboard (p
|
|
|
896
890
|
font-size: 12px;
|
|
897
891
|
line-height: 1.35;
|
|
898
892
|
}
|
|
899
|
-
.exPlate { width: 100%; }
|
|
900
|
-
/* WebGL clay city (echo-extraction-plate.html) — the only plate; no SVG layer */
|
|
901
|
-
.plateStack { position: relative; width: 100%; aspect-ratio: 640 / 470; }
|
|
902
|
-
.plateStack .plateGl { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; background: transparent; }
|
|
903
|
-
.exHeroActive {
|
|
904
|
-
grid-template-columns: minmax(0, 640px);
|
|
905
|
-
justify-content: center;
|
|
906
|
-
justify-items: stretch;
|
|
907
|
-
width: 100%;
|
|
908
|
-
max-width: min(920px, calc(100vw - clamp(36px, 8vw, 120px)));
|
|
909
|
-
margin: 0 auto;
|
|
910
|
-
}
|
|
911
|
-
.exHeroActive .exPlate { display: none; }
|
|
912
893
|
.exHeroActive .exCopy { width: 100%; }
|
|
913
894
|
.workbench { display: grid; gap: 14px; max-width: 640px; }
|
|
914
895
|
.workbench h2 {
|
|
@@ -2538,15 +2519,4 @@ export const SETUP_PAGE_STYLES_EXTRACTION = String.raw ` /* ---- dashboard (p
|
|
|
2538
2519
|
.sessionPickerFoot button { width: 100%; }
|
|
2539
2520
|
}
|
|
2540
2521
|
|
|
2541
|
-
/* ===== workspace readout dashboard (Part 2 — replaces explainPage) ===== */
|
|
2542
|
-
@keyframes rdoRise { from { opacity: 0; translate: 0 14px; } to { opacity: 1; translate: 0 0; } }
|
|
2543
|
-
@keyframes rdoGrow { from { transform: scaleY(0); } to { transform: scaleY(1); } }
|
|
2544
|
-
@keyframes rdoDraw { from { transform: scaleX(0); } to { transform: scaleX(1); } }
|
|
2545
|
-
@keyframes rdoWipe { from { clip-path: inset(0 100% 0 0); } to { clip-path: inset(0 0 0 0); } }
|
|
2546
|
-
/* Pull the report up toward the city figure — the canvas bottom fades out now,
|
|
2547
|
-
so the document can tuck into that empty band without touching the board. */
|
|
2548
|
-
body.cityMode .detailsWrap { background: var(--echo-paper-canvas); margin-top: clamp(-120px, -7vw, -56px); }
|
|
2549
|
-
|
|
2550
|
-
/* Context audit — a printed report sheet on the desk: serif document type, tight rhythm. */
|
|
2551
|
-
.context-audit {
|
|
2552
2522
|
`;
|