@oharness/server 0.9.3 → 0.10.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 (59) hide show
  1. package/dist/i18n.js +354 -62
  2. package/dist/i18n.js.map +1 -1
  3. package/dist/main.js +4 -0
  4. package/dist/main.js.map +1 -1
  5. package/dist/page/client/account.js +430 -23
  6. package/dist/page/client/account.js.map +1 -1
  7. package/dist/page/client/base.js +20 -1
  8. package/dist/page/client/base.js.map +1 -1
  9. package/dist/page/client/composer.js +693 -54
  10. package/dist/page/client/composer.js.map +1 -1
  11. package/dist/page/client/computer.d.ts +27 -0
  12. package/dist/page/client/computer.js +358 -0
  13. package/dist/page/client/computer.js.map +1 -0
  14. package/dist/page/client/dialogs.js +93 -6
  15. package/dist/page/client/dialogs.js.map +1 -1
  16. package/dist/page/client/events.js +94 -7
  17. package/dist/page/client/events.js.map +1 -1
  18. package/dist/page/client/files.js +86 -1
  19. package/dist/page/client/files.js.map +1 -1
  20. package/dist/page/client/index.js +4 -0
  21. package/dist/page/client/index.js.map +1 -1
  22. package/dist/page/client/panels.js +10 -6
  23. package/dist/page/client/panels.js.map +1 -1
  24. package/dist/page/client/routines.js +296 -41
  25. package/dist/page/client/routines.js.map +1 -1
  26. package/dist/page/client/shell.js +187 -51
  27. package/dist/page/client/shell.js.map +1 -1
  28. package/dist/page/client/state.js +273 -19
  29. package/dist/page/client/state.js.map +1 -1
  30. package/dist/page/client/tasks.d.ts +8 -0
  31. package/dist/page/client/tasks.js +293 -0
  32. package/dist/page/client/tasks.js.map +1 -0
  33. package/dist/page/client/transcript.js +137 -5
  34. package/dist/page/client/transcript.js.map +1 -1
  35. package/dist/page/client/usage.js +1 -11
  36. package/dist/page/client/usage.js.map +1 -1
  37. package/dist/page/style.js +568 -60
  38. package/dist/page/style.js.map +1 -1
  39. package/dist/page.js +90 -11
  40. package/dist/page.js.map +1 -1
  41. package/dist/prefs.d.ts +14 -0
  42. package/dist/prefs.js +8 -1
  43. package/dist/prefs.js.map +1 -1
  44. package/dist/remote-crypto.d.ts +78 -0
  45. package/dist/remote-crypto.js +216 -0
  46. package/dist/remote-crypto.js.map +1 -0
  47. package/dist/remote.d.ts +103 -0
  48. package/dist/remote.js +498 -0
  49. package/dist/remote.js.map +1 -0
  50. package/dist/server.d.ts +70 -0
  51. package/dist/server.js +1291 -49
  52. package/dist/server.js.map +1 -1
  53. package/dist/starters.d.ts +92 -0
  54. package/dist/starters.js +213 -0
  55. package/dist/starters.js.map +1 -0
  56. package/dist/turn-shots.d.ts +27 -0
  57. package/dist/turn-shots.js +71 -0
  58. package/dist/turn-shots.js.map +1 -0
  59. package/package.json +2 -2
@@ -7,6 +7,51 @@
7
7
  export const EVENTS = String.raw `
8
8
  // --- events ---------------------------------------------------------------
9
9
 
10
+ /**
11
+ * Streamed text, revealed at a steady pace rather than as it arrives.
12
+ *
13
+ * Models send text in pieces of very different sizes: measured through the
14
+ * official plan, GPT-5.6 Sol came as ~5 characters a piece, many to the same
15
+ * millisecond, and Claude Opus 5.5 as four pieces of ~260 characters some
16
+ * 280ms apart. Drawn as received, a reply lurched a sentence at a time. Each
17
+ * frame now shows a share of what is still waiting — a tenth of it, at least
18
+ * two characters — so a large piece unrolls over a few hundred milliseconds
19
+ * and a trickle is shown as it comes. Nothing is held back for long: the
20
+ * backlog is never more than a fraction of a second behind.
21
+ *
22
+ * A stream the pane has finished with (P.streaming cleared) still runs to the
23
+ * end of its text here; it only ever shows what it was sent.
24
+ */
25
+ const streamsRevealing = new Set();
26
+ let streamFrame = 0;
27
+
28
+ function revealStream(stream) {
29
+ streamsRevealing.add(stream);
30
+ if (!streamFrame) streamFrame = requestAnimationFrame(revealStreams);
31
+ }
32
+
33
+ function revealStreams() {
34
+ streamFrame = 0;
35
+ for (const stream of streamsRevealing) {
36
+ const waiting = stream.text.length - stream.shown;
37
+ if (waiting <= 0) {
38
+ streamsRevealing.delete(stream);
39
+ continue;
40
+ }
41
+ let next = Math.min(stream.text.length, stream.shown + Math.max(2, Math.ceil(waiting / 10)));
42
+ // Never between the halves of a surrogate pair: an emoji cut in two draws
43
+ // as a box for a frame.
44
+ const code = stream.text.charCodeAt(next - 1);
45
+ if (code >= 0xd800 && code <= 0xdbff && next < stream.text.length) next++;
46
+ stream.shown = next;
47
+ // The whole block again: markdown is not incremental, and a half fence
48
+ // rendered as literal text then rewritten looks worse than this.
49
+ stream.node.replaceChildren(renderMarkdown(stream.text.slice(0, next)));
50
+ }
51
+ follow();
52
+ if (streamsRevealing.size) streamFrame = requestAnimationFrame(revealStreams);
53
+ }
54
+
10
55
  const events = new EventSource(q("/events"));
11
56
  events.onerror = () => { ctxText.textContent = "offline"; };
12
57
  events.onmessage = (message) => {
@@ -44,15 +89,27 @@ events.onmessage = (message) => {
44
89
  break;
45
90
  }
46
91
 
92
+ // Background tasks ended and their results were just read in. When that
93
+ // is what started the turn — nobody typed anything — this is also the
94
+ // only sign the conversation is working again.
95
+ // A background task started, moved on or ended.
96
+ case "tasks": tasksChanged(event.session, event.tasks); break;
97
+ case "task_notification":
98
+ P.turn = null;
99
+ taskCard(event.tasks || []);
100
+ if (!P.busy) {
101
+ startWorking(t("work.thinking"));
102
+ setBusy(true);
103
+ }
104
+ break;
105
+
47
106
  case "text_delta":
48
107
  sealThinking();
49
108
  stopWorking();
50
- if (!P.streaming) { P.streaming = { text: "", node: el("div") }; assistantTurn().appendChild(P.streaming.node); }
109
+ if (!P.streaming) { P.streaming = { text: "", shown: 0, node: el("div") }; assistantTurn().appendChild(P.streaming.node); }
51
110
  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();
111
+ // Not drawn here: revealed at a steady pace by revealStreams.
112
+ revealStream(P.streaming);
56
113
  break;
57
114
 
58
115
  case "thinking_delta": {
@@ -88,6 +145,12 @@ events.onmessage = (message) => {
88
145
  break;
89
146
 
90
147
  case "assistant_message": P.streaming = null; break;
148
+ // After the turn ended: under its last reply, not a new turn of its own.
149
+ case "turn_shots": {
150
+ const replies = P.node.querySelectorAll(":scope > .turn.assistant");
151
+ turnShots(replies[replies.length - 1], event.shots);
152
+ break;
153
+ }
91
154
  case "turn_start": dropPendingTools(); break;
92
155
  // A session named from its first prompt, or renamed: the tab and the
93
156
  // sidebar row both carry the name.
@@ -162,6 +225,8 @@ events.onmessage = (message) => {
162
225
  }
163
226
 
164
227
  case "permission_request": showAsk(event); break;
228
+ case "remote_pair_request": showPairing(event); break;
229
+ case "remote_pair_closed": closePairing(event.id); break;
165
230
  case "question_request": questionCard(event); break;
166
231
  case "question_result": settleQuestion(event); break;
167
232
  // Sent once per (re)connect, after the replay: a card this page still
@@ -177,7 +242,10 @@ events.onmessage = (message) => {
177
242
  closeQuestion(event.id);
178
243
  break;
179
244
  case "permission_result":
180
- if (!event.allowed) addNotice("denied: " + (event.reason || ""), "err");
245
+ // Plan mode holding a step back is the mode working, not something
246
+ // going wrong: said plainly, and not in the colour of an error.
247
+ if (!event.allowed && event.plan) addNotice(t("plan.held"), "notice");
248
+ else if (!event.allowed) addNotice("denied: " + (event.reason || ""), "err");
181
249
  else if (event.remembered) addNotice("remembered: " + event.remembered);
182
250
  break;
183
251
 
@@ -200,6 +268,18 @@ events.onmessage = (message) => {
200
268
  void loadSessions();
201
269
  if (shownPanel === "nav-routines") void showRoutines();
202
270
  break;
271
+ // A worktree came or went: which session directories nest under which
272
+ // project is part of the project list.
273
+ case "projects":
274
+ void loadProjects();
275
+ break;
276
+ // A routine run started or ended, here or from the clock.
277
+ case "routines":
278
+ if (shownPanel === "nav-routines") void showRoutines();
279
+ break;
280
+ case "routine_activity":
281
+ if (shownPanel === "nav-routines") paintRoutineActivity(event);
282
+ break;
203
283
  case "error":
204
284
  P.streaming = null;
205
285
  // A 402 is not an error to print, it is a screen to open. What the
@@ -242,10 +322,17 @@ events.onmessage = (message) => {
242
322
  }
243
323
  case "idle":
244
324
  applyState(event.state);
245
- renderChanges(event.changes);
325
+ // The bar above the transcript is the tab on screen's; a turn ending in
326
+ // a background tab (a worktree session, a second conversation) must not
327
+ // put its files there.
328
+ if (!event.session || event.session === activeId) renderChanges(event.changes);
246
329
  void loadSessions();
247
330
  break;
248
331
  case "state": applyState(event.state); break;
332
+ // What a conversation is doing on the computer, for the live view.
333
+ case "computer": computerEvent(event.session, event.event); break;
334
+ // The project's starters, written in the background after the fixed three.
335
+ case "starters": startersArrived(event); break;
249
336
  case "connectors_changed":
250
337
  if (authorizing === event.connector) authorizing = null;
251
338
  offer = { connectors: event.connectors, plugins: event.plugins || [], skills: event.skills || [] };
@@ -1 +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"}
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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+W/B,CAAC"}
@@ -15,6 +15,7 @@ const explorer = document.getElementById("explorer");
15
15
  const explorerTitle = document.getElementById("explorer-title");
16
16
  const explorerTree = document.getElementById("explorer-tree");
17
17
  const explorerView = document.getElementById("explorer-view");
18
+ const explorerQuery = document.getElementById("explorer-q");
18
19
 
19
20
  /** The project folder on show, absolute; null while the pane has never opened. */
20
21
  let explorerCwd = null;
@@ -65,6 +66,8 @@ async function openExplorer(cwd, force) {
65
66
  explorerChildren = new Map();
66
67
  explorerSelected = null;
67
68
  explorerSource = false;
69
+ explorerQuery.value = "";
70
+ explorerHits = null;
68
71
  }
69
72
  explorerTitle.textContent = projectName(cwd);
70
73
  explorerTitle.title = cwd;
@@ -96,6 +99,7 @@ async function explorerLoad() {
96
99
  else explorerChildren.set(dir, body.entries || []);
97
100
  });
98
101
  paintExplorerTree();
102
+ if (explorerQuery.value.trim()) void explorerSearch();
99
103
  if (explorerSelected) await explorerShow(explorerSelected);
100
104
  else explorerView.replaceChildren(el("p", "muted explorer-hint", t("files.pick")));
101
105
  }
@@ -125,6 +129,7 @@ async function explorerToggleDir(dir) {
125
129
  }
126
130
 
127
131
  function paintExplorerTree() {
132
+ if (explorerHits) return paintExplorerHits();
128
133
  const scroll = explorerTree.scrollTop;
129
134
  explorerTree.replaceChildren();
130
135
  const root = explorerChildren.get("") || [];
@@ -285,6 +290,86 @@ function tabProjectButtons(id, cwd) {
285
290
  return [files, term];
286
291
  }
287
292
 
293
+ // --- searching the tree ------------------------------------------------------
294
+ // A name typed here is looked for through the whole project, not only the
295
+ // folders unfolded; while there is one, the matches stand in for the tree and
296
+ // clearing it brings the tree back exactly as it was.
297
+
298
+ /** The matches on show, or null while the tree is. */
299
+ let explorerHits = null;
300
+ let explorerSearchTimer = 0;
301
+ let explorerSearchSeq = 0;
302
+
303
+ async function explorerSearch() {
304
+ const query = explorerQuery.value.trim();
305
+ const seq = ++explorerSearchSeq;
306
+ if (!query) {
307
+ explorerHits = null;
308
+ return paintExplorerTree();
309
+ }
310
+ const cwd = explorerCwd;
311
+ const body = await getJson("/files/search?cwd=" + encodeURIComponent(cwd) + "&q=" + encodeURIComponent(query));
312
+ if (seq !== explorerSearchSeq || cwd !== explorerCwd) return;
313
+ explorerHits = body.error ? [] : (body.entries || []);
314
+ explorerTree.scrollTop = 0;
315
+ paintExplorerTree();
316
+ }
317
+
318
+ function paintExplorerHits() {
319
+ explorerTree.replaceChildren();
320
+ if (!explorerHits.length) return void explorerTree.appendChild(el("p", "muted explorer-hint", t("files.noMatch")));
321
+ for (const entry of explorerHits) {
322
+ const row = el("button", "explorer-row" + (entry.path === explorerSelected ? " on" : ""));
323
+ row.type = "button";
324
+ row.setAttribute("role", "treeitem");
325
+ row.title = entry.path;
326
+ row.appendChild(svgIcon(entry.dir ? "folder" : "file"));
327
+ row.appendChild(el("span", "explorer-name", entry.name));
328
+ const parent = entry.path.slice(0, Math.max(0, entry.path.length - entry.name.length - 1));
329
+ if (parent) row.appendChild(el("span", "explorer-dir", parent));
330
+ row.addEventListener("click", () => void (entry.dir ? explorerReveal(entry.path) : explorerShow(entry.path)));
331
+ explorerTree.appendChild(row);
332
+ }
333
+ }
334
+
335
+ /** A folder picked from the matches: back to the tree, unfolded down to it. */
336
+ async function explorerReveal(dir) {
337
+ const parts = dir.split("/");
338
+ const dirs = explorerDirs();
339
+ for (let i = 1; i <= parts.length; i++) {
340
+ const at = parts.slice(0, i).join("/");
341
+ dirs.add(at);
342
+ if (!explorerChildren.has(at)) {
343
+ const body = await explorerFetch(at);
344
+ if (body.error) { dirs.delete(at); break; }
345
+ explorerChildren.set(at, body.entries || []);
346
+ }
347
+ }
348
+ explorerQuery.value = "";
349
+ explorerHits = null;
350
+ explorerSearchSeq++;
351
+ paintExplorerTree();
352
+ const row = [...explorerTree.children].find((node) => node.title === dir);
353
+ if (row) row.scrollIntoView({ block: "nearest" });
354
+ }
355
+
356
+ explorerQuery.addEventListener("input", () => {
357
+ clearTimeout(explorerSearchTimer);
358
+ explorerSearchTimer = setTimeout(() => void explorerSearch(), 120);
359
+ });
360
+ explorerQuery.addEventListener("keydown", (event) => {
361
+ if (event.key === "Escape" && explorerQuery.value) {
362
+ event.preventDefault();
363
+ event.stopPropagation();
364
+ explorerQuery.value = "";
365
+ void explorerSearch();
366
+ } else if (event.key === "Enter" && explorerHits && explorerHits.length) {
367
+ event.preventDefault();
368
+ const first = explorerHits[0];
369
+ void (first.dir ? explorerReveal(first.path) : explorerShow(first.path));
370
+ }
371
+ });
372
+
288
373
  document.getElementById("explorer-close").addEventListener("click", closeExplorer);
289
374
  document.getElementById("explorer-refresh").addEventListener("click", () => void explorerLoad());
290
375
  document.getElementById("explorer-terminal").addEventListener("click", () => void openTerminalIn(explorerCwd, true));
@@ -398,7 +483,7 @@ let explorerTreeDragged;
398
483
  dragHandle(explorerSplit, {
399
484
  get axis() { return isPhone() ? "y" : "x"; },
400
485
  start: () => {
401
- const rect = explorerTree.getBoundingClientRect();
486
+ const rect = document.getElementById("explorer-side").getBoundingClientRect();
402
487
  return isPhone() ? rect.height : rect.width;
403
488
  },
404
489
  move: (dx, dy, start) => { explorerTreeDragged = explorerSetTree(start + (isPhone() ? dy : dx)); },
@@ -1 +1 @@
1
- {"version":3,"file":"files.js","sourceRoot":"","sources":["../../../src/page/client/files.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,CAAC,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAoZ9B,CAAC"}
1
+ {"version":3,"file":"files.js","sourceRoot":"","sources":["../../../src/page/client/files.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,CAAC,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAye9B,CAAC"}
@@ -5,12 +5,14 @@ import { STATE } from "./state.js";
5
5
  import { EVENTS } from "./events.js";
6
6
  import { DIALOGS } from "./dialogs.js";
7
7
  import { COMPOSER } from "./composer.js";
8
+ import { TASKS } from "./tasks.js";
8
9
  import { ARTIFACTS } from "./artifacts.js";
9
10
  import { ACCOUNT } from "./account.js";
10
11
  import { ROUTINES } from "./routines.js";
11
12
  import { USAGE } from "./usage.js";
12
13
  import { PANELS } from "./panels.js";
13
14
  import { BROWSER } from "./browser.js";
15
+ import { COMPUTER } from "./computer.js";
14
16
  import { FILES } from "./files.js";
15
17
  import { TERMINAL } from "./terminal.js";
16
18
  import { SHELL } from "./shell.js";
@@ -37,6 +39,7 @@ export const APP_SCRIPT = [
37
39
  EVENTS,
38
40
  DIALOGS,
39
41
  COMPOSER,
42
+ TASKS,
40
43
  ARTIFACTS,
41
44
  ACCOUNT,
42
45
  ROUTINES,
@@ -45,6 +48,7 @@ export const APP_SCRIPT = [
45
48
  FILES,
46
49
  TERMINAL,
47
50
  BROWSER,
51
+ COMPUTER,
48
52
  SHELL,
49
53
  ].join("") + "\n";
50
54
  //# sourceMappingURL=index.js.map
@@ -1 +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;AACnC,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,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,KAAK;IACL,QAAQ;IACR,OAAO;IACP,KAAK;CACJ,CAAC,IAAI,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC"}
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,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,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,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,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,KAAK;IACL,SAAS;IACT,OAAO;IACP,QAAQ;IACR,KAAK;IACL,MAAM;IACN,KAAK;IACL,QAAQ;IACR,OAAO;IACP,QAAQ;IACR,KAAK;CACJ,CAAC,IAAI,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC"}
@@ -168,7 +168,7 @@ async function refreshRoutineCount() {
168
168
  }
169
169
 
170
170
  /** Sidebar entries that open a view of their own rather than a conversation. */
171
- const PANEL_NAV = new Set(["nav-artifacts", "nav-sessions", "nav-models", "nav-routines", "nav-settings"]);
171
+ const PANEL_NAV = new Set(["nav-artifacts", "nav-sessions", "nav-usage", "nav-models", "nav-routines", "nav-settings"]);
172
172
  const pluginPages = new Map();
173
173
  async function refreshPluginPages() {
174
174
  const session = activeId;
@@ -599,12 +599,12 @@ async function showMemoryPage(id) {
599
599
  * Panels reached from a row inside another panel rather than from the sidebar.
600
600
  *
601
601
  * A full page, drawn where Models and Routines are drawn, but without a
602
- * permanent destination in the sidebar: usage is looked at now and then, and a
603
- * row in the sidebar for it would be a standing invitation to a page most
604
- * sessions have no reason to open. It keeps the row it came from lit, so the
605
- * sidebar still says where in the app this is.
602
+ * permanent destination in the sidebar. It keeps the row it came from lit, so
603
+ * the sidebar still says where in the app this is. Usage is the exception that
604
+ * has a row of its own on the rail as well as Settings', and lights that one
605
+ * whichever way it was reached.
606
606
  */
607
- const SUB_PANELS = { usage: "nav-settings", audit: "nav-settings", permissions: "nav-settings" };
607
+ const SUB_PANELS = { usage: "nav-usage", audit: "nav-settings", permissions: "nav-settings" };
608
608
 
609
609
  /** Which panel the panel view is currently showing, sub-panels included. */
610
610
  let shownPanel = null;
@@ -652,6 +652,10 @@ document.getElementById("nav-sessions").addEventListener("click", () => {
652
652
  setNav("nav-sessions");
653
653
  void showUsageSessions();
654
654
  });
655
+ document.getElementById("nav-usage").addEventListener("click", () => {
656
+ setNav("usage");
657
+ void showUsage();
658
+ });
655
659
  document.getElementById("nav-routines").addEventListener("click", () => {
656
660
  setNav("nav-routines");
657
661
  void showRoutines();
@@ -1 +1 @@
1
- {"version":3,"file":"panels.js","sourceRoot":"","sources":["../../../src/page/client/panels.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,CAAC,MAAM,MAAM,GAAG,MAAM,CAAC,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAipB/B,CAAC"}
1
+ {"version":3,"file":"panels.js","sourceRoot":"","sources":["../../../src/page/client/panels.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,CAAC,MAAM,MAAM,GAAG,MAAM,CAAC,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAqpB/B,CAAC"}