@oharness/server 0.7.1 → 0.8.0

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.
Files changed (61) hide show
  1. package/dist/browser-pane.d.ts +48 -0
  2. package/dist/browser-pane.js +185 -0
  3. package/dist/browser-pane.js.map +1 -0
  4. package/dist/i18n.js +132 -0
  5. package/dist/i18n.js.map +1 -1
  6. package/dist/main.js +20 -0
  7. package/dist/main.js.map +1 -1
  8. package/dist/page/client/account.d.ts +8 -0
  9. package/dist/page/client/account.js +1895 -0
  10. package/dist/page/client/account.js.map +1 -0
  11. package/dist/page/client/artifacts.d.ts +7 -0
  12. package/dist/page/client/artifacts.js +767 -0
  13. package/dist/page/client/artifacts.js.map +1 -0
  14. package/dist/page/client/base.d.ts +8 -0
  15. package/dist/page/client/base.js +400 -0
  16. package/dist/page/client/base.js.map +1 -0
  17. package/dist/page/client/browser.d.ts +21 -0
  18. package/dist/page/client/browser.js +904 -0
  19. package/dist/page/client/browser.js.map +1 -0
  20. package/dist/page/client/composer.d.ts +8 -0
  21. package/dist/page/client/composer.js +1694 -0
  22. package/dist/page/client/composer.js.map +1 -0
  23. package/dist/page/client/dialogs.d.ts +7 -0
  24. package/dist/page/client/dialogs.js +724 -0
  25. package/dist/page/client/dialogs.js.map +1 -0
  26. package/dist/page/client/events.d.ts +7 -0
  27. package/dist/page/client/events.js +288 -0
  28. package/dist/page/client/events.js.map +1 -0
  29. package/dist/page/client/index.d.ts +16 -0
  30. package/dist/page/client/index.js +46 -0
  31. package/dist/page/client/index.js.map +1 -0
  32. package/dist/page/client/panels.d.ts +7 -0
  33. package/dist/page/client/panels.js +659 -0
  34. package/dist/page/client/panels.js.map +1 -0
  35. package/dist/page/client/pickers.d.ts +7 -0
  36. package/dist/page/client/pickers.js +624 -0
  37. package/dist/page/client/pickers.js.map +1 -0
  38. package/dist/page/client/routines.d.ts +6 -0
  39. package/dist/page/client/routines.js +910 -0
  40. package/dist/page/client/routines.js.map +1 -0
  41. package/dist/page/client/shell.d.ts +6 -0
  42. package/dist/page/client/shell.js +690 -0
  43. package/dist/page/client/shell.js.map +1 -0
  44. package/dist/page/client/state.d.ts +7 -0
  45. package/dist/page/client/state.js +1275 -0
  46. package/dist/page/client/state.js.map +1 -0
  47. package/dist/page/client/transcript.d.ts +8 -0
  48. package/dist/page/client/transcript.js +699 -0
  49. package/dist/page/client/transcript.js.map +1 -0
  50. package/dist/page/client/usage.d.ts +7 -0
  51. package/dist/page/client/usage.js +954 -0
  52. package/dist/page/client/usage.js.map +1 -0
  53. package/dist/page/style.d.ts +1 -0
  54. package/dist/page/style.js +2793 -0
  55. package/dist/page/style.js.map +1 -0
  56. package/dist/page.js +62 -13871
  57. package/dist/page.js.map +1 -1
  58. package/dist/server.d.ts +24 -0
  59. package/dist/server.js +349 -22
  60. package/dist/server.js.map +1 -1
  61. package/package.json +2 -2
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dialogs.js","sourceRoot":"","sources":["../../../src/page/client/dialogs.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,CAAC,MAAM,OAAO,GAAG,MAAM,CAAC,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4sBhC,CAAC"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * The event stream. Everything the server says happened arrives here and is
3
+ * turned into a change on screen.
4
+ *
5
+ * One part of the client script; see ./index.ts for how the parts join.
6
+ */
7
+ export declare const EVENTS: string;
@@ -0,0 +1,288 @@
1
+ /**
2
+ * The event stream. Everything the server says happened arrives here and is
3
+ * turned into a change on screen.
4
+ *
5
+ * One part of the client script; see ./index.ts for how the parts join.
6
+ */
7
+ export const EVENTS = String.raw `
8
+ // --- events ---------------------------------------------------------------
9
+
10
+ const events = new EventSource(q("/events"));
11
+ events.onerror = () => { ctxText.textContent = "offline"; };
12
+ events.onmessage = (message) => {
13
+ const event = JSON.parse(message.data);
14
+
15
+ // Session-scoped events render into their own pane; the rest (model lists,
16
+ // notices about discovery) apply to the page.
17
+ if (event.session) {
18
+ P = pane(event.session);
19
+ if (!panes.has(activeId)) activeId = event.session;
20
+ }
21
+
22
+ switch (event.type) {
23
+ case "connection_card":
24
+ connectionCard(event.card);
25
+ break;
26
+ case "prompt": {
27
+ // The previews this tab made a moment ago, so a message just sent looks
28
+ // like the same message will after a reload — the transcript has not
29
+ // been written yet, so there is nothing at /attachment to ask for.
30
+ const sent = sentPreviews.get(event.session) || [];
31
+ sentPreviews.delete(event.session);
32
+ addUser(event.text || "", sent);
33
+ startWorking(t("work.thinking"));
34
+ const unshown = (event.attachments || []).filter(
35
+ (name) => !sent.some((image) => image.name === name),
36
+ );
37
+ if (unshown.length) addNotice("attached: " + unshown.join(", "));
38
+ // Without this a turn that reads three files looks like it went and
39
+ // found them by itself, rather than being pointed at them.
40
+ if (event.references && event.references.length) {
41
+ addNotice("referenced: " + event.references.map((ref) => ref.value).join(", "));
42
+ }
43
+ setBusy(true);
44
+ break;
45
+ }
46
+
47
+ case "text_delta":
48
+ sealThinking();
49
+ stopWorking();
50
+ if (!P.streaming) { P.streaming = { text: "", node: el("div") }; assistantTurn().appendChild(P.streaming.node); }
51
+ P.streaming.text += event.text;
52
+ // Re-render the whole block: markdown is not incremental, and a half
53
+ // fence rendered as literal text then rewritten looks worse than this.
54
+ P.streaming.node.replaceChildren(renderMarkdown(P.streaming.text));
55
+ follow();
56
+ break;
57
+
58
+ case "thinking_delta": {
59
+ // Tracked on the turn rather than found with :last-child, which stopped
60
+ // matching the moment anything else was appended after it.
61
+ let block = P.turn && P.turn.__thinking;
62
+ if (!block) {
63
+ // The reasoning block carries its own spinner and streams visible text,
64
+ // so the waiting row above it would be a second "Thinking" saying less.
65
+ stopWorking();
66
+ block = el("details", "thinking");
67
+ block.open = true;
68
+ const summary = el("summary");
69
+ summary.appendChild(el("span", "spinner"));
70
+ summary.appendChild(el("span", "t-label", t("work.thinking")));
71
+ block.appendChild(summary);
72
+ block.appendChild(el("div", "thinking-body"));
73
+ assistantTurn().appendChild(block);
74
+ P.turn.__thinking = block;
75
+ P.turn.__thinkingStart = Date.now();
76
+ }
77
+ block.querySelector(".thinking-body").textContent += event.text;
78
+ follow();
79
+ break;
80
+ }
81
+
82
+ // One event carrying the whole list rather than add/remove pairs: the
83
+ // page redraws from it, so a dropped event cannot leave a chip behind for
84
+ // a message that was sent ten minutes ago.
85
+ case "queue":
86
+ P.queued = event.queued || [];
87
+ renderQueued();
88
+ break;
89
+
90
+ case "assistant_message": P.streaming = null; break;
91
+ case "turn_start": dropPendingTools(); break;
92
+ // A session named from its first prompt, or renamed: the tab and the
93
+ // sidebar row both carry the name.
94
+ case "title":
95
+ P.title = event.title;
96
+ paintTabs();
97
+ void loadSessions();
98
+ break;
99
+ case "tool_pending":
100
+ sealThinking();
101
+ pendingToolCard(event.toolCallId, event.toolName);
102
+ startWorking(toolTitle(event.toolName));
103
+ break;
104
+ case "tool_start":
105
+ sealThinking();
106
+ browserToolStarted(event.session, event.toolName);
107
+ toolCard(event.toolCallId, event.title, event.toolName, event.input);
108
+ startWorking(event.toolName);
109
+ break;
110
+ case "tool_end":
111
+ finishTool(event.toolCallId, event.result && event.result.content, event.durationMs,
112
+ event.result && event.result.isError);
113
+ // Back to waiting on the model until the next tool or the next token.
114
+ startWorking(t("work.thinking"));
115
+ break;
116
+
117
+ case "artifact": {
118
+ // A revision the user typed, in a canvas they are looking at right now.
119
+ // It gets neither a card nor a redraw: the transcript records what
120
+ // happened in the conversation and this did not happen there, and
121
+ // rebuilding the frame would reload the editor under the hands of the
122
+ // person using it — losing their viewport, their selection and their
123
+ // undo history at the exact moment they pressed Save.
124
+ void refreshArtifactCount();
125
+ if (event.artifact.author === "user") {
126
+ const mine = artifacts.findIndex((a) => a.id === event.artifact.id);
127
+ if (mine >= 0) artifacts[mine] = event.artifact;
128
+ artifactVersion.delete(event.artifact.id);
129
+ if (selectedArtifact === event.artifact.id) refreshArtifactBar(event.artifact);
130
+ break;
131
+ }
132
+
133
+ // A card in the conversation, not only a row in the drawer. The drawer
134
+ // is a place you have to know to look; the transcript is where the user
135
+ // already is, and an artifact is a thing that happened in it.
136
+ artifactCard(event.artifact);
137
+ // The descriptor carries no body, so the list is patched from it and only
138
+ // the visible artifact is refetched. A revision of something the user is
139
+ // not looking at costs nothing.
140
+ const at = artifacts.findIndex((a) => a.id === event.artifact.id);
141
+ const before = at >= 0 ? artifacts[at] : null;
142
+ if (at >= 0) artifacts[at] = event.artifact;
143
+ else artifacts = artifacts.concat([event.artifact]);
144
+ // A new revision moves the view back to the latest: the user is watching
145
+ // the model work, and pinning them to v3 while it writes v4 is wrong.
146
+ // Only a revision, though — this event also fires when an artifact is
147
+ // published or withdrawn, and that must not yank the reader off the
148
+ // version they deliberately went back to.
149
+ if (!before || before.version !== event.artifact.version) {
150
+ artifactVersion.delete(event.artifact.id);
151
+ }
152
+ const wasSelected = selectedArtifact === event.artifact.id;
153
+ renderArtifacts(artifacts);
154
+ repaintPanel();
155
+ if (wasSelected) void showArtifact(event.artifact.id);
156
+ // First artifact of the session opens the drawer, the way the first file
157
+ // change makes the review button appear. Later ones do not steal focus.
158
+ // Not on a phone, where the drawer is the whole screen: it would cover
159
+ // the reply that produced the artifact.
160
+ if (artifacts.length === 1 && !isPhone()) shelf.classList.add("open");
161
+ break;
162
+ }
163
+
164
+ case "permission_request": showAsk(event); break;
165
+ case "question_request": questionCard(event); break;
166
+ case "question_result": settleQuestion(event); break;
167
+ // Sent once per (re)connect, after the replay: a card this page still
168
+ // shows as open but the server no longer holds closed while we were away.
169
+ case "pending_interactions":
170
+ for (const entry of questionCards.values()) {
171
+ if ((entry.status === "open" || entry.status === "sending") && !event.ids.includes(entry.id)) closeQuestion(entry.id);
172
+ }
173
+ break;
174
+ // Answered in another window, or the turn waiting on it stopped.
175
+ case "interaction_closed":
176
+ withdrawAsk(event.id);
177
+ closeQuestion(event.id);
178
+ break;
179
+ case "permission_result":
180
+ if (!event.allowed) addNotice("denied: " + (event.reason || ""), "err");
181
+ else if (event.remembered) addNotice("remembered: " + event.remembered);
182
+ break;
183
+
184
+ case "compacted":
185
+ addNotice("compacted " + event.droppedMessages + " messages (" +
186
+ event.tokensBefore + " → " + event.tokensAfter + " tokens)");
187
+ break;
188
+
189
+ case "model_changed": addNotice(event.from + " → " + event.to); break;
190
+ case "memory_recall":
191
+ addNotice(t("memory.recalled", { count: event.notes.length, list: event.notes.map((note) => note.summary).join("; ") }));
192
+ break;
193
+ case "memory_extracted":
194
+ addNotice(t("memory.extracted", { count: event.notes.length, list: event.notes.map((note) => note.summary).join("; ") }));
195
+ break;
196
+ case "notice": addNotice(event.message); break;
197
+ // A routine ran: its session is new, and the Routines page, if open, has
198
+ // a result to show.
199
+ case "sessions":
200
+ void loadSessions();
201
+ if (shownPanel === "nav-routines") void showRoutines();
202
+ break;
203
+ case "error":
204
+ P.streaming = null;
205
+ // A 402 is not an error to print, it is a screen to open. What the
206
+ // gateway sends back is an HTTP body — a true sentence, in English,
207
+ // with nothing in it to click — and printing that was the whole of the
208
+ // old behaviour on the one failure the user can actually fix.
209
+ if (event.status === 402) { openPaywall(); break; }
210
+ // A 401 is the other status worth reading rather than printing. The
211
+ // credential the request went out with was refused, which means the
212
+ // panel's belief that this vendor is connected is now known to be
213
+ // wrong — and the vendor's own JSON, pasted into the transcript, tells
214
+ // the user neither that nor what to do about it.
215
+ if (event.status === 401 && event.provider) { openReconnect(event.provider); break; }
216
+ // Never signed in at all. It carries no status — nothing was sent — but
217
+ // it is the same screen as a refused key, and the engine's sentence ends
218
+ // at "or sign in" precisely because where that is done differs per
219
+ // frontend. Here it is a dialog, one click away.
220
+ if (event.code === "no_credentials" && event.provider) { openSignIn(event.provider); break; }
221
+ addNotice("✗ " + event.error, "err");
222
+ break;
223
+ case "done": {
224
+ sealThinking();
225
+ stopWorking();
226
+ dropPendingTools();
227
+ // The fork point comes from the server: one reply can span several
228
+ // stored messages, and a client-side count would drift the moment tool
229
+ // results land in a message of their own.
230
+ const finished = P.turn;
231
+ P.streaming = null;
232
+ P.turn = null;
233
+ if (finished) {
234
+ void getJson("/history")
235
+ .then((body) => {
236
+ const last = (body.entries || []).at(-1);
237
+ if (last) turnActions(finished, last.keepMessages);
238
+ })
239
+ .catch(() => {});
240
+ }
241
+ break;
242
+ }
243
+ case "idle":
244
+ applyState(event.state);
245
+ renderChanges(event.changes);
246
+ void loadSessions();
247
+ break;
248
+ case "state": applyState(event.state); break;
249
+ case "connectors_changed":
250
+ if (authorizing === event.connector) authorizing = null;
251
+ offer = { connectors: event.connectors, plugins: event.plugins || [], skills: event.skills || [] };
252
+ // The failure already said what went wrong; this only announces the
253
+ // half that is worth celebrating.
254
+ if (event.authorized) addNotice(t("market.authorized", { name: event.connector }));
255
+ paintMarket();
256
+ break;
257
+ case "models_changed":
258
+ void loadModels().then(() => {
259
+ addNotice("discovered " + event.discovered + " models from " + event.provider);
260
+ // A sign-in that finished in a browser tab lands here, not in the
261
+ // click that started it — so whatever was waiting on it is told now.
262
+ const waiting = signedInWaiters.get(event.provider);
263
+ if (waiting) {
264
+ signedInWaiters.delete(event.provider);
265
+ waiting();
266
+ return;
267
+ }
268
+ if (document.getElementById("nav-models").classList.contains("active")) void showModels();
269
+ });
270
+ break;
271
+ // Also fired when another window switches the project, which moves the
272
+ // working directory out from under whatever panel is open here.
273
+ case "session_changed":
274
+ applyState(event.state);
275
+ renderHistory(event.entries);
276
+ // Artifacts are per-session, so the shelf has to be rebuilt rather than
277
+ // left showing the previous session's work under the new transcript.
278
+ selectedArtifact = null;
279
+ artifactVersion.clear();
280
+ shelf.classList.remove("open", "full");
281
+ document.body.classList.remove("shelf-full");
282
+ void loadArtifacts();
283
+ void loadProjects().then(repaintPanel);
284
+ break;
285
+ }
286
+ };
287
+ `;
288
+ //# sourceMappingURL=events.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"events.js","sourceRoot":"","sources":["../../../src/page/client/events.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,CAAC,MAAM,MAAM,GAAG,MAAM,CAAC,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAwR/B,CAAC"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * The client script, assembled.
3
+ *
4
+ * It was one template literal of eleven thousand lines, and it is split here
5
+ * along the section dividers it already carried. The parts are plain text and
6
+ * nothing interpolates into them, so the only thing that matters is that they
7
+ * join back exactly: each opens with the newline after its backtick and closes
8
+ * flush against its last line, which leaves one newline between neighbours and
9
+ * none doubled. The newline the closing delimiter used to contribute is added
10
+ * back at the end.
11
+ *
12
+ * Order is the whole contract. The script is one scope — a helper declared in
13
+ * an earlier part is what a later one calls — so this list is the file, and
14
+ * rearranging it rearranges the program.
15
+ */
16
+ export declare const APP_SCRIPT: string;
@@ -0,0 +1,46 @@
1
+ import { BASE } from "./base.js";
2
+ import { PICKERS } from "./pickers.js";
3
+ import { TRANSCRIPT } from "./transcript.js";
4
+ import { STATE } from "./state.js";
5
+ import { EVENTS } from "./events.js";
6
+ import { DIALOGS } from "./dialogs.js";
7
+ import { COMPOSER } from "./composer.js";
8
+ import { ARTIFACTS } from "./artifacts.js";
9
+ import { ACCOUNT } from "./account.js";
10
+ import { ROUTINES } from "./routines.js";
11
+ import { USAGE } from "./usage.js";
12
+ import { PANELS } from "./panels.js";
13
+ import { BROWSER } from "./browser.js";
14
+ import { SHELL } from "./shell.js";
15
+ /**
16
+ * The client script, assembled.
17
+ *
18
+ * It was one template literal of eleven thousand lines, and it is split here
19
+ * along the section dividers it already carried. The parts are plain text and
20
+ * nothing interpolates into them, so the only thing that matters is that they
21
+ * join back exactly: each opens with the newline after its backtick and closes
22
+ * flush against its last line, which leaves one newline between neighbours and
23
+ * none doubled. The newline the closing delimiter used to contribute is added
24
+ * back at the end.
25
+ *
26
+ * Order is the whole contract. The script is one scope — a helper declared in
27
+ * an earlier part is what a later one calls — so this list is the file, and
28
+ * rearranging it rearranges the program.
29
+ */
30
+ export const APP_SCRIPT = [
31
+ BASE,
32
+ PICKERS,
33
+ TRANSCRIPT,
34
+ STATE,
35
+ EVENTS,
36
+ DIALOGS,
37
+ COMPOSER,
38
+ ARTIFACTS,
39
+ ACCOUNT,
40
+ ROUTINES,
41
+ USAGE,
42
+ PANELS,
43
+ BROWSER,
44
+ SHELL,
45
+ ].join("") + "\n";
46
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/page/client/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAEnC;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,MAAM,UAAU,GACrB;IACA,IAAI;IACJ,OAAO;IACP,UAAU;IACV,KAAK;IACL,MAAM;IACN,OAAO;IACP,QAAQ;IACR,SAAS;IACT,OAAO;IACP,QAAQ;IACR,KAAK;IACL,MAAM;IACN,OAAO;IACP,KAAK;CACJ,CAAC,IAAI,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * The remaining side panels — every artifact, the counts in the sidebar,
3
+ * plugin pages and the memory page — and the navigation between them.
4
+ *
5
+ * One part of the client script; see ./index.ts for how the parts join.
6
+ */
7
+ export declare const PANELS: string;