@oharness/server 0.7.0 → 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 +144 -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 +70 -13583
  57. package/dist/page.js.map +1 -1
  58. package/dist/server.d.ts +24 -0
  59. package/dist/server.js +511 -25
  60. package/dist/server.js.map +1 -1
  61. package/package.json +2 -2
@@ -0,0 +1 @@
1
+ {"version":3,"file":"artifacts.js","sourceRoot":"","sources":["../../../src/page/client/artifacts.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG,MAAM,CAAC,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAuvB/B,CAAC"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * The preamble: the token, the desktop flag, the phone query, the string
3
+ * table and the locale, the icon set, and the handles to the elements the
4
+ * rest of the script paints into.
5
+ *
6
+ * One part of the client script; see ./index.ts for how the parts join.
7
+ */
8
+ export declare const BASE: string;
@@ -0,0 +1,400 @@
1
+ /**
2
+ * The preamble: the token, the desktop flag, the phone query, the string
3
+ * table and the locale, the icon set, and the handles to the elements the
4
+ * rest of the script paints into.
5
+ *
6
+ * One part of the client script; see ./index.ts for how the parts join.
7
+ */
8
+ export const BASE = String.raw `
9
+ const STRINGS = window.__STRINGS;
10
+ /**
11
+ * The token, from the desktop bridge when there is one.
12
+ *
13
+ * The browser gets it in the query string because that is the only channel a
14
+ * bare URL has. The desktop shell hands it over through a preload instead: a
15
+ * token in the address bar lands in history, in the window title, and in every
16
+ * screenshot of a bug report.
17
+ */
18
+ const token =
19
+ (window.oharness && window.oharness.token) ||
20
+ new URLSearchParams(location.search).get("token") ||
21
+ "";
22
+
23
+ // Only the shell knows whether the window has a title bar of its own; in a
24
+ // browser tab the reserved strip would be a gap with nothing in it.
25
+ if (window.oharness && window.oharness.insetTitlebar) {
26
+ document.body.classList.add("inset-titlebar");
27
+ }
28
+
29
+ /** Whether this page is the desktop window rather than a browser tab. */
30
+ const IS_DESKTOP = Boolean(window.oharness && window.oharness.desktop);
31
+
32
+ /**
33
+ * Whether this page is inside the browser extension's side panel.
34
+ *
35
+ * The panel is about as wide as a phone, so the phone layout serves it; what
36
+ * it drops is the built-in browser, which beside the person's own browser
37
+ * would be a browser inside the browser. The server lets this page be framed
38
+ * by the extension and by nothing else.
39
+ */
40
+ const IS_EMBED = new URLSearchParams(location.search).get("embed") === "sidepanel";
41
+ if (IS_EMBED) document.documentElement.classList.add("embed");
42
+
43
+ /**
44
+ * Tell the side panel around this page something. The frame's parent is the
45
+ * extension, the only origin allowed to embed this page; outside a panel this
46
+ * says nothing to anyone.
47
+ */
48
+ function embedTell(message) {
49
+ if (!IS_EMBED || window.parent === window) return;
50
+ const parentOrigin = (location.ancestorOrigins && location.ancestorOrigins[0]) || "*";
51
+ window.parent.postMessage(message, parentOrigin);
52
+ }
53
+
54
+ /** Whether the page is laid out for a phone right now; the stylesheet's breakpoint. */
55
+ const PHONE_QUERY = window.matchMedia("(max-width:760px)");
56
+ const isPhone = () => PHONE_QUERY.matches;
57
+
58
+ // --- language --------------------------------------------------------------
59
+
60
+ /**
61
+ * An inline icon, built as SVG nodes.
62
+ *
63
+ * Unicode glyphs come from whichever font happens to carry them, so a row of
64
+ * them arrives at inconsistent weights and optical sizes. These share one
65
+ * viewBox and one stroke width with the icons in the markup.
66
+ */
67
+ const ICON_PATHS = {
68
+ // A padlock: the shackle as a path, the body as a rect.
69
+ lock: ["M5.33 7.33V5.33a2.67 2.67 0 0 1 5.34 0v2"],
70
+ lockRects: [[3.33, 7.33, 9.34, 6.67, 1.6]],
71
+ // Same drawing as the Settings item in the nav, so the model picker's
72
+ // "manage" link and the place it leads to are recognisably one thing.
73
+ settings: ["M12.67 4.67h-6", "M9.33 11.33H3.33"],
74
+ settingsCircles: [[4.67, 4.67, 2], [11.33, 11.33, 2]],
75
+ // Shut and open, drawn on the same body so the swap reads as one folder
76
+ // moving rather than as two icons trading places: the back panel, the left
77
+ // edge and the bottom hold still, and only the front flap tips forward.
78
+ //
79
+ // Two things were wrong with the pair this replaces. It sat on a 1.33–14.67
80
+ // box while every icon beside it sits on 2–14, so the folder was the widest
81
+ // mark in the menu and read as the heaviest. And its tab was 1.8 units deep
82
+ // out of ten: under two pixels at the size this is actually drawn, which
83
+ // left a rounded rectangle with a nick in it rather than a folder.
84
+ folder: [
85
+ "M3.33 3h2.87a1.33 1.33 0 0 1 1.09.57l.85 1.22a1.33 1.33 0 0 0 1.09.57H12.67" +
86
+ "A1.33 1.33 0 0 1 14 6.7v4.97A1.33 1.33 0 0 1 12.67 13H3.33" +
87
+ "A1.33 1.33 0 0 1 2 11.67V4.33A1.33 1.33 0 0 1 3.33 3Z",
88
+ ],
89
+ folderOpen: [
90
+ "M4.4 10.3l.87-1.71a1.33 1.33 0 0 1 1.19-.72H12.8a1.33 1.33 0 0 1 1.2 1.6" +
91
+ "l-.8 2.7a1.33 1.33 0 0 1-1.42.83H3.33A1.33 1.33 0 0 1 2 11.67V4.33" +
92
+ "A1.33 1.33 0 0 1 3.33 3h2.87a1.33 1.33 0 0 1 1.09.57l.85 1.22" +
93
+ "a1.33 1.33 0 0 0 1.09.57H11.6a1.33 1.33 0 0 1 1.33 1.33v1.18",
94
+ ],
95
+ moreDots: [[3.2, 8], [8, 8], [12.8, 8]],
96
+ // The same chevron the composer's pickers draw, and pointing the same way:
97
+ // both menus open upward, and both mark themselves with a caret that reads as
98
+ // "there is a list here" rather than one that promises a direction.
99
+ caret: ["M4.5 6.5 8 10l3.5-3.5"],
100
+ // The account control can both open and close its menu, so it uses the
101
+ // familiar paired chevrons instead of borrowing a one-way dropdown caret.
102
+ chevronsVertical: ["M5.5 6.5 8 4l2.5 2.5", "M5.5 9.5 8 12l2.5-2.5"],
103
+ // The three themes, drawn the same way the site draws them so the two
104
+ // controls are recognisably the same control.
105
+ themeSystem: ["M6 13.4h4"],
106
+ themeLight: ["M8 1.4v1.4M8 13.2v1.4M1.4 8h1.4M13.2 8h1.4M3.4 3.4l1 1M11.6 11.6l1 1M12.6 3.4l-1 1M4.4 11.6l-1 1"],
107
+ themeDark: ["M13.2 9.6A5.6 5.6 0 0 1 6.4 2.8a5.6 5.6 0 1 0 6.8 6.8Z"],
108
+ themeSystemRects: [[1.8, 2.6, 12.4, 8.6, 1.6]],
109
+ themeLightCircles: [[8, 8, 3.1]],
110
+ // A page with its corner turned back, which is the one mark that reads as
111
+ // "file" at thirteen pixels without any lines of text inside it.
112
+ file: [
113
+ "M9.33 1.33H4a1.33 1.33 0 0 0-1.33 1.34v10.66A1.33 1.33 0 0 0 4 14.67h8" +
114
+ "a1.33 1.33 0 0 0 1.33-1.34V5.33Z",
115
+ "M9.33 1.33v4h4",
116
+ ],
117
+ // Two pins and a body: a connector is the thing something else plugs into.
118
+ plug: ["M6 1.33v3.34", "M10 1.33v3.34", "M4 4.67h8v3.33a4 4 0 0 1-8 0Z", "M8 12v2.67"],
119
+ // Stacked plates, because a plugin arrives as a bundle of contributions
120
+ // rather than as one thing.
121
+ plugin: ["M8 1.33 1.33 4.67 8 8l6.67-3.33Z", "M1.33 11.33 8 14.67l6.67-3.34", "M1.33 8 8 11.33 14.67 8"],
122
+ /* A bookmark: one path, and the only shape in this menu that is neither a
123
+ sheet nor a folder. The open book it replaced was two mirrored paths
124
+ meeting at a spine, which at the 13px these rows use closed into a blob —
125
+ two strokes a pixel apart read as one thick one. */
126
+ skill: ["M4.5 2.5h7v11l-3.5-2.6-3.5 2.6Z"],
127
+ /* A prompt caret: what a command line looks like anywhere else. */
128
+ command: ["M4 5.5 6.5 8 4 10.5", "M8.5 10.5H12"],
129
+ /* A bare chevron. The other back arrow has a shaft, which is right on a
130
+ page header and heavy on a menu row sitting one line above the thing it
131
+ returns to. */
132
+ chevronLeft: ["M9.5 4.5 6 8l3.5 3.5"],
133
+ /* Two strokes, for the corner of a dialog. */
134
+ close: ["M4.5 4.5l7 7", "M11.5 4.5l-7 7"],
135
+ /* Its counterpart, for adding one. */
136
+ plus: ["M8 3.5v9", "M3.5 8h9"],
137
+ search: ["M11.5 11.5 14 14"],
138
+ searchCircles: [[7.3, 7.3, 4.6]],
139
+ // A shopfront: an awning over a counter, for the list of what is on offer.
140
+ market: ["M2 6.67h12", "M3.33 6.67 4.67 2.67h6.66l1.34 4", "M2.67 6.67v6.66h10.67V6.67"],
141
+ // An arrow pointing back the way the menu came in.
142
+ back: ["M12.67 8H3.33", "M7.33 12 3.33 8l4-4"],
143
+ // An arrow leaving a tray, for the upload that still goes to this machine.
144
+ upload: ["M8 10.67V2", "M4.67 5.33 8 2l3.33 3.33", "M2 10.67v2A1.33 1.33 0 0 0 3.33 14h9.34A1.33 1.33 0 0 0 14 12.67v-2"],
145
+ // A trunk with a branch leaving it, which is what forking a turn does to the
146
+ // transcript: the arc lands on the new node rather than crossing it.
147
+ fork: ["M5 2.6v7.7", "M11 6a5.7 5.7 0 0 1-4.3 6"],
148
+ forkCircles: [[11, 4.3], [5, 12]],
149
+ copy: [
150
+ "M6.67 5.33h6.66a1.33 1.33 0 0 1 1.34 1.34v6.66a1.33 1.33 0 0 1-1.34 1.34" +
151
+ "H6.67a1.33 1.33 0 0 1-1.34-1.34V6.67a1.33 1.33 0 0 1 1.34-1.34Z",
152
+ "M2.67 10.67a1.33 1.33 0 0 1-1.34-1.34V2.67a1.33 1.33 0 0 1 1.34-1.34h6.66" +
153
+ "a1.33 1.33 0 0 1 1.34 1.34",
154
+ ],
155
+ // A pin seen from the side: head, shaft, and the point going into the page.
156
+ pin: [
157
+ "M9.5 2.5 13.5 6.5",
158
+ "M10.2 3.2 7.6 5a2.7 2.7 0 0 1-1.7.5l-1.6-.1a.6.6 0 0 0-.45 1.03l5.72 5.72a.6.6 0 0 0 1.03-.45l-.1-1.6A2.7 2.7 0 0 1 11 8.4l1.8-2.6",
159
+ "M6.2 9.8 2.8 13.2",
160
+ ],
161
+ // The product's own mark: two open arcs around a centre, the same glyph the
162
+ // application icon draws, at half its scale. Drawn rather than fetched,
163
+ // because the official plan's rows have to have a mark on a machine that
164
+ // has never reached the network.
165
+ oharness: ["M10 3H6a3 3 0 0 0-3 3v4", "M6 13h4a3 3 0 0 0 3-3V6"],
166
+ oharnessDots: [[8, 8]],
167
+ // A speech bubble with a tail: what a session is, before it has a title.
168
+ chat: [
169
+ "M14 10a1.33 1.33 0 0 1-1.33 1.33H4.67L2 14V3.33A1.33 1.33 0 0 1 3.33 2h9.34" +
170
+ "A1.33 1.33 0 0 1 14 3.33Z",
171
+ ],
172
+ // The five permission modes. Five short words in a column look alike until
173
+ // they are read; a glyph in front of each lets the one in use be found by
174
+ // shape from the bar, the way the vendor mark does for the model.
175
+ //
176
+ // Manual is by hand, and a raised palm is also the sign for "wait": three
177
+ // fingers, a thumb, and the heel of the hand in one sweep. Four fingers
178
+ // would be the honest count, but at fifteen pixels the fourth turns the
179
+ // top edge into a comb.
180
+ modeAsk: [
181
+ "M12 7.4V4a1.33 1.33 0 0 0-2.67 0",
182
+ "M9.33 6.7V2.7a1.33 1.33 0 0 0-2.66 0v1.3",
183
+ "M6.67 7V4A1.33 1.33 0 0 0 4 4v5.33",
184
+ "M12 5.33a1.33 1.33 0 1 1 2.67 0v4a5.33 5.33 0 0 1-5.34 5.34H8" +
185
+ "c-1.87 0-3-.58-4-1.56l-2.4-2.4a1.33 1.33 0 0 1 1.89-1.89L4.67 10",
186
+ ],
187
+ // Accept edits lets the pencil through.
188
+ modeEdits: ["M11.3 2.7 13.3 4.7 5.3 12.7H3.3v-2Z", "M9.8 4.2l2 2"],
189
+ // Plan is read-only: an eye, which looks and does not touch.
190
+ modePlan: ["M1.6 8s2.4-4.4 6.4-4.4S14.4 8 14.4 8s-2.4 4.4-6.4 4.4S1.6 8 1.6 8Z"],
191
+ modePlanCircles: [[8, 8, 2.1]],
192
+ // Auto waves the routine through: a bolt.
193
+ modeAuto: ["M8.8 1.6 3.6 9h4l-.7 5.4L12.4 7h-4Z"],
194
+ // Bypass leaves the gate open: a padlock with its shackle swung clear.
195
+ modeBypass: ["M5.4 7.4V5.2a2.6 2.6 0 0 1 5-1"],
196
+ modeBypassRects: [[3.2, 7.4, 9.6, 6.2, 1.6]],
197
+ // The date picker's trigger and its month arrows. The same marks the site's
198
+ // picker draws, because it is the same control.
199
+ calendar: ["M2 6.5h12M5.5 1.5v3M10.5 1.5v3"],
200
+ calendarRects: [[2, 3, 12, 11, 2.5]],
201
+ chevronLeft: ["M10 3.5 5.5 8l4.5 4.5"],
202
+ chevronRight: ["M6 3.5 10.5 8 6 12.5"],
203
+ };
204
+
205
+ function svgIcon(name, extra) {
206
+ const NS = "http://www.w3.org/2000/svg";
207
+ const svg = document.createElementNS(NS, "svg");
208
+ svg.setAttribute("viewBox", "0 0 16 16");
209
+ svg.setAttribute("fill", "none");
210
+ svg.setAttribute("stroke", "currentColor");
211
+ svg.setAttribute("stroke-width", "1.5");
212
+ svg.setAttribute("stroke-linecap", "round");
213
+ svg.setAttribute("stroke-linejoin", "round");
214
+ svg.setAttribute("class", "ico-sm" + (extra ? " " + extra : ""));
215
+ for (const d of ICON_PATHS[name] || []) {
216
+ const path = document.createElementNS(NS, "path");
217
+ path.setAttribute("d", d);
218
+ svg.appendChild(path);
219
+ }
220
+ // The radius is per-circle because the icons it serves disagree: a fork's
221
+ // node is a dot, a settings knob is a ring you look through.
222
+ for (const [x, y, w, h, rx] of ICON_PATHS[name + "Rects"] || []) {
223
+ const rect = document.createElementNS(NS, "rect");
224
+ rect.setAttribute("x", String(x));
225
+ rect.setAttribute("y", String(y));
226
+ rect.setAttribute("width", String(w));
227
+ rect.setAttribute("height", String(h));
228
+ rect.setAttribute("rx", String(rx ?? 1.6));
229
+ svg.appendChild(rect);
230
+ }
231
+ for (const [cx, cy, r] of ICON_PATHS[name + "Circles"] || []) {
232
+ const circle = document.createElementNS(NS, "circle");
233
+ circle.setAttribute("cx", String(cx));
234
+ circle.setAttribute("cy", String(cy));
235
+ circle.setAttribute("r", String(r ?? 1.7));
236
+ svg.appendChild(circle);
237
+ }
238
+ // Solid, not stroked: a ring that small reads as a smudge, and three of them
239
+ // in a row read as a chain rather than an ellipsis.
240
+ for (const [cx, cy] of ICON_PATHS[name + "Dots"] || []) {
241
+ const dot = document.createElementNS(NS, "circle");
242
+ dot.setAttribute("cx", String(cx));
243
+ dot.setAttribute("cy", String(cy));
244
+ dot.setAttribute("r", "1.35");
245
+ dot.setAttribute("fill", "currentColor");
246
+ dot.setAttribute("stroke", "none");
247
+ svg.appendChild(dot);
248
+ }
249
+ return svg;
250
+ }
251
+
252
+ const LOCALE = (() => {
253
+ const asked = new URLSearchParams(location.search).get("lang");
254
+ if (asked && STRINGS[asked]) return asked;
255
+ // The browser's list, most preferred first; the base language is enough to
256
+ // match, so zh-Hans-CN finds zh.
257
+ for (const tag of navigator.languages || [navigator.language || "en"]) {
258
+ const base = String(tag).toLowerCase().split("-")[0];
259
+ if (STRINGS[base]) return base;
260
+ }
261
+ return "en";
262
+ })();
263
+
264
+ /**
265
+ * A translated string.
266
+ *
267
+ * Falls back to English rather than to the key: a missing translation should
268
+ * still read as a sentence. Placeholders are named so a translation can move
269
+ * them, which positional ones cannot survive.
270
+ */
271
+ function t(key, vars) {
272
+ const text = (STRINGS[LOCALE] && STRINGS[LOCALE][key]) || STRINGS.en[key] || key;
273
+ if (!vars) return text;
274
+ return text.replace(/\{([a-z]+)\}/g, (whole, name) =>
275
+ vars[name] === undefined ? whole : String(vars[name]),
276
+ );
277
+ }
278
+ /**
279
+ * The same string, in the shell that is reading it.
280
+ *
281
+ * This page is both a desktop window and a tab in someone's browser, and a
282
+ * few lines can only be true in one of them — "no browser on this machine"
283
+ * is nonsense read inside a browser, and the sign-in column called itself an
284
+ * app to a reader looking at a web page. The browser wording lives under the
285
+ * same key with .web on the end; a key with no such sibling is shell-neutral
286
+ * and answers the same either way, which is nearly all of them.
287
+ */
288
+ function ts(key, vars) {
289
+ const web = key + ".web";
290
+ const known = (STRINGS[LOCALE] && STRINGS[LOCALE][web]) || STRINGS.en[web];
291
+ return t(!IS_DESKTOP && known ? web : key, vars);
292
+ }
293
+ /*
294
+ * A path that already carries a query needs an ampersand, not a second "?".
295
+ * With one, /history?session=<id>?token=<t> sent the token as part of the
296
+ * session id and the server answered 401 — so resuming a session showed the
297
+ * empty invitation instead of its transcript, on every request that passes a
298
+ * parameter of its own.
299
+ */
300
+ const q = (path) =>
301
+ path + (path.includes("?") ? "&" : "?") + "token=" + encodeURIComponent(token);
302
+ /**
303
+ * A GET that reports why it failed.
304
+ *
305
+ * The panels used to swallow errors into an empty object, which rendered as a
306
+ * page of em-dashes with no hint that anything had gone wrong — a 401 from a
307
+ * stale token looked exactly like a working directory that is somehow blank.
308
+ */
309
+ async function getJson(path) {
310
+ try {
311
+ const response = await fetch(q(path), { signal: AbortSignal.timeout(20000) });
312
+ const body = await response.json().catch(() => ({}));
313
+ if (!response.ok) {
314
+ return { error: body.error || "HTTP " + response.status + " from " + path };
315
+ }
316
+ return body;
317
+ } catch (err) {
318
+ return { error: err.name === "TimeoutError" ? path + " timed out" : String(err.message || err) };
319
+ }
320
+ }
321
+
322
+ /** A banner for a panel that could not load, in place of blank fields. */
323
+ /**
324
+ * A memory error in this page's language. The engine sends a code and its
325
+ * values with the English message; the page says it in its own words when it
326
+ * has them, and passes the message through when it does not.
327
+ */
328
+ function memoryText(failure) {
329
+ const key = failure && failure.code ? "memory.err." + failure.code : "";
330
+ if (key && (STRINGS.en[key] || (STRINGS[LOCALE] && STRINGS[LOCALE][key]))) return t(key, failure.params || {});
331
+ return (failure && failure.message) || t("memory.failed");
332
+ }
333
+
334
+ /** Stands in while a panel fetches, so a click never lands on a blank page. */
335
+ function panelLoading() {
336
+ const panel = el("div", "panel panel-loading");
337
+ panel.appendChild(el("span", "spinner"));
338
+ panel.appendChild(el("span", "working-label", t("market.loading")));
339
+ return panel;
340
+ }
341
+
342
+ function panelError(message) {
343
+ const panel = el("div", "panel");
344
+ const box = el("div", "row-card");
345
+ box.appendChild(el("div", "row-title", t("error.title")));
346
+ box.appendChild(el("div", "muted small", message));
347
+ box.appendChild(el("div", "muted small", t("error.stale")));
348
+ panel.appendChild(box);
349
+ return panel;
350
+ }
351
+
352
+ const api = (path, body, timeoutMs) =>
353
+ fetch(q(path), {
354
+ method: "POST",
355
+ headers: { "content-type": "application/json" },
356
+ body: JSON.stringify(body || {}),
357
+ // Every button that flips to a busy label needs the promise to settle, or
358
+ // it stays busy forever when the server does not answer.
359
+ signal: AbortSignal.timeout(timeoutMs || 30000),
360
+ })
361
+ .then((r) => r.json().catch(() => ({})))
362
+ .catch((err) => ({ error: err.name === "TimeoutError" ? "timed out" : String(err.message || err) }));
363
+
364
+ /**
365
+ * Record that a feature was used. Does nothing unless analytics are on.
366
+ *
367
+ * window.__TRACK exists only when the server was started with a PostHog key —
368
+ * see analytics.ts — so the calls below read the same either way and nobody
369
+ * has to remember which branch they are writing in.
370
+ *
371
+ * There is deliberately no way to send text through here: every caller passes
372
+ * an event name and a handful of enumerable properties — which panel, whether
373
+ * a permission was allowed, which model — and never anything the user typed.
374
+ */
375
+ function track(event, props) {
376
+ if (window.__TRACK) window.__TRACK(event, props);
377
+ }
378
+
379
+ const log = document.getElementById("log");
380
+ const panelView = document.getElementById("panel-view");
381
+ const panelBody = document.getElementById("panel-body");
382
+ const panelNotes = document.getElementById("panel-notes");
383
+ const input = document.getElementById("input");
384
+ const sendBtn = document.getElementById("send");
385
+ const stopBtn = document.getElementById("stop");
386
+ const jumpBtn = document.getElementById("jump");
387
+ const metaEl = document.getElementById("cost");
388
+ const tokensEl = document.getElementById("tokens");
389
+ const cacheEl = document.getElementById("cache");
390
+ const ctxBar = document.querySelector("#ctx i");
391
+ const ctxText = document.getElementById("ctxtext");
392
+ const sessionList = document.getElementById("sessions");
393
+ const sidebarEl = document.querySelector("body > aside");
394
+ const ask = document.getElementById("ask");
395
+
396
+ let pendingId = null;
397
+ let atBottom = true;
398
+
399
+ `;
400
+ //# sourceMappingURL=base.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"base.js","sourceRoot":"","sources":["../../../src/page/client/base.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAuY7B,CAAC"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * The browser pane: what the agent's browser is looking at, live, and a way to
3
+ * take the mouse off it.
4
+ *
5
+ * A column beside the transcript, not a panel over it. Watching what the model
6
+ * does is only useful next to what it is saying, and the layout already had the
7
+ * shape for that — the diff pane is a sibling of the main column and slides in
8
+ * the same way, so this is built the same way rather than inventing a second
9
+ * kind of side surface.
10
+ *
11
+ * The picture is an img pointed at an MJPEG stream, so the browser decodes the
12
+ * frames and this file never touches one. The element is created once and kept:
13
+ * the pane closes by collapsing to zero width, which leaves the stream running
14
+ * rather than tearing it down and reconnecting on the next toggle.
15
+ *
16
+ * Taking over sends the same input events the tools send, through the same
17
+ * call, so a person and the model reach the page identically.
18
+ *
19
+ * One part of the client script; see ./index.ts for how the parts join.
20
+ */
21
+ export declare const BROWSER: string;