@oharness/server 0.9.2 → 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 (61) hide show
  1. package/dist/i18n.js +356 -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 +709 -58
  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 +12 -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 +229 -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 +174 -6
  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 +706 -104
  38. package/dist/page/style.js.map +1 -1
  39. package/dist/page.js +161 -54
  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/theme.js +5 -0
  57. package/dist/theme.js.map +1 -1
  58. package/dist/turn-shots.d.ts +27 -0
  59. package/dist/turn-shots.js +71 -0
  60. package/dist/turn-shots.js.map +1 -0
  61. package/package.json +2 -2
@@ -20,7 +20,8 @@ let currentCwd = "";
20
20
  function formatTokens(n) {
21
21
  if (n < 1000) return String(n);
22
22
  if (n < 1000000) return (n / 1000).toFixed(n < 10000 ? 1 : 0) + "k";
23
- return (n / 1000000).toFixed(1) + "M";
23
+ // "1M", not "1.0M": a model's window is usually a round number.
24
+ return (n / 1000000).toFixed(1).replace(/\.0$/, "") + "M";
24
25
  }
25
26
 
26
27
  /**
@@ -44,10 +45,129 @@ function paintTokens(usage) {
44
45
  : "";
45
46
  }
46
47
 
48
+ /*
49
+ * The context panel: what the ring stands for, opened by clicking it. The
50
+ * window split into what the model is given every time (system prompt, tool
51
+ * definitions), the conversation, and what is left; where automatic
52
+ * compaction fires; and a way to compact now instead of waiting for it.
53
+ */
54
+ let ctxState = null;
55
+ let ctxPanel = null;
56
+
57
+ const CTX_PARTS = [
58
+ { key: "system", label: "ctx.system", color: "var(--ctx-system)" },
59
+ { key: "tools", label: "ctx.tools", color: "var(--ctx-tools)" },
60
+ { key: "messages", label: "ctx.messages", color: "var(--ctx-messages)" },
61
+ ];
62
+
63
+ function paintCtxPanel() {
64
+ const c = ctxState;
65
+ const panel = ctxPanel;
66
+ panel.replaceChildren();
67
+ const pct = (n) => Math.round((n / c.window) * 1000) / 10;
68
+
69
+ const head = el("div", "ctx-head");
70
+ head.appendChild(el("span", "ctx-title", t("ctx.title")));
71
+ head.appendChild(el("span", "ctx-total", t("ctx.total", {
72
+ used: formatTokens(c.total),
73
+ total: formatTokens(c.window),
74
+ pct: Math.round((c.total / c.window) * 100),
75
+ })));
76
+ panel.appendChild(head);
77
+
78
+ // One bar, a segment per part, the empty rest as track. The tick marks where
79
+ // automatic compaction fires; it is a fill two pixels wide, not a rule.
80
+ const bar = el("div", "ctx-bar");
81
+ for (const part of CTX_PARTS) {
82
+ const seg = el("i");
83
+ seg.style.width = Math.min(100, pct(c[part.key])) + "%";
84
+ seg.style.background = part.color;
85
+ bar.appendChild(seg);
86
+ }
87
+ if (c.compactAt) {
88
+ const tick = el("b", "ctx-tick");
89
+ tick.style.left = Math.min(100, pct(c.compactAt)) + "%";
90
+ bar.appendChild(tick);
91
+ }
92
+ panel.appendChild(bar);
93
+
94
+ const rows = el("div", "ctx-rows");
95
+ const row = (color, label, value) => {
96
+ const r = el("div", "ctx-row");
97
+ const dot = el("span", "ctx-dot");
98
+ dot.style.background = color;
99
+ r.appendChild(dot);
100
+ r.appendChild(el("span", "ctx-label", label));
101
+ r.appendChild(el("span", "ctx-value", value));
102
+ rows.appendChild(r);
103
+ };
104
+ for (const part of CTX_PARTS) row(part.color, t(part.label), formatTokens(c[part.key]));
105
+ row("var(--track)", t("ctx.free"), formatTokens(Math.max(0, c.window - c.total)));
106
+ panel.appendChild(rows);
107
+
108
+ const notes = el("div", "ctx-notes");
109
+ notes.appendChild(el("div", "", c.compactAt
110
+ ? t("ctx.autoAt", { n: formatTokens(c.compactAt) })
111
+ : t("ctx.autoOff")));
112
+ // Said plainly, because the two numbers can disagree by a lot: a provider's
113
+ // count of the prompt and a character-based guess at it.
114
+ notes.appendChild(el("div", "", t(c.measured ? "ctx.measured" : "ctx.estimated")));
115
+ panel.appendChild(notes);
116
+
117
+ const compact = el("button", "menu-item ctx-compact");
118
+ compact.type = "button";
119
+ compact.appendChild(el("span", "menu-label", t("ctx.compactNow")));
120
+ compact.addEventListener("click", () => { closeCtxPanel(); compactConversation(); });
121
+ panel.appendChild(compact);
122
+ placeCtxPanel();
123
+ }
124
+
125
+ function placeCtxPanel() {
126
+ const box = ctxRing.getBoundingClientRect();
127
+ // Above the ring and flush with its right edge: the ring sits at the right
128
+ // end of the line under the composer, with the box it belongs to above it.
129
+ ctxPanel.style.right = Math.max(8, window.innerWidth - box.right) + "px";
130
+ ctxPanel.style.bottom = (window.innerHeight - box.top + 8) + "px";
131
+ }
132
+
133
+ function onCtxAway(event) {
134
+ if (!ctxPanel.contains(event.target) && !ctxRing.contains(event.target)) closeCtxPanel();
135
+ }
136
+ function onCtxKey(event) {
137
+ if (event.key === "Escape") { event.stopPropagation(); closeCtxPanel(); ctxRing.focus(); }
138
+ }
139
+
140
+ function closeCtxPanel() {
141
+ if (!ctxPanel) return;
142
+ ctxPanel.remove();
143
+ ctxPanel = null;
144
+ ctxRing.setAttribute("aria-expanded", "false");
145
+ document.removeEventListener("mousedown", onCtxAway, true);
146
+ document.removeEventListener("keydown", onCtxKey, true);
147
+ window.removeEventListener("resize", placeCtxPanel);
148
+ }
149
+
150
+ ctxRing.addEventListener("click", () => {
151
+ if (ctxPanel) return closeCtxPanel();
152
+ if (!ctxState) return;
153
+ ctxPanel = el("div", "menu pop ctx-panel");
154
+ ctxPanel.setAttribute("role", "dialog");
155
+ ctxPanel.setAttribute("aria-label", t("ctx.title"));
156
+ document.body.appendChild(ctxPanel);
157
+ ctxRing.setAttribute("aria-expanded", "true");
158
+ paintCtxPanel();
159
+ document.addEventListener("mousedown", onCtxAway, true);
160
+ document.addEventListener("keydown", onCtxKey, true);
161
+ window.addEventListener("resize", placeCtxPanel);
162
+ });
163
+
47
164
  function applyState(s) {
48
165
  if (!s) return;
49
166
  if (s.model) modelPicker.set(modelPicker.options || [], s.model);
50
167
  if (s.mode) modePicker.set(MODES, s.mode);
168
+ // Kept per tab for the + menu's Plan mode row, which reads it.
169
+ if (s.sessionId && s.mode) pane(s.sessionId).mode = s.mode;
170
+ if (s.sessionId && Array.isArray(s.tasks)) tasksChanged(s.sessionId, s.tasks);
51
171
  effortPicker.set(s);
52
172
  if (s.cwd) {
53
173
  // Kept so the approval dialog can show a path the way the user thinks of
@@ -60,22 +180,37 @@ function applyState(s) {
60
180
  metaEl.textContent = s.costUsd > 0 ? "$" + s.costUsd.toFixed(4) : "";
61
181
  paintTokens(s.usage);
62
182
  const meta = document.getElementById("meta");
63
- // A meter reading 0% is noise: it says nothing and takes the eye.
64
- // Shown from half a window on, not from half a percent. Below that there is
65
- // nothing to act on, and a bar sitting at 2% all day is a permanent piece of
66
- // furniture that says the same thing every time you look at it.
67
- meta.classList.toggle("has-ctx", typeof s.contextUsed === "number" && s.contextUsed >= 0.5);
68
- if (typeof s.contextUsed === "number") {
183
+ // Shown once the conversation has anything in it: how full the window is
184
+ // decides when earlier detail starts being summarized away, and that is
185
+ // worth knowing before it happens rather than only once it is close.
186
+ const hasCtx = typeof s.contextUsed === "number" && s.contextTokens > 0;
187
+ meta.classList.toggle("has-ctx", hasCtx);
188
+ if (hasCtx) {
69
189
  const pct = Math.round(s.contextUsed * 100);
70
- ctxBar.style.width = pct + "%";
71
- ctxBar.style.background = pct >= 90 ? "var(--err)" : pct >= 70 ? "var(--warn)" : "var(--ok)";
72
- ctxText.textContent = pct + "%";
190
+ // At least a sliver: an empty ring reads as a meter that is not working.
191
+ ctxRing.style.setProperty("--ctx-pct", String(Math.max(pct, 3)));
192
+ ctxRing.style.setProperty("--ctx-fill", pct >= 90 ? "var(--err)" : pct >= 70 ? "var(--warn)" : "var(--brand)");
193
+ const label = t("usage.contextOf", {
194
+ used: formatTokens(s.contextTokens),
195
+ total: formatTokens(s.contextWindow),
196
+ pct,
197
+ });
198
+ ctxRing.title = label;
199
+ ctxRing.setAttribute("aria-label", label);
73
200
  }
201
+ // The panel reads the latest breakdown, and redraws if it is open, so a turn
202
+ // finishing under it is not shown with the numbers from before.
203
+ ctxState = hasCtx && s.context ? s.context : null;
204
+ if (ctxPanel) ctxState ? paintCtxPanel() : closeCtxPanel();
74
205
  setBusy(Boolean(s.busy));
75
206
  // Addressed by session: a reload and a tab switch both land here, and the
76
207
  // pane this state describes is not always the one an event last touched.
77
208
  if (s.sessionId) pane(s.sessionId).queued = s.queued || [];
209
+ if (s.sessionId) pane(s.sessionId).goal = s.goal || "";
210
+ // Absent outside the desktop app: no helper, no grants, no row to offer.
211
+ if (s.sessionId) pane(s.sessionId).computer = s.computerUse || null;
78
212
  renderQueued();
213
+ renderAttachments();
79
214
  // The state knows the session's name; a tab opened before it had one would
80
215
  // otherwise keep the placeholder until the page reloaded.
81
216
  if (s.sessionId && s.title && pane(s.sessionId).title !== s.title) {
@@ -201,16 +336,49 @@ function routineHead(count, open) {
201
336
  return head;
202
337
  }
203
338
 
339
+ /**
340
+ * The pin on a pinned row, which unpins it when clicked.
341
+ *
342
+ * It used to be a mark only, drawn inside the row's own button: clicking it
343
+ * folded the project or opened the session, and the way to unpin was hidden
344
+ * in the ⋯ menu while the thing that looked like the control did nothing of
345
+ * the kind. A span with a button's role rather than a <button>, because the
346
+ * project header it sits in is already a button and buttons do not nest.
347
+ */
348
+ function pinMark(label, unpin) {
349
+ const mark = el("span", "pin-mark");
350
+ mark.appendChild(svgIcon("pin"));
351
+ mark.setAttribute("role", "button");
352
+ mark.tabIndex = 0;
353
+ mark.title = label;
354
+ mark.setAttribute("aria-label", label);
355
+ const act = (e) => {
356
+ e.preventDefault();
357
+ e.stopPropagation();
358
+ unpin();
359
+ };
360
+ mark.addEventListener("click", act);
361
+ mark.addEventListener("keydown", (e) => {
362
+ if (e.key === "Enter" || e.key === " ") act(e);
363
+ });
364
+ // A drag that starts on the pin must not carry the row away with it.
365
+ mark.addEventListener("mousedown", (e) => e.stopPropagation());
366
+ return mark;
367
+ }
368
+
204
369
  /** A project header: a folder, a name, and how many sessions it holds. */
205
370
  function projectHead(cwd, name, count, open) {
206
- const head = el("button", "fold" + (open ? " open" : ""));
371
+ const wt = worktreeAt(cwd);
372
+ const head = el("button", "fold" + (open ? " open" : "") + (wt ? " wt" : ""));
207
373
  head.type = "button";
208
374
  // The folder itself says which state it is in — it is the only thing on the
209
- // row that can, now that there is no arrow beside it.
210
- head.appendChild(svgIcon(open ? "folderOpen" : "folder"));
375
+ // row that can, now that there is no arrow beside it. A worktree is a branch
376
+ // off the project above it, and draws as one.
377
+ head.appendChild(svgIcon(wt ? "worktree" : open ? "folderOpen" : "folder"));
211
378
  head.appendChild(el("span", "fold-name", name));
379
+ if (wt) head.title = (wt.branch ? wt.branch + " · " : "") + wt.root;
212
380
  if ((projects.find((project) => project.path === cwd) || {}).pinned) {
213
- head.appendChild(svgIcon("pin", "pin-mark"));
381
+ head.appendChild(pinMark(t("projects.unpin"), () => void togglePin("project", cwd, false)));
214
382
  }
215
383
  head.addEventListener("click", () => toggleProject(cwd, open));
216
384
 
@@ -271,7 +439,11 @@ function projectHead(cwd, name, count, open) {
271
439
  } });
272
440
 
273
441
  const pinned = (projects.find((project) => project.path === cwd) || {}).pinned === true;
442
+ const finish = wt
443
+ ? [menuItem(t("worktree.finish"), { key: "w", run: () => void showWorktreeFinish(cwd, anchor, head) }), menuSep()]
444
+ : [];
274
445
  const items = [
446
+ ...finish,
275
447
  menuItem(t("projects.open"), { key: "o", run: () => {
276
448
  closeRowMenu();
277
449
  void chooseProject(cwd);
@@ -298,10 +470,11 @@ function projectHead(cwd, name, count, open) {
298
470
  closeRowMenu();
299
471
  exportProjectUi(cwd);
300
472
  } }),
301
- menuItem(t("projects.rename"), { key: "n", run: () => {
473
+ // A worktree's name is the one its branch and registry entry know it by.
474
+ ...(wt ? [] : [menuItem(t("projects.rename"), { key: "n", run: () => {
302
475
  closeRowMenu();
303
476
  void renameProjectUi(cwd, name);
304
- } }),
477
+ } })]),
305
478
  ];
306
479
  // Forgetting a path is only offered where it can do something: this row
307
480
  // stays on screen either way, because the sidebar groups by the tree a
@@ -491,7 +664,7 @@ function paintSessions() {
491
664
  // recent session; the rest follow by how recently they were touched — and
492
665
  // then a pin or a drag overrides both, because those were said out loud.
493
666
  const pinnedProject = (cwd) => (projects.find((p) => p.path === cwd) || {}).pinned === true;
494
- const order = arrange(
667
+ const sorted = arrange(
495
668
  [...byProject.keys()].sort((a, b) => {
496
669
  if (a === here) return -1;
497
670
  if (b === here) return 1;
@@ -501,6 +674,23 @@ function paintSessions() {
501
674
  projectOrder,
502
675
  pinnedProject,
503
676
  );
677
+ /*
678
+ * Worktrees sit directly under the project they came from, whatever their
679
+ * own recency — they are that project's parallel work, not projects of
680
+ * their own. One with no session yet still shows, so it can be finished.
681
+ */
682
+ const order = sorted.filter((cwd) => !worktreeAt(cwd) || !sorted.includes(worktreeAt(cwd).parent));
683
+ for (const wt of worktrees) {
684
+ if (!byProject.has(wt.path)) byProject.set(wt.path, []);
685
+ const at = order.indexOf(wt.parent);
686
+ if (at === -1) {
687
+ if (!order.includes(wt.path) && byProject.get(wt.path).length) order.push(wt.path);
688
+ continue;
689
+ }
690
+ let end = at + 1;
691
+ while (end < order.length && worktreeAt(order[end]) && worktreeAt(order[end]).parent === wt.parent) end++;
692
+ order.splice(end, 0, wt.path);
693
+ }
504
694
 
505
695
  // Everything here belongs to the project already named at the top of the
506
696
  // sidebar: a header repeating it is noise, and the rows do not need
@@ -741,6 +931,59 @@ function pointAnchor(e) {
741
931
  return { getBoundingClientRect: () => rect };
742
932
  }
743
933
 
934
+ /**
935
+ * The ways out of a worktree, in the menu it was asked from.
936
+ *
937
+ * Asked of the server each time rather than drawn from what the sidebar holds:
938
+ * whether there is anything to lose is the whole question, and the answer
939
+ * changes with every edit made in there.
940
+ */
941
+ async function showWorktreeFinish(cwd, anchor, head) {
942
+ const body = await getJson("/worktree/status?path=" + encodeURIComponent(cwd));
943
+ if (body.error) return void addNotice(body.error, "notice err");
944
+ const wt = body.worktree;
945
+ const st = body.status;
946
+ const name = projectName(wt.root);
947
+ const parentName = projectName(wt.parent);
948
+ const facts = st.clean
949
+ ? t("worktree.clean")
950
+ : [
951
+ st.commits ? t("worktree.commits", { n: st.commits, branch: wt.branch || "" }) : "",
952
+ st.changes.length ? t("worktree.changes", { n: st.changes.length }) : "",
953
+ ].filter(Boolean).join(" · ");
954
+ const keys = { remove: "r", discard: "d", commit: "c", push: "p", merge: "m", apply: "a" };
955
+ const items = body.actions.map((action) =>
956
+ menuItem(t("worktree.action." + action, { name: parentName }), {
957
+ key: keys[action],
958
+ danger: action === "discard",
959
+ run: () => {
960
+ closeRowMenu();
961
+ void finishWorktreeUi(wt, action, name, st.clean);
962
+ },
963
+ }),
964
+ );
965
+ openRowMenu(anchor, head, [el("div", "menu-note", name + " — " + facts), ...items]);
966
+ }
967
+
968
+ async function finishWorktreeUi(wt, action, name, clean) {
969
+ const body = { path: wt.path, action };
970
+ if (action === "commit") {
971
+ const message = window.prompt(t("worktree.commitPrompt"), wt.task || "");
972
+ if (!message) return;
973
+ body.message = message;
974
+ }
975
+ if (action === "discard" && !clean && !window.confirm(t("worktree.discardConfirm", { name }))) return;
976
+ const answer = await api("/worktree/finish", body, 5 * 60_000);
977
+ if (!answer || answer.error) return void addNotice((answer && answer.error) || t("worktree.failed"), "notice err");
978
+ addNotice(answer.result.message);
979
+ if (answer.result.url) window.open(answer.result.url, "_blank", "noopener");
980
+ // Its tabs and harness are gone with it; reloading drops every pane and
981
+ // path that pointed there at once, the way a rename does.
982
+ if (answer.result.removed) return void window.location.reload();
983
+ await loadProjects();
984
+ await loadSessions();
985
+ }
986
+
744
987
  function openRowMenu(button, row, items) {
745
988
  closeRowMenu();
746
989
  rowMenu.replaceChildren(...items);
@@ -784,7 +1027,7 @@ function sessionRow(s, nested) {
784
1027
  item.appendChild(el("div", "title", s.title || t("session.untitled")));
785
1028
  // Outside the title rather than in it: that line ellipsises, and a mark that
786
1029
  // vanishes with the end of a long name is not a mark.
787
- if (s.pinned) item.appendChild(svgIcon("pin", "pin-mark"));
1030
+ if (s.pinned) item.appendChild(pinMark(t("session.unpin"), () => void togglePin("session", s.id, false)));
788
1031
 
789
1032
  draggable(item, {
790
1033
  kind: "session",
@@ -1204,6 +1447,11 @@ function renderHistory(entries, into) {
1204
1447
  P.turn = null;
1205
1448
  P.streaming = null;
1206
1449
  for (const entry of entries || []) {
1450
+ if (entry.tasks) {
1451
+ P.turn = null;
1452
+ taskCard(entry.tasks);
1453
+ continue;
1454
+ }
1207
1455
  if (entry.role === "user") {
1208
1456
  if (entry.text || (entry.images && entry.images.length)) {
1209
1457
  addUser(entry.text || "", entry.images);
@@ -1224,7 +1472,8 @@ function renderHistory(entries, into) {
1224
1472
  if (entry.text) assistantTurn().appendChild(renderMarkdown(entry.text));
1225
1473
  for (const call of entry.tools || []) toolCard(call.id, call.name, call.name, call.input);
1226
1474
  for (const result of entry.results || []) {
1227
- finishTool(result.id, [{ type: "text", text: result.text }], 0, result.isError);
1475
+ const shots = (result.images || []).map((image) => ({ type: "image", src: image.src }));
1476
+ finishTool(result.id, [{ type: "text", text: result.text }, ...shots], 0, result.isError);
1228
1477
  }
1229
1478
  // The cards, again. Live, these come from the "artifact" event, which a
1230
1479
  // replay does not produce — so a resumed conversation had every message and
@@ -1237,6 +1486,7 @@ function renderHistory(entries, into) {
1237
1486
  const card = replayedArtifactCard(call.input);
1238
1487
  if (card) assistantTurn().appendChild(card);
1239
1488
  }
1489
+ if (entry.shots) turnShots(assistantTurn(), entry.shots);
1240
1490
  if (P.turn) turnActions(P.turn, entry.keepMessages);
1241
1491
  }
1242
1492
  for (const card of connectionCards.values()) {
@@ -1254,7 +1504,11 @@ function renderHistory(entries, into) {
1254
1504
  async function switchTo(body) {
1255
1505
  const state = await api("/session", body);
1256
1506
  if (state.error) return void addNotice(state.error, "err");
1507
+ await openState(state);
1508
+ }
1257
1509
 
1510
+ /** Show a session the server has just opened, as its own focused tab. */
1511
+ async function openState(state) {
1258
1512
  // A tab per session, focused on arrival.
1259
1513
  const record = pane(state.sessionId);
1260
1514
  record.title = state.title || "";
@@ -1 +1 @@
1
- {"version":3,"file":"state.js","sourceRoot":"","sources":["../../../src/page/client/state.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,CAAC,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA2vC9B,CAAC"}
1
+ {"version":3,"file":"state.js","sourceRoot":"","sources":["../../../src/page/client/state.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,CAAC,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAy/C9B,CAAC"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Background tasks: their rows over the composer, and the sheet one opens
3
+ * into — what the subagent was told, what it did, what it said — with a way
4
+ * to stop it or tell it something.
5
+ *
6
+ * One part of the client script; see ./index.ts for how the parts join.
7
+ */
8
+ export declare const TASKS: string;