ds-bot 0.1.2 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/client.js CHANGED
@@ -3,9 +3,11 @@ window.__ModuleLoader__.load({
3
3
  id: 'ds-bot',
4
4
  factory: (require) => {
5
5
  const module = { exports: {} }
6
+ var __create = Object.create;
6
7
  var __defProp = Object.defineProperty;
7
8
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
8
9
  var __getOwnPropNames = Object.getOwnPropertyNames;
10
+ var __getProtoOf = Object.getPrototypeOf;
9
11
  var __hasOwnProp = Object.prototype.hasOwnProperty;
10
12
  var __export = (target, all) => {
11
13
  for (var name in all)
@@ -19,6 +21,14 @@ var __copyProps = (to, from, except, desc) => {
19
21
  }
20
22
  return to;
21
23
  };
24
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
25
+ // If the importer is in node compatibility mode or this is not an ESM
26
+ // file that has been converted to a CommonJS file using a Babel-
27
+ // compatible transform (i.e. "__esModule" has not been set), then set
28
+ // "default" to the CommonJS "module.exports" for node compatibility.
29
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
30
+ mod
31
+ ));
22
32
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
23
33
 
24
34
  // src/client/index.js
@@ -57,6 +67,9 @@ var ZH = {
57
67
  Close: "关闭",
58
68
  Copy: "复制",
59
69
  Copied: "已复制",
70
+ Content: "内容",
71
+ "Referenced {labels}": "引用了 {labels}",
72
+ ", ": "、",
60
73
  You: "你",
61
74
  you: "你",
62
75
  Bot: "Bot",
@@ -417,6 +430,7 @@ var ZH = {
417
430
  "Replies from": "群聊回复:",
418
431
  "Reply from": "收到回复:",
419
432
  "Message from": "收到消息:",
433
+ "Stopped by": "叫停者:",
420
434
  "{count} messages with": "{count} 条消息,来自",
421
435
  "Routine started": "例行任务开始",
422
436
  Update: "更新",
@@ -443,6 +457,8 @@ var ZH = {
443
457
  "Checking the keys": "查看密钥",
444
458
  "Asking {name}": "询问 {name}",
445
459
  "Asking another Bot": "询问其他 Bot",
460
+ "Stopping {name}": "叫停 {name}",
461
+ "Stopping another Bot": "叫停其他 Bot",
446
462
  "Creating {name}": "创建 {name}",
447
463
  "Updating {name}": "更新 {name}",
448
464
  "Checking the team": "查看团队",
@@ -451,8 +467,19 @@ var ZH = {
451
467
  "{name} looked up its own chat": "{name} 查看了自己的对话",
452
468
  "{name} looked up its own chat for “{text}”": "{name} 在自己的对话里查找「{text}」",
453
469
  "{name} is typing…": "{name} 正在输入…",
470
+ "{name} didn't answer in time": "{name} 没有及时回应",
471
+ "{name} paused for your approval": "{name} 停下来等你批准",
472
+ "{name} is waiting for your approval": "{name} 等你批准",
473
+ "{first} and {second} are waiting for your approval": "{first}、{second} 等你批准",
474
+ "{first}, {second} and {count} more are waiting for your approval": "{first}、{second} 等 {total} 个等你批准",
475
+ "Waiting for your approval": "等你批准",
476
+ "in its own chat": "在自己的对话里",
477
+ Reject: "拒绝",
478
+ "Allow once": "允许一次",
454
479
  "Couldn't message {name}": "没能给 {name} 发消息",
455
480
  Messaged: "已发消息给",
481
+ "Couldn't stop {name}": "没能叫停 {name}",
482
+ Stopped: "已叫停",
456
483
  Created: "已创建",
457
484
  Updated: "已更新",
458
485
  "Created group": "已创建群聊",
@@ -746,6 +773,8 @@ var ZH = {
746
773
  "Its group conversation could not open": "它的群聊会话打不开",
747
774
  // The composer's +
748
775
  "Add files": "添加文件",
776
+ "Reference a chat": "引用对话",
777
+ "Chat with {name}": "与 {name} 的对话",
749
778
  "Scheduled task": "定时任务",
750
779
  "Send to": "发给",
751
780
  "Scheduled task: {text}": "定时任务:{text}",
@@ -813,6 +842,7 @@ var HOST_ERRORS = [
813
842
  [/^Model (.+) is not available\. Available: (.*)$/, "模型 $1 不可用。可用的:$2"],
814
843
  [/^A Bot named (.+) already exists$/, "已经有叫 $1 的 Bot 了"],
815
844
  [/^Name (.*) is not available$/, "名字 $1 不可用"],
845
+ [/^"(.+)" calls everyone in a group, so it cannot be a Bot name$/, "「$1」在群里表示所有人,不能用作 Bot 名字"],
816
846
  [/^Another group chat is already called "(.+)"$/, "已经有叫「$1」的群聊了"],
817
847
  [/^A group chat holds at most (\d+) Bots$/, "一个群聊最多 $1 个 Bot"],
818
848
  [/^Reply mode must be one of (.+)$/, "回复模式只能是 $1"],
@@ -841,6 +871,7 @@ var HOST_ZH = {
841
871
  "Avatar images must be PNG, JPEG, WebP, or GIF": "头像图片必须是 PNG、JPEG、WebP 或 GIF",
842
872
  "Avatar image is too large": "头像图片太大了",
843
873
  "A Bot needs a name": "Bot 需要一个名字",
874
+ "A Bot name cannot contain @, ` or a line break": "Bot 名字里不能有 @、` 或换行",
844
875
  "Unknown Bot": "找不到这个 Bot",
845
876
  "Unknown conversation": "找不到这个对话",
846
877
  "A group chat needs at least two Bots": "群聊至少要两个 Bot",
@@ -959,12 +990,17 @@ function failureLabel(code, message) {
959
990
  var URL_LIMIT = 7e3;
960
991
  var WHAT_MAX = 2e3;
961
992
  var PASTE_NOTE = "DS Bot copied the diagnostics to the clipboard. Paste them here.";
993
+ var REST_NOTE = "The recent errors do not fit in the link. DS Bot copied the diagnostics to the clipboard: paste them below this line.";
962
994
  function issueUrl(base, { what = "", diagnostics }) {
963
995
  const text = what.trim().slice(0, WHAT_MAX);
964
996
  const title = text.split("\n")[0].slice(0, 80);
965
997
  const build = (body) => `${base}?${new URLSearchParams({ template: "bug.yml", ...title ? { title } : {}, ...text ? { what: text } : {}, diagnostics: body })}`;
966
998
  const full2 = build(diagnostics);
967
- return full2.length <= URL_LIMIT ? { url: full2, inline: true } : { url: build(PASTE_NOTE), inline: false };
999
+ if (full2.length <= URL_LIMIT) return { url: full2, inline: true };
1000
+ const facts = build(`${diagnostics.split("\n\n")[0]}
1001
+
1002
+ ${REST_NOTE}`);
1003
+ return { url: facts.length <= URL_LIMIT ? facts : build(PASTE_NOTE), inline: false };
968
1004
  }
969
1005
 
970
1006
  // src/client/api.js
@@ -1113,8 +1149,8 @@ function bodyOf(def, paint, flat = false) {
1113
1149
  return "";
1114
1150
  }
1115
1151
  }
1116
- var eyeMarkup = ([cx, cy, w, h30, tilt]) => `<g transform="translate(${cx} ${cy}) rotate(${tilt})"><rect class="bt-eye" x="${-w / 2}" y="${-h30 / 2}" width="${w}" height="${h30}" rx="${w / 2}"/></g>`;
1117
- var happyEyeMarkup = ([cx, cy, w, h30, tilt]) => `<g transform="translate(${cx} ${cy}) rotate(${tilt})"><path class="bt-eye-happy" opacity="0" d="M${-w * 0.62} ${h30 * 0.14}Q0 ${-h30 * 0.42} ${w * 0.62} ${h30 * 0.14}" fill="none" stroke="#000" stroke-width="${w * 0.5}" stroke-linecap="round"/></g>`;
1152
+ var eyeMarkup = ([cx, cy, w, h32, tilt]) => `<g transform="translate(${cx} ${cy}) rotate(${tilt})"><rect class="bt-eye" x="${-w / 2}" y="${-h32 / 2}" width="${w}" height="${h32}" rx="${w / 2}"/></g>`;
1153
+ var happyEyeMarkup = ([cx, cy, w, h32, tilt]) => `<g transform="translate(${cx} ${cy}) rotate(${tilt})"><path class="bt-eye-happy" opacity="0" d="M${-w * 0.62} ${h32 * 0.14}Q0 ${-h32 * 0.42} ${w * 0.62} ${h32 * 0.14}" fill="none" stroke="#000" stroke-width="${w * 0.5}" stroke-linecap="round"/></g>`;
1118
1154
  var maskBox = (size) => `maskUnits="userSpaceOnUse" x="${-size}" y="${-size}" width="${size * 3}" height="${size * 3}"`;
1119
1155
  var holes = (def, eyes) => `<rect x="${-def.size}" y="${-def.size}" width="${def.size * 3}" height="${def.size * 3}" fill="#fff"/>${eyes}`;
1120
1156
  var MASK_ID = "%MASK%";
@@ -1300,20 +1336,65 @@ function registerTheme(id, theme) {
1300
1336
  }
1301
1337
  var allThemes = () => ({ ...THEMES, ...customThemes });
1302
1338
 
1339
+ // src/shared/mentions.js
1340
+ var ALL_WORDS = ["all", "everyone", "所有人", "全体"];
1341
+ var NAME_GOES_ON = /^(?:[a-z0-9_\-/\\]|\.[a-z0-9_-])/i;
1342
+ var ADDRESS_BEFORE = /[a-z0-9_./]/i;
1343
+ var LITERAL = /```[\s\S]*?(?:```|$)|`[^`\n]*`|\[[^\]\n]*\]\([^)\s]*(?:\s+(?:"[^"\n]*"|'[^'\n]*'))?\)/g;
1344
+ var fitsAt = (text, at, word) => text.slice(at, at + word.length).toLowerCase() === word.toLowerCase() && !NAME_GOES_ON.test(text.slice(at + word.length));
1345
+ var overlaps = (spans, start, end) => spans.some((span) => start < span.end && span.start < end);
1346
+ function nameProblem(name) {
1347
+ const text = String(name ?? "").trim();
1348
+ if (text === "") return "A Bot needs a name";
1349
+ if (/[@`\r\n]/.test(text)) return "A Bot name cannot contain @, ` or a line break";
1350
+ if (ALL_WORDS.includes(text.toLowerCase())) return `"${text}" calls everyone in a group, so it cannot be a Bot name`;
1351
+ return void 0;
1352
+ }
1353
+ function referenceSpans(text, labels = []) {
1354
+ const spans = [];
1355
+ for (const label of [...labels].sort((a, b) => b.length - a.length)) {
1356
+ const mention = `@${label}`;
1357
+ for (let at = text.indexOf(mention); at !== -1; at = text.indexOf(mention, at + 1)) {
1358
+ const end = at + mention.length;
1359
+ if (at > 0 && ADDRESS_BEFORE.test(text[at - 1]) || NAME_GOES_ON.test(text.slice(end)) || overlaps(spans, at, end)) continue;
1360
+ spans.push({ start: at, end, label });
1361
+ break;
1362
+ }
1363
+ }
1364
+ return spans.sort((a, b) => a.start - b.start);
1365
+ }
1366
+ function mentionSpans(text, bots, { references = [] } = {}) {
1367
+ const value = String(text ?? "");
1368
+ const skip = [
1369
+ ...[...value.matchAll(LITERAL)].map((match) => ({ start: match.index, end: match.index + match[0].length })),
1370
+ ...referenceSpans(value, references)
1371
+ ];
1372
+ const byLength = bots.filter((bot) => bot?.name).sort((a, b) => b.name.length - a.name.length);
1373
+ const spans = [];
1374
+ for (let at = value.indexOf("@"); at !== -1; at = value.indexOf("@", at + 1)) {
1375
+ if (at > 0 && ADDRESS_BEFORE.test(value[at - 1]) || overlaps(skip, at, at + 1)) continue;
1376
+ const bot = byLength.find((candidate) => fitsAt(value, at + 1, candidate.name));
1377
+ const all = bot ? void 0 : ALL_WORDS.find((word) => fitsAt(value, at + 1, word));
1378
+ if (!bot && !all) continue;
1379
+ const end = at + 1 + (bot ? bot.name.length : all.length);
1380
+ spans.push(bot ? { start: at, end, bot } : { start: at, end, all: true });
1381
+ at = end - 1;
1382
+ }
1383
+ return spans;
1384
+ }
1385
+
1303
1386
  // src/client/text.js
1304
1387
  var MENTION_ORIGIN = "https://bots.local/";
1305
- var escapeRegExp = (text) => text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1306
1388
  function linkMentions(text, bots, selfId) {
1307
- if (!text || bots.length === 0 || text.includes("```") || !text.includes("@")) return text;
1308
- const names = bots.filter((bot) => bot.name).sort((a, b) => b.name.length - a.name.length);
1309
- if (!names.some((bot) => bot.id !== selfId)) return text;
1310
- const byName = new Map(names.map((bot) => [bot.name.toLowerCase(), bot]));
1311
- const pattern = new RegExp(`(\\[[^\\]]*\\]\\([^)]*\\)|\`[^\`]*\`)|(?<![A-Za-z0-9_.])@(${names.map((bot) => escapeRegExp(bot.name)).join("|")})(?![A-Za-z0-9_-])`, "gi");
1312
- return text.replace(pattern, (match, skip, name) => {
1313
- if (skip) return skip;
1314
- const bot = byName.get(name.toLowerCase());
1315
- return bot && bot.id !== selfId ? `[${name}](${MENTION_ORIGIN}${bot.id})` : match;
1316
- });
1389
+ if (!text || !text.includes("@")) return text;
1390
+ let out = "";
1391
+ let cursor = 0;
1392
+ for (const span of mentionSpans(text, bots)) {
1393
+ if (!span.bot || span.bot.id === selfId) continue;
1394
+ out += `${text.slice(cursor, span.start)}[${text.slice(span.start + 1, span.end).replace(/[[\]\\]/g, "\\$&")}](${MENTION_ORIGIN}${span.bot.id})`;
1395
+ cursor = span.end;
1396
+ }
1397
+ return cursor === 0 ? text : out + text.slice(cursor);
1317
1398
  }
1318
1399
  function mentionTarget(event) {
1319
1400
  const anchor = event.target instanceof Element ? event.target.closest(`a[href^="${MENTION_ORIGIN}"]`) : null;
@@ -1405,6 +1486,30 @@ function toolArgs(root) {
1405
1486
  value: (key) => object()?.[key]
1406
1487
  };
1407
1488
  }
1489
+ var RELAY_ROLES = /* @__PURE__ */ new Set(["request", "reply", "stopped"]);
1490
+ var isRelayContext = (data) => {
1491
+ const source = data?.source;
1492
+ if (source === null || typeof source !== "object" || source.kind !== "bot" || source.handoff === true || source.deferred === true) return false;
1493
+ return RELAY_ROLES.has(source.role) && typeof source.senderSessionId === "string";
1494
+ };
1495
+ var stepShows = (step) => (step?.blocks ?? []).some((block) => {
1496
+ if (block === void 0 || block.kind === "tool-call") return false;
1497
+ return block.kind === "text" || block.kind === "reasoning" ? String(block.text ?? "").trim() !== "" : true;
1498
+ });
1499
+ var stepHasReasoning = (step) => (step?.blocks ?? []).some((block) => block?.kind === "reasoning" && String(block.text ?? "").trim() !== "");
1500
+ var relayPart = (data, groupPart) => groupPart === void 0 || (stepHasReasoning(data) ? groupPart === "reasoning" : groupPart === "response");
1501
+ function relaysBefore(contexts, steps, step) {
1502
+ const index = steps.findIndex((entry) => entry?.step === step?.step);
1503
+ if (index === -1) return [];
1504
+ let prev = -Infinity;
1505
+ for (let cursor = index - 1; cursor >= 0; cursor -= 1) {
1506
+ if (stepShows(steps[cursor])) {
1507
+ prev = steps[cursor].time;
1508
+ break;
1509
+ }
1510
+ }
1511
+ return (contexts ?? []).filter((data) => isRelayContext(data) && typeof data.time === "number" && data.time >= prev && data.time < step.time);
1512
+ }
1408
1513
  function commRunOf(order, peers, turn, hiddenTurns = []) {
1409
1514
  const turns = [];
1410
1515
  for (const entry of order) {
@@ -1489,12 +1594,12 @@ function liveSprings(apply2) {
1489
1594
  const dt = Math.min(0.5, Math.max(1e-3, (now - last) / 1e3));
1490
1595
  last = now;
1491
1596
  const steps = Math.ceil(dt * 240);
1492
- const h30 = dt / steps;
1597
+ const h32 = dt / steps;
1493
1598
  let moving = false;
1494
1599
  for (const value of values.values()) {
1495
1600
  for (let index = 0; index < steps; index += 1) {
1496
- value.v += (-value.k * (value.x - value.to) - value.c * value.v) * h30;
1497
- value.x += value.v * h30;
1601
+ value.v += (-value.k * (value.x - value.to) - value.c * value.v) * h32;
1602
+ value.x += value.v * h32;
1498
1603
  }
1499
1604
  if (Math.abs(value.x - value.to) < 0.05 && Math.abs(value.v) < 1) {
1500
1605
  value.x = value.to;
@@ -1840,39 +1945,52 @@ button.bt-lead{border:0;background:none;padding:0;cursor:pointer}
1840
1945
  .bt-x{border:0;background:none;color:var(--bt-ink-2);cursor:pointer;width:24px;height:24px;border-radius:6px;font-size:16px;line-height:24px;padding:0;transition:background-color .12s ease,color .12s ease,${spring(SPRING_TAP, "scale")}}
1841
1946
  .bt-x:hover{background:var(--bt-hover);color:var(--bt-ink)}
1842
1947
 
1843
- .bt-q{margin:0 auto 8px;width:100%;max-width:var(--dsh-chat-content-width,748px);display:flex;flex-direction:column;gap:10px;background:var(--bt-bubble);border-radius:16px;padding:12px;box-sizing:border-box;color:var(--bt-ink);font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",system-ui,sans-serif;animation:bt-q-in ${SPRING_SHAPE.duration}ms ${SPRING_SHAPE.easing} backwards,bt-veil-in .2s ease-out backwards}
1948
+ .bt-q{margin:0 auto 8px;width:100%;max-width:var(--dsh-chat-content-width,748px);display:flex;flex-direction:column;gap:12px;--bt-q-edge:color-mix(in srgb,var(--bt-ink) 10%,transparent);--bt-q-rule:color-mix(in srgb,var(--bt-ink) 8%,transparent);--bt-q-rule-2:color-mix(in srgb,var(--bt-ink) 5%,transparent);background:var(--bt-card);box-shadow:0 1px 3px rgba(20,20,20,.04),inset 0 0 0 1px var(--bt-q-edge);border-radius:16px;padding:14px 16px;box-sizing:border-box;color:var(--bt-ink);font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",system-ui,sans-serif;animation:bt-q-in ${SPRING_SHAPE.duration}ms ${SPRING_SHAPE.easing} backwards,bt-veil-in .2s ease-out backwards}
1949
+ body[data-ds-dark-theme] .bt-q{--bt-q-edge:rgba(255,255,255,.16);--bt-q-rule:rgba(255,255,255,.12);--bt-q-rule-2:rgba(255,255,255,.08)}
1844
1950
  @keyframes bt-q-in{from{transform:translateY(10px) scale(.97)}}
1845
1951
  .bt-q-settled{animation:none}
1846
1952
  .bt-q-head{display:flex;align-items:flex-start;gap:8px;min-width:0}
1847
1953
  .bt-q-text{flex:1 1 0;min-width:0;display:flex;flex-direction:column}
1848
- .bt-q-title{margin:0;font-size:14px;line-height:20px;font-weight:500;overflow-wrap:anywhere}
1954
+ .bt-q-title{margin:0;font-size:15px;line-height:22px;font-weight:600;overflow-wrap:anywhere}
1849
1955
  .bt-q-detail{font-size:13px;line-height:18px;color:var(--bt-ink-2)}
1850
1956
  .bt-q-x{flex:none;width:20px;height:20px;border:0;padding:0;border-radius:6px;background:none;color:var(--bt-ink-2);display:inline-flex;align-items:center;justify-content:center;cursor:pointer;transition:background-color .12s ease,color .12s ease,${spring(SPRING_TAP, "scale")}}
1851
1957
  .bt-q-x svg{width:14px;height:14px}
1852
1958
  .bt-q-x:hover{background:var(--bt-hover);color:var(--bt-ink)}
1853
1959
  .bt-q-x:focus-visible,.bt-q-opt:focus-visible{outline:2px solid var(--bt-accent);outline-offset:-2px}
1854
- .bt-q-list{display:flex;flex-direction:column;width:100%;min-width:0;box-sizing:border-box;background:var(--bt-main);border:.5px solid var(--bt-line-weak);border-radius:8px;overflow:hidden}
1855
- .bt-q-opt{display:flex;align-items:center;gap:8px;width:100%;min-width:0;box-sizing:border-box;margin:0;border:0;border-radius:0;background:transparent;padding:9px 8px;font:inherit;color:var(--bt-ink);text-align:start;cursor:pointer;transition:background-color .12s ease}
1856
- .bt-q-opt+.bt-q-opt{border-top:.5px solid var(--bt-line-weak)}
1857
- .bt-q-opt:first-child{border-top-left-radius:7.5px;border-top-right-radius:7.5px}
1858
- .bt-q-opt:last-child{border-bottom-left-radius:7.5px;border-bottom-right-radius:7.5px}
1960
+ .bt-q-list{display:flex;flex-direction:column;gap:1px;min-width:0;box-sizing:border-box;margin:0 -8px}
1961
+ .bt-q-opt{position:relative;display:flex;align-items:flex-start;gap:16px;width:100%;min-width:0;box-sizing:border-box;margin:0;border:0;border-radius:12px;background:transparent;padding:10px 8px;font:inherit;color:var(--bt-ink);text-align:start;cursor:pointer;transition:background-color .12s ease}
1962
+ .bt-q:not(.bt-q-raised)>.bt-q-head:has(+.bt-q-list){padding-bottom:12px;border-bottom:1px solid var(--bt-q-rule)}
1963
+ .bt-q:not(.bt-q-raised)>.bt-q-head+.bt-q-list{margin-top:-6px}
1964
+ .bt-q-opt+.bt-q-opt::before{content:"";position:absolute;left:calc(var(--bt-key,24px) + 24px);right:8px;top:-1px;height:1px;background:var(--bt-q-rule-2);transition:opacity .12s ease;pointer-events:none}
1965
+ button.bt-q-opt:hover::before,button.bt-q-opt:hover+.bt-q-opt::before,.bt-q-opt[aria-pressed=true]::before,.bt-q-opt[aria-pressed=true]+.bt-q-opt::before{opacity:0}
1966
+ .bt-q-raised .bt-q-opt::before{display:none}
1859
1967
  button.bt-q-opt:hover{background:var(--bt-hover)}
1860
1968
  button.bt-q-opt:active{background:var(--bt-active)}
1861
1969
  .bt-q-opt[aria-pressed=true]{background:var(--bt-active)}
1862
- .bt-key{flex:none;box-sizing:border-box;min-width:18px;height:18px;padding:1px 4px;border-radius:4px;border:.5px solid color-mix(in srgb,var(--bt-ink) 5%,transparent);background:var(--bt-active);color:color-mix(in srgb,var(--bt-ink) 61%,transparent);font-size:11px;line-height:15px;text-align:center;font-variant-numeric:tabular-nums}
1863
- .bt-q-body{flex:1 1 auto;min-width:0;display:flex;flex-direction:column}
1864
- .bt-q-label{font-size:14px;line-height:20px;white-space:normal;overflow-wrap:anywhere}
1865
- .bt-q-desc{font-size:13px;line-height:18px;color:var(--bt-ink-2);overflow-wrap:anywhere}
1970
+ .bt-q:not(.bt-q-raised) :is(button.bt-q-opt:hover,.bt-q-opt[aria-pressed=true]) .bt-key{background:var(--bt-card);color:var(--bt-ink)}
1971
+ .bt-key{flex:none;box-sizing:border-box;width:var(--bt-key,24px);height:var(--bt-key,24px);border-radius:8px;background:var(--bt-hover);color:var(--bt-ink-2);font-size:12px;line-height:var(--bt-key,24px);font-weight:500;text-align:center;font-variant-numeric:tabular-nums;transition:background-color .12s ease,color .12s ease}
1972
+ .bt-q-body{flex:1 1 auto;min-width:0;display:flex;flex-direction:column;gap:1px;padding-top:2px}
1973
+ /* With a description the key spans the two text lines less 2px above and below. */
1974
+ .bt-q-opt:has(.bt-q-desc){--bt-key:34px}
1975
+ .bt-q-opt:has(.bt-q-desc) .bt-key{margin-top:2px;border-radius:10px;font-size:13px}
1976
+ .bt-q-opt:has(.bt-q-desc) .bt-q-body{padding-top:0}
1977
+ .bt-q-label{font-size:14px;line-height:20px;font-weight:500;white-space:normal;overflow-wrap:anywhere}
1978
+ .bt-q-desc{font-size:12px;line-height:17px;color:var(--bt-ink-2);overflow-wrap:anywhere}
1866
1979
  .bt-q-check{flex:none;display:inline-flex;align-items:center;color:var(--bt-ink-2);animation:bt-check-pop ${SPRING_POP.duration}ms ${SPRING_POP.easing} backwards}
1867
1980
  .bt-q-settled .bt-q-check{animation:none}
1868
1981
  .bt-q-check svg{width:16px;height:16px}
1869
1982
  .bt-q-settled .bt-key{opacity:.4}
1870
1983
  .bt-q-settled .bt-q-label{color:color-mix(in srgb,var(--bt-ink) 45%,transparent)}
1871
1984
  .bt-q-dismissed .bt-q-title{color:var(--bt-ink-2)}
1985
+ .bt-q.bt-q-settled{gap:6px;padding:12px 16px}
1986
+ .bt-q-settled .bt-q-title{font-size:14px;line-height:20px;font-weight:500}
1987
+ .bt-q.bt-q-settled>.bt-q-head:has(+.bt-q-list){padding-bottom:0;border-bottom:0}
1988
+ .bt-q.bt-q-settled>.bt-q-head+.bt-q-list{margin-top:0}
1989
+ .bt-q-settled .bt-q-opt{padding:4px 8px}
1872
1990
  .bt-q-badge{flex:none;height:20px;padding:0 7px;border-radius:999px;border:1px dotted var(--bt-line-2);color:var(--bt-ink-2);font-size:12px;line-height:18px;box-sizing:border-box}
1873
1991
  .bt-q-custom{display:flex;align-items:flex-start;gap:8px;width:100%;min-width:0}
1874
- .bt-q-field{flex:1 1 0;min-width:0;display:flex;box-sizing:border-box;min-height:32px;padding:5px 11px;border-radius:8px;border:.5px solid var(--bt-line-weak);background:var(--bt-main);cursor:text;transition:border-color .12s ease}
1875
- .bt-q-field:focus-within{border-color:var(--bt-line-2)}
1992
+ .bt-q-field{flex:1 1 0;min-width:0;display:flex;box-sizing:border-box;min-height:32px;padding:5px 11px;border-radius:8px;border:.5px solid var(--bt-line-2);background:none;cursor:text;transition:border-color .12s ease}
1993
+ .bt-q-field:focus-within{border-color:color-mix(in srgb,var(--bt-ink) 30%,transparent)}
1876
1994
  .bt-q-input{flex:1;width:100%;min-width:0;max-height:120px;margin:0;padding:0;border:0;outline:none;background:transparent;color:var(--bt-ink);font:inherit;font-size:14px;line-height:20px;resize:none;overflow-y:hidden;white-space:pre-wrap;overflow-wrap:anywhere}
1877
1995
  .bt-q-input:not(:placeholder-shown){field-sizing:content}
1878
1996
  .bt-q-input::placeholder{color:var(--bt-ink-3)}
@@ -1884,16 +2002,14 @@ button.bt-q-opt:active{background:var(--bt-active)}
1884
2002
  .bt-q-btn{background:var(--bt-hover);color:var(--bt-ink)}
1885
2003
  .bt-q-btn:hover{background:var(--bt-active)}
1886
2004
  .bt-q-foot{display:flex;justify-content:flex-end;gap:8px}
1887
- .bt-q-raised{width:calc(var(--dsh-chat-content-width,748px) - 32px);max-width:calc(100% - 32px);gap:0;padding:0;background:var(--bt-main);box-shadow:0 2px 8px rgba(20,20,20,.035),inset 0 0 0 .5px color-mix(in srgb,var(--bt-ink) 5%,transparent)}
2005
+ .bt-q-raised{width:calc(var(--dsh-chat-content-width,748px) - 32px);max-width:calc(100% - 32px);gap:0;padding:0;box-shadow:0 2px 8px rgba(20,20,20,.035),inset 0 0 0 .5px color-mix(in srgb,var(--bt-ink) 5%,transparent)}
1888
2006
  .bt-q-raised .bt-q-head{padding:14px 16px 4px 20px}
1889
2007
  .bt-q-raised .bt-q-head:last-child{padding-bottom:16px}
1890
- .bt-q-raised .bt-q-title{font-size:15px;line-height:22px;font-weight:600}
1891
- .bt-q-raised .bt-q-list{gap:1px;padding:8px;border:0;border-radius:0;background:none;overflow:visible}
1892
- .bt-q-raised .bt-q-opt{align-items:flex-start;gap:4px;padding:7px;border:0;border-radius:16px;color:var(--bt-ink-2)}
2008
+ .bt-q-raised .bt-q-list{gap:1px;margin:0;padding:8px}
2009
+ .bt-q-raised .bt-q-opt{gap:4px;padding:7px;border-radius:16px;color:var(--bt-ink-2)}
1893
2010
  .bt-q-raised button.bt-q-opt:hover{color:var(--bt-ink)}
1894
2011
  .bt-q-raised .bt-q-opt[aria-pressed=true]{background:transparent;color:var(--bt-ink)}
1895
2012
  .bt-q-raised .bt-q-opt[aria-pressed=true]:hover{background:var(--bt-hover)}
1896
- .bt-q-raised .bt-key{width:24px;min-width:24px;height:24px;padding:0;border:0;border-radius:8px;background:var(--bt-hover);color:var(--bt-ink-2);font-size:12px;line-height:24px;font-weight:500;transition:background-color .12s ease,color .12s ease}
1897
2013
  .bt-q-raised .bt-q-opt[aria-pressed=true] .bt-key,.bt-q-ownrow:focus-within .bt-key,.bt-q-ownrow[data-filled] .bt-key{background:var(--bt-user);color:var(--bt-user-ink)}
1898
2014
  .bt-q-raised .bt-q-body{padding:2px 6px 0}
1899
2015
  .bt-q-raised .bt-q-desc{color:color-mix(in srgb,var(--bt-ink) 61%,transparent)}
@@ -1957,6 +2073,7 @@ button.bt-q-opt:active{background:var(--bt-active)}
1957
2073
  @keyframes bt-sheet-out{to{opacity:0;transform:translateY(16px);filter:blur(4px)}}
1958
2074
  .bt-exchange .bt-dialog{position:relative}
1959
2075
  .bt-exchange .bt-dialog-log{max-width:none;margin:0;padding:39px max(16px,calc((100% - 960px)/2)) 76px;scrollbar-width:thin}
2076
+ .bt-exchange .bt-tools{display:none}
1960
2077
  .bt-exchange .bt-dialog-foot{position:absolute;left:0;right:0;bottom:0;height:76px;box-sizing:border-box;align-items:flex-end;padding:0 0 28px;background:var(--bt-main);z-index:3;animation:bt-foot-in .3s cubic-bezier(.22,1,.36,1) .3s both}
1961
2078
  .bt-exchange .bt-dialog-foot::before{content:"";position:absolute;left:0;right:0;bottom:100%;height:40px;background:linear-gradient(to top,var(--bt-main),transparent);pointer-events:none}
1962
2079
  .bt-exchange[data-leaving] .bt-dialog-foot{animation:bt-foot-out .22s cubic-bezier(.4,0,1,1) forwards}
@@ -2422,6 +2539,11 @@ var CONVERSATION_CSS = `
2422
2539
  .bt-event button{border:0;background:none;color:inherit;font:inherit;display:inline-flex;align-items:center;gap:4px;cursor:pointer;height:24px;padding:4px 6px 4px 4px;border-radius:999px;box-sizing:border-box;transition:background-color .12s ease}
2423
2540
  .bt-event button:hover{background:var(--bt-hover)}
2424
2541
  .bt-event button:active{background:var(--bt-active)}
2542
+ .bt-event button.bt-event-whole{min-width:0;max-width:100%;padding:4px 6px 4px 8px}
2543
+ .bt-event-whole>*{flex:none}
2544
+ .bt-event-whole>span:first-child{margin-right:2px}
2545
+ /* One button cannot wrap like separate parts, so a long name is cut to fit the chat. */
2546
+ .bt-event-whole>.bt-event-name{flex:0 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
2425
2547
  .bt-time-sep{display:flex;justify-content:center;align-items:center;height:28px;margin:14px 0 8px;font-size:12px;line-height:16px;color:var(--bt-ink-2);white-space:nowrap}
2426
2548
  [class*="_scroll"]>[class*="_column"]::before{content:var(--bt-first-sep,none);display:block;flex:none;height:28px;margin:17px 0 12px;font-size:12px;line-height:28px;text-align:center;color:var(--bt-ink-2);white-space:nowrap}
2427
2549
  .bt-activity{display:flex;align-items:center;gap:10px;margin:8px 0 4px 2px;font-size:14px;color:var(--bt-ink-2)}
@@ -2664,7 +2786,7 @@ function installCompiledStyles(text, tag) {
2664
2786
  var import_react2 = require("react");
2665
2787
  var STATES = ["idle", "thinking", "searching", "working", "sending", "orbit", "alert", "done"];
2666
2788
  var botState = (running, activity, alert) => running ? STATES.includes(activity) ? activity : "working" : alert ? "alert" : "idle";
2667
- var toolState = (name) => /^(message_bot|create_bot|update_bot|create_group|update_group|delete_group|post_to_group)$/.test(name) ? "sending" : /search|fetch|browse|read|grep|glob|list|recall/i.test(name) ? "searching" : "working";
2789
+ var toolState = (name) => /^(message_bot|stop_bot|create_bot|update_bot|create_group|update_group|delete_group|post_to_group)$/.test(name) ? "sending" : /search|fetch|browse|read|grep|glob|list|recall/i.test(name) ? "searching" : "working";
2668
2790
  function useGaze(ref, enabled) {
2669
2791
  (0, import_react2.useEffect)(() => {
2670
2792
  if (!enabled) return void 0;
@@ -3697,25 +3819,54 @@ function ThinkingRow({ text }) {
3697
3819
  (0, import_react6.createElement)("div", { className: "bt-think-text" }, text)
3698
3820
  );
3699
3821
  }
3700
- function AssistantCell({ node, groupPart, sessionId, useRoster, useUi, useTurnSteps, actions }) {
3822
+ function AssistantCell({ node, groupPart, sessionId, useRoster, useUi, useTurnSteps, useTurnContext, actions }) {
3701
3823
  const roster = useRoster((value) => value);
3702
3824
  const data = node.data;
3703
3825
  const steps = useTurnSteps(String(data.turn));
3826
+ const contexts = useTurnContext(String(data.turn));
3704
3827
  const hidden = Boolean(roster.roomsById[sessionId]) || isHiddenTurn(roster, sessionId, data.turn);
3705
3828
  const texts2 = groupPart === "reasoning" ? [] : data.blocks.filter((block) => block.kind === "text" && block.text.trim() !== "").map((block) => block.text);
3706
3829
  const running = data.status === "running";
3707
3830
  useLiveNote(actions, sessionId, `text:${node.key ?? data.seq}`, !hidden && running && texts2.length > 0 ? { text: true } : null);
3708
3831
  if (hidden) return null;
3709
3832
  const reasoning = groupPart === "response" ? "" : turnReasoning(steps, data);
3710
- if (texts2.length === 0 && !reasoning) return null;
3833
+ const relays = relayPart(data, groupPart) ? relaysBefore(contexts, steps, data) : [];
3834
+ if (texts2.length === 0 && !reasoning && relays.length === 0) return null;
3835
+ const selfId = roster.byId[sessionId]?.id ?? sessionId;
3711
3836
  return (0, import_react6.createElement)(
3712
3837
  import_react6.Fragment,
3713
3838
  null,
3839
+ relays.map((relay) => {
3840
+ const source = relay.source;
3841
+ const sender = roster.byId[source.senderSessionId] ?? { id: source.senderSessionId, name: source.senderName ?? t("a Bot"), color: "gray" };
3842
+ return (0, import_react6.createElement)(EventLine, {
3843
+ key: relay.seq,
3844
+ prefix: source.role === "stopped" ? t("Stopped by") : source.role === "reply" ? t("Reply from") : t("Message from"),
3845
+ bots: [sender],
3846
+ onOpen: () => {
3847
+ if (sender.id) actions.openExchange(selfId, sender.id);
3848
+ },
3849
+ whole: true
3850
+ });
3851
+ }),
3714
3852
  reasoning ? (0, import_react6.createElement)(ThinkingRow, { text: reasoning }) : null,
3715
- texts2.length ? (0, import_react6.createElement)(Bubbles, { texts: texts2, roster, selfId: roster.byId[sessionId]?.id ?? sessionId, actions, streaming: running, sessionId, msgKey: `${sessionId}:${data.turn}:${node.key ?? data.seq ?? ""}`, useUi }) : null
3853
+ texts2.length ? (0, import_react6.createElement)(Bubbles, { texts: texts2, roster, selfId, actions, streaming: running, sessionId, msgKey: `${sessionId}:${data.turn}:${node.key ?? data.seq ?? ""}`, useUi }) : null
3716
3854
  );
3717
3855
  }
3718
- function EventLine({ prefix, bots, joiner = t("and"), onOpen }) {
3856
+ function EventLine({ prefix, bots, joiner = t("and"), onOpen, whole = false }) {
3857
+ if (whole && bots.length === 1) {
3858
+ return (0, import_react6.createElement)(
3859
+ "div",
3860
+ { className: "bt-event" },
3861
+ (0, import_react6.createElement)(
3862
+ "button",
3863
+ { type: "button", className: "bt-event-whole", title: bots[0].name, onClick: () => onOpen?.(bots[0]) },
3864
+ (0, import_react6.createElement)("span", null, prefix),
3865
+ (0, import_react6.createElement)(BotMark, { bot: bots[0], size: 16 }),
3866
+ (0, import_react6.createElement)("span", { className: "bt-event-name" }, bots[0].name)
3867
+ )
3868
+ );
3869
+ }
3719
3870
  const parts = [];
3720
3871
  parts.push((0, import_react6.createElement)("span", { key: "p" }, prefix));
3721
3872
  bots.forEach((bot, index) => {
@@ -3854,9 +4005,10 @@ function EarlierParts({ sessionId, roster, actions }) {
3854
4005
  case "event": {
3855
4006
  if (row.role === "report") return (0, import_react6.createElement)(GroupEvent, { key, prefix: t("Replies from"), room: roster.roomsById[row.roomId], name: t("a group chat"), roster, actions });
3856
4007
  const other = roster.byId[row.botId] ?? { id: row.botId, name: row.name ?? t("a Bot"), color: "gray" };
3857
- return (0, import_react6.createElement)(EventLine, { key, prefix: row.role === "reply" ? t("Reply from") : t("Message from"), bots: [other], onOpen: () => {
4008
+ const prefix = { stopped: t("Stopped by"), reply: t("Reply from") }[row.role] ?? t("Message from");
4009
+ return (0, import_react6.createElement)(EventLine, { key, prefix, bots: [other], onOpen: () => {
3858
4010
  if (selfId && other.id) actions.openExchange(selfId, other.id);
3859
- } });
4011
+ }, whole: true });
3860
4012
  }
3861
4013
  default:
3862
4014
  return null;
@@ -3870,7 +4022,8 @@ function TriggerCell({ node, sessionId, useRoster, useSessions, useTurnClock, us
3870
4022
  const data = node.data;
3871
4023
  const source = data.source !== null && typeof data.source === "object" ? data.source : {};
3872
4024
  const selfId = roster.byId[sessionId]?.id ?? sessionId;
3873
- const relayed = source.kind === "bot" && source.handoff !== true && (source.role === "request" || source.role === "reply") && typeof source.senderSessionId === "string";
4025
+ const deferred = source.kind === "bot" && source.deferred === true;
4026
+ const relayed = source.kind === "bot" && source.handoff !== true && !deferred && (source.role === "request" || source.role === "reply" || source.role === "stopped") && typeof source.senderSessionId === "string";
3874
4027
  const turn = node.location?.turn?.turn ?? data.turn;
3875
4028
  (0, import_react6.useLayoutEffect)(() => {
3876
4029
  if (relayed) actions.noteTurnPeer(sessionId, turn, source.senderSessionId);
@@ -3896,8 +4049,9 @@ function TriggerCell({ node, sessionId, useRoster, useSessions, useTurnClock, us
3896
4049
  if (source.kind === "bot") {
3897
4050
  if (source.handoff === true) return roster.roomsById[sessionId] ? null : (0, import_react6.createElement)(EarlierParts, { sessionId, roster, actions });
3898
4051
  if (source.role === "kickoff" || source.role === "room") return null;
3899
- if (source.role === "secret") return null;
4052
+ if (source.role === "secret" || deferred) return null;
3900
4053
  if (source.role === "post") {
4054
+ if (source.failed !== void 0) return null;
3901
4055
  const room2 = roster.roomsById[sessionId];
3902
4056
  const poster = roster.byId[source.senderSessionId] ?? { id: source.senderSessionId, name: source.senderName ?? t("a Bot"), color: "gray" };
3903
4057
  return (0, import_react6.createElement)(GroupMessage, { bot: poster, text: stripHeader(contentText(data.content)), room: room2, roster, actions, msgKey: `${sessionId}:${data.turn}:post`, useUi });
@@ -3919,9 +4073,10 @@ function TriggerCell({ node, sessionId, useRoster, useSessions, useTurnClock, us
3919
4073
  if (!bots.some((bot) => bot.id === peer.id)) bots.push(peer);
3920
4074
  }
3921
4075
  return (0, import_react6.createElement)(EventLine, {
3922
- prefix: count > 1 ? t("{count} messages with", { count }) : t("Message from"),
4076
+ prefix: source.role === "stopped" ? t("Stopped by") : count > 1 ? t("{count} messages with", { count }) : t("Message from"),
3923
4077
  bots,
3924
- onOpen: (bot) => actions.openExchange(selfId, bot.id)
4078
+ onOpen: (bot) => actions.openExchange(selfId, bot.id),
4079
+ whole: true
3925
4080
  });
3926
4081
  }
3927
4082
  if (source.kind === "schedule") {
@@ -3961,11 +4116,11 @@ function clockLabel(time, now = Date.now()) {
3961
4116
  const date = new Date(time);
3962
4117
  const midnight = (value) => new Date(value.getFullYear(), value.getMonth(), value.getDate()).getTime();
3963
4118
  const days = Math.round((midnight(new Date(now)) - midnight(date)) / 864e5);
3964
- const clock3 = date.toLocaleTimeString(dateLocale(), { hour: "numeric", minute: "2-digit" });
3965
- if (days === 0) return t("Today {time}", { time: clock3 });
3966
- if (days === 1) return t("Yesterday {time}", { time: clock3 });
4119
+ const clock4 = date.toLocaleTimeString(dateLocale(), { hour: "numeric", minute: "2-digit" });
4120
+ if (days === 0) return t("Today {time}", { time: clock4 });
4121
+ if (days === 1) return t("Yesterday {time}", { time: clock4 });
3967
4122
  const sameYear = date.getFullYear() === new Date(now).getFullYear();
3968
- return `${date.toLocaleDateString(dateLocale(), { weekday: "short", month: "short", day: "numeric", ...sameYear ? {} : { year: "numeric" } })} ${clock3}`;
4123
+ return `${date.toLocaleDateString(dateLocale(), { weekday: "short", month: "short", day: "numeric", ...sameYear ? {} : { year: "numeric" } })} ${clock4}`;
3969
4124
  }
3970
4125
  function nextTurnStart(turns, turn) {
3971
4126
  let next;
@@ -4025,7 +4180,7 @@ var TOOL_PHRASES = [
4025
4180
  [/image|photo|draw/i, "Working on an image"]
4026
4181
  ];
4027
4182
  var GROUP_PHRASES = { create_group: "Creating a group", update_group: "Updating the group", delete_group: "Deleting the group", post_to_group: "Posting in the group" };
4028
- function toolActivity(name, arg, findBot) {
4183
+ function toolActivity(name, arg, findBot, self2) {
4029
4184
  switch (name) {
4030
4185
  case "ask_user":
4031
4186
  return null;
@@ -4041,10 +4196,14 @@ function toolActivity(name, arg, findBot) {
4041
4196
  const target = findBot(arg("to"));
4042
4197
  return { label: target ? t("Asking {name}", { name: target.name }) : t("Asking another Bot"), state: "sending" };
4043
4198
  }
4199
+ case "stop_bot": {
4200
+ const target = findBot(arg("bot"));
4201
+ return { label: target ? t("Stopping {name}", { name: target.name }) : t("Stopping another Bot"), state: "sending" };
4202
+ }
4044
4203
  case "create_bot":
4045
4204
  return { label: t("Creating {name}", { name: arg("name") ?? t("a Bot") }), state: "sending" };
4046
4205
  case "update_bot": {
4047
- const target = findBot(arg("name")) ?? findBot(arg("bot"));
4206
+ const target = findBot(arg("name")) ?? findBot(arg("bot")) ?? (arg("bot") ? void 0 : self2);
4048
4207
  return { label: t("Updating {name}", { name: target?.name ?? t("a Bot") }), state: "sending" };
4049
4208
  }
4050
4209
  case "list_bots":
@@ -4065,6 +4224,10 @@ function toolActivity(name, arg, findBot) {
4065
4224
  return { label: phrase ? t(phrase) : null, state: toolState(name) };
4066
4225
  }
4067
4226
  }
4227
+ function workPhrase(tool) {
4228
+ if (!tool) return t("Thinking about it");
4229
+ return toolActivity(tool, () => void 0, () => void 0)?.label ?? t("On it");
4230
+ }
4068
4231
  function useLiveNote(actions, sessionId, id, entry) {
4069
4232
  const key = entry ? JSON.stringify(entry) : "";
4070
4233
  (0, import_react6.useEffect)(() => {
@@ -4073,7 +4236,7 @@ function useLiveNote(actions, sessionId, id, entry) {
4073
4236
  return () => actions.noteLive(sessionId, id, null);
4074
4237
  }, [sessionId, id, key]);
4075
4238
  }
4076
- function GroupReplies({ sessionId, callId, done, content, roster, actions, useUi }) {
4239
+ function GroupReplies({ sessionId, callId, done, content, roster, actions, useUi, useWork }) {
4077
4240
  const [live, setLive] = (0, import_react6.useState)({ replies: [], speaking: null });
4078
4241
  (0, import_react6.useEffect)(() => {
4079
4242
  if (done) return void 0;
@@ -4106,6 +4269,7 @@ function GroupReplies({ sessionId, callId, done, content, roster, actions, useUi
4106
4269
  }
4107
4270
  }
4108
4271
  const pending = done || !live.speaking ? [] : [live.speaking];
4272
+ const speakerTool = useWork?.((work) => work.bots?.[live.speaking]?.tool) ?? void 0;
4109
4273
  if (replies.length === 0 && pending.length === 0) return null;
4110
4274
  const room2 = roster.roomsById[sessionId];
4111
4275
  return (0, import_react6.createElement)(
@@ -4113,6 +4277,13 @@ function GroupReplies({ sessionId, callId, done, content, roster, actions, useUi
4113
4277
  { className: "bt-group-replies" },
4114
4278
  replies.map((reply, index) => {
4115
4279
  const bot = roster.byId[reply.botId] ?? { id: reply.botId, name: reply.name, color: reply.color ?? "gray" };
4280
+ if (reply.kind === "user") {
4281
+ return (0, import_react6.createElement)("div", { key: `user-${index}`, className: "bt-urow" }, (0, import_react6.createElement)(
4282
+ "div",
4283
+ { className: "bt-ububble" },
4284
+ (0, import_react6.createElement)(import_dsh_client_ui_primitives.MarkdownText, { text: reply.text, labels: mdLabels(), variant: "compact" })
4285
+ ));
4286
+ }
4116
4287
  if (reply.kind === "failed") {
4117
4288
  return (0, import_react6.createElement)(
4118
4289
  "div",
@@ -4127,6 +4298,21 @@ function GroupReplies({ sessionId, callId, done, content, roster, actions, useUi
4127
4298
  reply.text ? t("{name} looked up its own chat for “{text}”", { name: bot.name, text: reply.text }) : t("{name} looked up its own chat", { name: bot.name })
4128
4299
  );
4129
4300
  }
4301
+ if (reply.kind === "missed") {
4302
+ return (0, import_react6.createElement)("div", { key: `${reply.botId}-${index}`, className: "bt-event" }, t("{name} didn't answer in time", { name: bot.name }));
4303
+ }
4304
+ if (reply.kind === "approval") {
4305
+ return (0, import_react6.createElement)(
4306
+ "div",
4307
+ { key: `${reply.botId}-${index}`, className: "bt-event" },
4308
+ (0, import_react6.createElement)(
4309
+ "button",
4310
+ { type: "button", className: "bt-event-whole", onClick: () => actions.showDetails(sessionId) },
4311
+ (0, import_react6.createElement)(BotMark, { bot, size: 16 }),
4312
+ (0, import_react6.createElement)("span", { className: "bt-event-name" }, t("{name} paused for your approval", { name: bot.name }))
4313
+ )
4314
+ );
4315
+ }
4130
4316
  return (0, import_react6.createElement)(GroupMessage, { key: `${reply.botId}-${index}`, bot, text: reply.text, room: room2, roster, actions, msgKey: callId ? `${sessionId}:${callId}:${index}` : void 0, useUi });
4131
4317
  }),
4132
4318
  pending.map((id) => {
@@ -4135,13 +4321,13 @@ function GroupReplies({ sessionId, callId, done, content, roster, actions, useUi
4135
4321
  return (0, import_react6.createElement)(
4136
4322
  "div",
4137
4323
  { key: `pending-${id}`, className: "bt-activity" },
4138
- (0, import_react6.createElement)(BotMark, { bot, size: 22, state: "thinking" }),
4139
- (0, import_react6.createElement)(Shimmer, null, t("{name} is typing…", { name: bot.name }))
4324
+ (0, import_react6.createElement)(BotMark, { bot, size: 22, state: speakerTool ? toolState(speakerTool) : "thinking" }),
4325
+ (0, import_react6.createElement)(Shimmer, null, speakerTool ? `${bot.name} · ${workPhrase(speakerTool)}…` : t("{name} is typing…", { name: bot.name }))
4140
4326
  );
4141
4327
  })
4142
4328
  );
4143
4329
  }
4144
- function ToolCell({ node, sessionId, useRoster, useUi, actions }) {
4330
+ function ToolCell({ node, sessionId, useRoster, useUi, useWork, actions }) {
4145
4331
  const roster = useRoster((value) => value);
4146
4332
  const root = node.data.root;
4147
4333
  const done = root.kind === "tool-result";
@@ -4150,13 +4336,18 @@ function ToolCell({ node, sessionId, useRoster, useUi, actions }) {
4150
4336
  const tool = toolName(root);
4151
4337
  const { text: arg, value: argValue } = toolArgs(root);
4152
4338
  const findBot = (nameOrId) => roster.byId[nameOrId] ?? roster.bots.find((bot) => bot.name.toLowerCase() === String(nameOrId ?? "").toLowerCase());
4153
- useLiveNote(actions, sessionId, `tool:${root.callId ?? node.key}`, done || hidden ? null : toolActivity(tool, arg, findBot));
4339
+ useLiveNote(actions, sessionId, `tool:${root.callId ?? node.key}`, done || hidden ? null : toolActivity(tool, arg, findBot, roster.byId[sessionId]));
4154
4340
  if (hidden) return null;
4155
4341
  switch (tool) {
4156
4342
  case "message_bot": {
4157
4343
  const target = findBot(arg("to")) ?? { name: arg("to") ?? "…", color: "gray" };
4158
4344
  if (done && root.isError) return (0, import_react6.createElement)("div", { className: "bt-event" }, t("Couldn't message {name}", { name: target.name }));
4159
- return (0, import_react6.createElement)(EventLine, { prefix: t("Messaged"), bots: [target], onOpen: (bot) => bot.id && actions.openExchange(self?.id ?? sessionId, bot.id) });
4345
+ return (0, import_react6.createElement)(EventLine, { prefix: t("Messaged"), bots: [target], onOpen: (bot) => bot.id && actions.openExchange(self?.id ?? sessionId, bot.id), whole: true });
4346
+ }
4347
+ case "stop_bot": {
4348
+ const target = findBot(arg("bot")) ?? { name: arg("bot") ?? "…", color: "gray" };
4349
+ if (done && root.isError) return (0, import_react6.createElement)("div", { className: "bt-event" }, t("Couldn't stop {name}", { name: target.name }));
4350
+ return (0, import_react6.createElement)(EventLine, { prefix: t("Stopped"), bots: [target], onOpen: (bot) => bot.id && actions.openExchange(roster.byId[sessionId]?.id ?? sessionId, bot.id), whole: true });
4160
4351
  }
4161
4352
  case "create_bot": {
4162
4353
  const created = findBot(arg("name"));
@@ -4164,11 +4355,11 @@ function ToolCell({ node, sessionId, useRoster, useUi, actions }) {
4164
4355
  return (0, import_react6.createElement)(EventLine, { prefix: t("Created"), bots: [created], onOpen: (bot) => actions.openSession(bot.id) });
4165
4356
  }
4166
4357
  case "update_bot": {
4167
- const target = findBot(arg("name")) ?? findBot(arg("bot"));
4358
+ const target = findBot(arg("name")) ?? findBot(arg("bot")) ?? (arg("bot") ? void 0 : roster.byId[sessionId]);
4168
4359
  return done && target ? (0, import_react6.createElement)(EventLine, { prefix: t("Updated"), bots: [target], onOpen: (bot) => actions.openSession(bot.id) }) : null;
4169
4360
  }
4170
4361
  case "group_relay":
4171
- return (0, import_react6.createElement)(GroupReplies, { sessionId, callId: root.callId, done, content: root.content, roster, actions, useUi });
4362
+ return (0, import_react6.createElement)(GroupReplies, { sessionId, callId: root.callId, done, content: root.content, roster, actions, useUi, useWork });
4172
4363
  case "create_group":
4173
4364
  case "update_group":
4174
4365
  case "delete_group":
@@ -4303,10 +4494,11 @@ function useRowEntrances(replacing) {
4303
4494
  };
4304
4495
  }, []);
4305
4496
  }
4306
- function ActivityIndicator({ sessionId, useRoster, useSessionStatus, useLive, actions }) {
4497
+ function ActivityIndicator({ sessionId, useRoster, useSessionStatus, useLive, useSession, actions }) {
4307
4498
  const roster = useRoster((value) => value);
4308
4499
  const running = useSessionStatus((map) => map.get(sessionId)?.running === true);
4309
4500
  const liveKey = useLive((state) => JSON.stringify(state[sessionId] ?? {}));
4501
+ const pendingInput = useSession((snapshot) => Math.max(0, ...(snapshot?.pendingSubmissions ?? []).filter((sub) => sub.placement !== "queued").map((sub) => sub.time)));
4310
4502
  const bot = roster.byId[sessionId];
4311
4503
  const room2 = roster.roomsById[sessionId];
4312
4504
  const entries2 = (0, import_react7.useMemo)(() => Object.values(JSON.parse(liveKey)), [liveKey]);
@@ -4315,7 +4507,10 @@ function ActivityIndicator({ sessionId, useRoster, useSessionStatus, useLive, ac
4315
4507
  const relaying = tools.some((entry) => entry.relay);
4316
4508
  const visible = running && !streaming && !relaying && (bot !== void 0 || room2 !== void 0);
4317
4509
  const tool = tools.at(-1);
4318
- const want = !visible ? null : tool ? { key: tool.label ? `tool:${tool.label}` : "working", label: tool.label, pool: PHRASES.working, state: tool.state ?? "working" } : { key: "thinking", pool: PHRASES.thinking, state: "thinking" };
4510
+ const inputRef = (0, import_react7.useRef)(0);
4511
+ const inputAt = Math.max(inputRef.current, pendingInput);
4512
+ inputRef.current = inputAt;
4513
+ const want = !visible ? null : inputAt > (tool?.at ?? 0) ? { key: `input:${inputAt}`, pool: PHRASES.thinking, state: "thinking" } : tool ? { key: tool.label ? `tool:${tool.label}` : "working", label: tool.label, pool: PHRASES.working, state: tool.state ?? "working" } : { key: "thinking", pool: PHRASES.thinking, state: "thinking" };
4319
4514
  const [shown, setShown] = (0, import_react7.useState)(null);
4320
4515
  const wantKey = want?.key ?? null;
4321
4516
  const markState = (0, import_react7.useRef)("thinking");
@@ -5176,7 +5371,7 @@ function MemoryNotice({ shown }) {
5176
5371
  (0, import_react9.createElement)("span", { className: "bt-pill-mem-in", ref: inner }, (0, import_react9.createElement)("span", { className: "bt-pill-mem-ico" }, (0, import_react9.createElement)(MemoryIcon)), label)
5177
5372
  );
5178
5373
  }
5179
- function HeaderPill({ sessionId, useRoster, useSessions, useSessionStatus, useActivity, useMemoryNews, useTurnClock, useUi, useLive, actions }) {
5374
+ function HeaderPill({ sessionId, useRoster, useSessions, useSessionStatus, useSession, useActivity, useMemoryNews, useTurnClock, useUi, useLive, actions }) {
5180
5375
  const roster = useRoster((value) => value);
5181
5376
  const title = useSessions((list) => list.byId[sessionId]?.displayTitle ?? list.byId[sessionId]?.title ?? "");
5182
5377
  const firstTurns = useSessions((list) => {
@@ -5240,7 +5435,7 @@ function HeaderPill({ sessionId, useRoster, useSessions, useSessionStatus, useAc
5240
5435
  ),
5241
5436
  (0, import_react9.createElement)("span", { className: "bt-sr", role: "status" }, memoryNotice ? t("Memory updated") : ""),
5242
5437
  (0, import_react9.createElement)(NewMessagePills, { key: sessionId, sessionId, useSessions, useUi, useTurnClock }),
5243
- (0, import_react9.createElement)(ActivityIndicator, { key: `activity:${sessionId}`, sessionId, useRoster, useSessionStatus, useLive, actions })
5438
+ (0, import_react9.createElement)(ActivityIndicator, { key: `activity:${sessionId}`, sessionId, useRoster, useSessionStatus, useSession, useLive, actions })
5244
5439
  );
5245
5440
  }
5246
5441
  function useTranscriptView() {
@@ -5472,7 +5667,16 @@ function ComposerVoice({ sessionId, inputActions, actions, useUi }) {
5472
5667
  dictation.note ? (0, import_react10.createElement)("span", { className: "bt-mic-note", role: "status" }, dictation.note) : null
5473
5668
  );
5474
5669
  }
5475
- function ComposerPlus({ sessionId, useRoster, actions }) {
5670
+ function menuIcons(roster) {
5671
+ const marks = /* @__PURE__ */ new Map();
5672
+ return (row) => {
5673
+ if (!row.bot) return PeopleIcon;
5674
+ const id = row.bot.id;
5675
+ if (!marks.has(id)) marks.set(id, ({ size = 14 }) => (0, import_react10.createElement)(BotMark, { bot: roster.getSnapshot().byId[id] ?? row.bot, size }));
5676
+ return marks.get(id);
5677
+ };
5678
+ }
5679
+ function ComposerPlus({ sessionId, inputActions, useRoster, actions }) {
5476
5680
  const roster = useRoster((value) => value);
5477
5681
  const [open, setOpen] = (0, import_react10.useState)(null);
5478
5682
  const [shown, leaving] = useLinger(open, 120);
@@ -5514,6 +5718,11 @@ function ComposerPlus({ sessionId, useRoster, actions }) {
5514
5718
  setOpen(null);
5515
5719
  actions.openSettings("schedules", target);
5516
5720
  };
5721
+ const reference = () => {
5722
+ setOpen(null);
5723
+ actions.referenceChat(sessionId, inputActions?.captureInsertion?.());
5724
+ };
5725
+ const canReference = typeof inputActions?.captureInsertion === "function" && actions.canReferenceChat();
5517
5726
  return (0, import_react10.createElement)(
5518
5727
  import_react10.Fragment,
5519
5728
  null,
@@ -5539,11 +5748,100 @@ function ComposerPlus({ sessionId, useRoster, actions }) {
5539
5748
  "data-leaving": leaving || void 0
5540
5749
  },
5541
5750
  (0, import_react10.createElement)("button", { type: "button", role: "menuitem", onClick: attach }, (0, import_react10.createElement)(PaperclipIcon), (0, import_react10.createElement)("span", { className: "bt-menu-label" }, t("Add files"))),
5751
+ canReference ? (0, import_react10.createElement)("button", { type: "button", role: "menuitem", onMouseDown: keepComposerFocus, onClick: reference }, (0, import_react10.createElement)(ChatsIcon), (0, import_react10.createElement)("span", { className: "bt-menu-label" }, t("Reference a chat"))) : null,
5542
5752
  (0, import_react10.createElement)("button", { type: "button", role: "menuitem", onClick: schedule }, (0, import_react10.createElement)(ClockIcon), (0, import_react10.createElement)("span", { className: "bt-menu-label" }, t("Scheduled task")))
5543
5753
  ), document.body) : null
5544
5754
  );
5545
5755
  }
5546
5756
 
5757
+ // src/client/at-menu.js
5758
+ var BOTS_SOURCE = "ds-bot";
5759
+ var CHATS_SOURCE = "ds-bot-chats";
5760
+ var ROOM_ALL = ["all", "everyone", "所有人", "全体"];
5761
+ var OTHER_CHATS_MAX = 8;
5762
+ var conversationOf = (roster, sessionId) => {
5763
+ if (!roster.ready) return null;
5764
+ const room2 = roster.roomsById[sessionId];
5765
+ if (room2) return { room: room2 };
5766
+ const self2 = roster.byId[sessionId];
5767
+ return self2 ? { self: self2 } : null;
5768
+ };
5769
+ function mentionRows(roster, sessionId, query = "") {
5770
+ const here = conversationOf(roster, sessionId);
5771
+ if (here === null) return null;
5772
+ const bots = here.room ? here.room.members.map((id) => roster.byId[id]).filter(Boolean) : roster.bots.filter((bot) => bot.id !== here.self.id);
5773
+ const needle = query.trim().toLowerCase();
5774
+ const rows = bots.filter((bot) => bot.name.toLowerCase().includes(needle)).map((bot) => ({ bot, text: `@${bot.name} ` }));
5775
+ const everyone = t("Everyone");
5776
+ if (here.room && bots.length > 1 && [everyone, ...ROOM_ALL].some((word) => word.toLowerCase().startsWith(needle))) {
5777
+ rows.push({ room: here.room, text: `@${everyone} ` });
5778
+ }
5779
+ return rows;
5780
+ }
5781
+ function chatRows(roster, list, sessionId) {
5782
+ const here = conversationOf(roster, sessionId);
5783
+ if (here === null) return [];
5784
+ const byId = list?.byId ?? {};
5785
+ const bots = roster.bots.filter((bot) => bot.id !== here.self?.id).map((bot) => {
5786
+ const id = currentPart(bot);
5787
+ return { sessionId: id, label: t("Chat with {name}", { name: bot.name }), bot, updatedAt: byId[id]?.updatedAt };
5788
+ });
5789
+ const team = /* @__PURE__ */ new Set([
5790
+ ...roster.bots.flatMap((bot) => [bot.id, currentPart(bot), ...bot.parts ?? []]),
5791
+ ...roster.rooms.flatMap((room2) => [room2.id, ...room2.parts ?? []])
5792
+ ]);
5793
+ const others = (list?.ids ?? []).map((id) => byId[id]).filter((row) => row !== void 0 && row.id !== sessionId && !team.has(row.id) && row.origin !== "subagent" && row.blank !== true).sort((a, b) => (b.updatedAt ?? 0) - (a.updatedAt ?? 0)).slice(0, OTHER_CHATS_MAX).map((row) => ({ sessionId: row.id, label: row.displayTitle || row.title || row.id, updatedAt: row.updatedAt }));
5794
+ return [...bots, ...others];
5795
+ }
5796
+ function sessionMention(sessionId, label) {
5797
+ let binary = "";
5798
+ for (const byte of new TextEncoder().encode(JSON.stringify(sessionId))) binary += String.fromCharCode(byte);
5799
+ const payload = btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
5800
+ return `@[${label.replace(/[\\\]]/g, (mark) => `\\${mark}`)}](dsh-session:${payload})`;
5801
+ }
5802
+ function atSources({ roster, sessions, launched, iconFor }) {
5803
+ const bots = {
5804
+ trigger: "@",
5805
+ name: BOTS_SOURCE,
5806
+ order: -20,
5807
+ showGroupTitle: false,
5808
+ async candidates(session, { query }) {
5809
+ return (mentionRows(roster.getSnapshot(), session.sessionId, query) ?? []).map((row) => ({
5810
+ name: row.bot ? row.bot.name : t("Everyone"),
5811
+ ...row.bot?.role ? { description: row.bot.role } : {},
5812
+ ...row.room ? { description: t("{count} members", { count: row.room.members.length }) } : {},
5813
+ icon: iconFor(row),
5814
+ section: t("Bots"),
5815
+ value: row.text
5816
+ }));
5817
+ },
5818
+ onPick: ({ candidate }) => candidate.value ? { text: candidate.value } : void 0
5819
+ };
5820
+ const chats = {
5821
+ trigger: "@",
5822
+ name: CHATS_SOURCE,
5823
+ order: -10,
5824
+ showGroupTitle: false,
5825
+ async candidates(session) {
5826
+ if (!launched(session.sessionId)) return [];
5827
+ return chatRows(roster.getSnapshot(), sessions.list.getSnapshot(), session.sessionId).map((row) => ({
5828
+ name: row.bot ? row.bot.name : row.label,
5829
+ ...row.updatedAt ? { description: relativeShort(row.updatedAt) } : {},
5830
+ icon: row.bot ? iconFor(row) : "session",
5831
+ section: row.bot ? t("Bots") : t("Conversations"),
5832
+ value: JSON.stringify({ sessionId: row.sessionId, label: row.label })
5833
+ }));
5834
+ },
5835
+ onPick({ candidate }) {
5836
+ const { sessionId, label } = JSON.parse(candidate.value);
5837
+ const mention = sessionMention(sessionId, label);
5838
+ return { insert: { source: CHATS_SOURCE, ref: mention, label, appearance: "session", clipboardText: mention } };
5839
+ },
5840
+ codec: { clipboardText: (ref) => ref, serialize: (ref) => Promise.resolve(ref) }
5841
+ };
5842
+ return [bots, chats];
5843
+ }
5844
+
5547
5845
  // src/client/speech.js
5548
5846
  var VOICE_BUNDLE = "@deepseek-ai/dsh-experimental-voice-input-bundle";
5549
5847
  var USABLE = /* @__PURE__ */ new Set(["ready", "standby", "waking"]);
@@ -7361,8 +7659,9 @@ function CreateBot({ roster, actions, start, brief = "", onBack, canGoBack, onCr
7361
7659
  const fallbackName = freshBotName(roster, t("New Bot"));
7362
7660
  const finalName = name.trim() || fallbackName;
7363
7661
  const clash = roster.bots.some((bot) => bot.name.toLowerCase() === finalName.toLowerCase());
7662
+ const problem = nameProblem(finalName);
7364
7663
  const create = async () => {
7365
- if (busy || clash) return;
7664
+ if (busy || clash || problem) return;
7366
7665
  setError("");
7367
7666
  setBusy(true);
7368
7667
  try {
@@ -7444,7 +7743,7 @@ function CreateBot({ roster, actions, start, brief = "", onBack, canGoBack, onCr
7444
7743
  }
7445
7744
  }
7446
7745
  }),
7447
- clash ? (0, import_react17.createElement)("span", { className: "bt-note bt-danger" }, t("A Bot named {name} already exists", { name: finalName })) : null
7746
+ problem ? (0, import_react17.createElement)("span", { className: "bt-note bt-danger" }, hostText(problem)) : clash ? (0, import_react17.createElement)("span", { className: "bt-note bt-danger" }, t("A Bot named {name} already exists", { name: finalName })) : null
7448
7747
  ),
7449
7748
  (0, import_react17.createElement)(
7450
7749
  "div",
@@ -7461,7 +7760,7 @@ function CreateBot({ roster, actions, start, brief = "", onBack, canGoBack, onCr
7461
7760
  "div",
7462
7761
  { className: "bt-create-foot" },
7463
7762
  (0, import_react17.createElement)("button", { type: "button", className: "bt-soft", onClick: onBack }, canGoBack ? t("Back") : t("Cancel")),
7464
- (0, import_react17.createElement)("button", { type: "button", className: "bt-send bt-create-go", disabled: busy || clash, onClick: () => void create() }, busy ? t("Creating…") : t("Create"))
7763
+ (0, import_react17.createElement)("button", { type: "button", className: "bt-send bt-create-go", disabled: busy || clash || Boolean(problem), onClick: () => void create() }, busy ? t("Creating…") : t("Create"))
7465
7764
  )
7466
7765
  );
7467
7766
  if (inline) return (0, import_react17.createElement)("div", { ref: frame, className: "bt-create-inline", role: "group", "aria-label": t("Create Bot") }, card);
@@ -8844,6 +9143,7 @@ function SpanPicker({ span, today, oldest, active, near, onPick, onClose }) {
8844
9143
  );
8845
9144
  }
8846
9145
  function Heat({ view, keep, paint, index }) {
9146
+ const [hover, setHover] = (0, import_react22.useState)(null);
8847
9147
  const today = hourMs(`${view.now.slice(0, 10)} 00`);
8848
9148
  const first = mondayOf(today) - (HEAT_WEEKS - 1) * 7 * DAY_MS;
8849
9149
  const from = dayKey(first);
@@ -8869,12 +9169,17 @@ function Heat({ view, keep, paint, index }) {
8869
9169
  key: ms,
8870
9170
  className: "bt-us-cell",
8871
9171
  style: { "--w": week },
9172
+ "data-on": hover?.ms === ms ? "" : void 0,
8872
9173
  "data-future": ms > today ? "" : void 0,
8873
9174
  "data-today": ms === today ? "" : void 0,
8874
- title: ms > today ? void 0 : `${dates({ month: "long", day: "numeric", weekday: "short" }).format(ms)} · ${t("{count} tokens", { count: full(value) })}`
9175
+ onMouseEnter: ms > today ? void 0 : () => setHover({ ms, week, day })
8875
9176
  }, level > 0 ? (0, import_react22.createElement)("i", { style: { background: soften(paint, levels[level - 1]) } }) : null));
8876
9177
  }
8877
9178
  }
9179
+ const tipSide = hover ? {
9180
+ ...hover.week >= HEAT_WEEKS / 2 ? { right: `calc(${(HEAT_WEEKS - hover.week) / HEAT_WEEKS * 100}% + 8px)` } : { left: `calc(${(hover.week + 1) / HEAT_WEEKS * 100}% + 8px)` },
9181
+ ...hover.day < 4 ? { top: `calc(${hover.day / 7 * 100}% - 2px)` } : { top: "auto", bottom: `calc(${(6 - hover.day) / 7 * 100}% - 2px)` }
9182
+ } : null;
8878
9183
  const active = [...daily.values()].filter((value) => value > 0).length;
8879
9184
  const summary = active === 1 ? t("Active on 1 day in the last year") : t("Active on {count} days in the last year", { count: active });
8880
9185
  return (0, import_react22.createElement)(
@@ -8882,7 +9187,17 @@ function Heat({ view, keep, paint, index }) {
8882
9187
  { className: "bt-us-card bt-us-heat", style: { "--i": index } },
8883
9188
  (0, import_react22.createElement)("div", { className: "bt-us-card-head" }, (0, import_react22.createElement)("h3", null, t("Activity")), (0, import_react22.createElement)("span", { className: "bt-us-head-note" }, summary)),
8884
9189
  (0, import_react22.createElement)("div", { className: "bt-us-heat-months", "aria-hidden": true }, months),
8885
- (0, import_react22.createElement)("div", { className: "bt-us-heat-grid", role: "img", "aria-label": summary }, cells),
9190
+ (0, import_react22.createElement)(
9191
+ "div",
9192
+ { className: "bt-us-heat-plot", onMouseLeave: () => setHover(null) },
9193
+ (0, import_react22.createElement)("div", { className: "bt-us-heat-grid", role: "img", "aria-label": summary }, cells),
9194
+ hover ? (0, import_react22.createElement)(
9195
+ "div",
9196
+ { className: "bt-us-tip bt-us-heat-tip", style: tipSide },
9197
+ (0, import_react22.createElement)("div", { className: "bt-us-tip-title" }, dates({ month: "long", day: "numeric", weekday: "short" }).format(hover.ms)),
9198
+ (0, import_react22.createElement)("div", { className: "bt-us-tip-total" }, t("{count} tokens", { count: full(daily.get(dayKey(hover.ms)) ?? 0) }))
9199
+ ) : null
9200
+ ),
8886
9201
  (0, import_react22.createElement)(
8887
9202
  "div",
8888
9203
  { className: "bt-us-heat-scale", "aria-hidden": true },
@@ -8897,9 +9212,18 @@ function Skeleton2() {
8897
9212
  "div",
8898
9213
  { className: "bt-us", "aria-busy": true },
8899
9214
  (0, import_react22.createElement)("div", { className: "bt-us-skel", style: { height: 32, width: 320, borderRadius: 999 } }),
8900
- (0, import_react22.createElement)("div", { className: "bt-us-skel", style: { height: 94 } }),
8901
- (0, import_react22.createElement)("div", { className: "bt-us-skel", style: { height: 318 } }),
8902
- (0, import_react22.createElement)("div", { className: "bt-us-pair" }, (0, import_react22.createElement)("div", { className: "bt-us-skel", style: { height: 200 } }), (0, import_react22.createElement)("div", { className: "bt-us-skel", style: { height: 200 } }))
9215
+ (0, import_react22.createElement)(
9216
+ "div",
9217
+ { className: "bt-us-sheet bt-us-skel-sheet" },
9218
+ (0, import_react22.createElement)("div", { className: "bt-us-skel", style: { height: 94 } }),
9219
+ (0, import_react22.createElement)("div", { className: "bt-us-skel", style: { height: 318 } }),
9220
+ (0, import_react22.createElement)(
9221
+ "div",
9222
+ { className: "bt-us-pair" },
9223
+ (0, import_react22.createElement)("div", { className: "bt-us-skel", style: { height: 200 } }),
9224
+ (0, import_react22.createElement)("div", { className: "bt-us-skel", style: { height: 200 } })
9225
+ )
9226
+ )
8903
9227
  );
8904
9228
  }
8905
9229
  var focusOf = (id) => typeof id !== "string" ? null : id.startsWith("model:") ? { kind: "model", key: id.slice("model:".length) } : id.startsWith("owner:") ? { kind: "owner", key: id.slice("owner:".length) } : null;
@@ -9069,19 +9393,23 @@ function UsagePage({ roster, actions, focusId }) {
9069
9393
  const owners = ranked(part.owners);
9070
9394
  body = [
9071
9395
  (0, import_react22.createElement)(Profile, { key: "profile", face, range, share: all.total.total > 0 ? whole / all.total.total : 0 }),
9072
- (0, import_react22.createElement)(Stats, { key: "stats", range, total: part.total, previous: part.previous }),
9073
- (0, import_react22.createElement)(Chart, { key: "chart", range, columns, series, tipSeries, onPick: pick2 }),
9074
9396
  (0, import_react22.createElement)(
9075
9397
  "div",
9076
- { key: "pair", className: "bt-us-pair" },
9077
- ownerAt >= 0 ? (0, import_react22.createElement)(ListCard, { title: t("Models"), count: usedModels.length, index: 2 }, usedModels.length > 0 ? usedModels.map(modelRow) : nothing) : (0, import_react22.createElement)(ListCard, { title: t("Who used it"), count: owners.length, index: 2 }, owners.length > 0 ? owners.map((entry, index) => ownerRow(entry, index)) : nothing),
9398
+ { key: "sheet", className: "bt-us-sheet" },
9399
+ (0, import_react22.createElement)(Stats, { key: "stats", range, total: part.total, previous: part.previous }),
9400
+ (0, import_react22.createElement)(Chart, { key: "chart", range, columns, series, tipSeries, onPick: pick2 }),
9078
9401
  (0, import_react22.createElement)(
9079
- ListCard,
9080
- { title: t("What for"), index: 3 },
9081
- kinds.length > 0 ? kinds.map(([at, sum], index) => (0, import_react22.createElement)(Row2, { key: at, name: kindLabel(view.kinds[at], ownerAt >= 0 && isBot(ownerAt)), sum, whole, index, fill: soften(face.paint, 0.8) })) : nothing
9082
- )
9083
- ),
9084
- (0, import_react22.createElement)(Heat, { key: "heat", view, keep, paint: face.paint, index: 4 })
9402
+ "div",
9403
+ { key: "pair", className: "bt-us-pair" },
9404
+ ownerAt >= 0 ? (0, import_react22.createElement)(ListCard, { title: t("Models"), count: usedModels.length, index: 2 }, usedModels.length > 0 ? usedModels.map(modelRow) : nothing) : (0, import_react22.createElement)(ListCard, { title: t("Who used it"), count: owners.length, index: 2 }, owners.length > 0 ? owners.map((entry, index) => ownerRow(entry, index)) : nothing),
9405
+ (0, import_react22.createElement)(
9406
+ ListCard,
9407
+ { title: t("What for"), index: 3 },
9408
+ kinds.length > 0 ? kinds.map(([at, sum], index) => (0, import_react22.createElement)(Row2, { key: at, name: kindLabel(view.kinds[at], ownerAt >= 0 && isBot(ownerAt)), sum, whole, index, fill: soften(face.paint, 0.8) })) : nothing
9409
+ )
9410
+ ),
9411
+ (0, import_react22.createElement)(Heat, { key: "heat", view, keep, paint: face.paint, index: 4 })
9412
+ )
9085
9413
  ];
9086
9414
  } else {
9087
9415
  const owners = ranked(all.owners);
@@ -9089,22 +9417,26 @@ function UsagePage({ roster, actions, focusId }) {
9089
9417
  const rest = owners.filter(([at]) => !isBot(at));
9090
9418
  const usedModels = ranked(all.models);
9091
9419
  body = [
9092
- (0, import_react22.createElement)(Stats, { key: "stats", range, total: all.total, previous: all.previous }),
9093
- (0, import_react22.createElement)(Chart, { key: "chart", range, columns, series, tipSeries, by, onBy: setBy, onPick: pick2 }),
9094
9420
  (0, import_react22.createElement)(
9095
9421
  "div",
9096
- { key: "pair", className: "bt-us-pair" },
9422
+ { key: "sheet", className: "bt-us-sheet" },
9423
+ (0, import_react22.createElement)(Stats, { key: "stats", range, total: all.total, previous: all.previous }),
9424
+ (0, import_react22.createElement)(Chart, { key: "chart", range, columns, series, tipSeries, by, onBy: setBy, onPick: pick2 }),
9097
9425
  (0, import_react22.createElement)(
9098
- ListCard,
9099
- { title: t("Bots"), count: bots.length, index: 2 },
9100
- owners.length > 0 ? null : nothing,
9101
- bots.map((entry, index) => ownerRow(entry, index)),
9102
- bots.length > 0 && rest.length > 0 ? (0, import_react22.createElement)("div", { className: "bt-us-split", role: "separator" }) : null,
9103
- rest.map((entry, index) => ownerRow(entry, bots.length + index))
9426
+ "div",
9427
+ { key: "pair", className: "bt-us-pair" },
9428
+ (0, import_react22.createElement)(
9429
+ ListCard,
9430
+ { title: t("Bots"), count: bots.length, index: 2 },
9431
+ owners.length > 0 ? null : nothing,
9432
+ bots.map((entry, index) => ownerRow(entry, index)),
9433
+ bots.length > 0 && rest.length > 0 ? (0, import_react22.createElement)("div", { className: "bt-us-split", role: "separator" }) : null,
9434
+ rest.map((entry, index) => ownerRow(entry, bots.length + index))
9435
+ ),
9436
+ (0, import_react22.createElement)(ListCard, { title: t("Models"), count: usedModels.length, index: 3 }, usedModels.length > 0 ? usedModels.map(modelRow) : nothing)
9104
9437
  ),
9105
- (0, import_react22.createElement)(ListCard, { title: t("Models"), count: usedModels.length, index: 3 }, usedModels.length > 0 ? usedModels.map(modelRow) : nothing)
9106
- ),
9107
- (0, import_react22.createElement)(Heat, { key: "heat", view, keep, paint: "var(--bt-accent)", index: 4 })
9438
+ (0, import_react22.createElement)(Heat, { key: "heat", view, keep, paint: "var(--bt-accent)", index: 4 })
9439
+ )
9108
9440
  ];
9109
9441
  }
9110
9442
  return (0, import_react22.createElement)(
@@ -9165,6 +9497,10 @@ var USAGE_CSS = `
9165
9497
  .bt-us-tabs>.bt-thumb{top:3px;bottom:3px;border-radius:999px;background:var(--bt-card);box-shadow:0 1px 3px rgba(0,0,0,.08),0 0 0 .5px var(--bt-line-2)}
9166
9498
  .bt-us-note{display:inline-flex;align-items:center;gap:8px;font-size:12px;line-height:16px;color:var(--bt-ink-3)}
9167
9499
  .bt-us-note::before{content:'';width:6px;height:6px;border-radius:50%;background:var(--bt-accent);animation:bt-us-pulse 1.4s ease-in-out infinite}
9500
+ .bt-us-sheet{display:flex;flex-direction:column;gap:4px}
9501
+ .bt-us-sheet>.bt-us-stats,.bt-us-sheet>.bt-us-card,.bt-us-sheet>.bt-us-pair>.bt-us-card{border-radius:0}
9502
+ .bt-us-sheet>:first-child{border-radius:16px 16px 0 0}
9503
+ .bt-us-sheet>:last-child{border-radius:0 0 16px 16px}
9168
9504
  .bt-us-stats{display:grid;grid-template-columns:1.6fr 1fr 1fr 1fr;border-radius:16px;border:1px solid var(--bt-line);background:var(--bt-card);animation:bt-us-in .45s var(--bt-us-ease) backwards}
9169
9505
  .bt-us-stat{display:flex;flex-direction:column;justify-content:flex-end;gap:6px;min-width:0;padding:16px 18px}
9170
9506
  .bt-us-stat+.bt-us-stat{border-left:1px solid var(--bt-line)}
@@ -9204,7 +9540,7 @@ var USAGE_CSS = `
9204
9540
  button.bt-us-key{cursor:pointer;transition:background-color .12s ease,color .12s ease}
9205
9541
  button.bt-us-key:hover{background:var(--bt-active);color:var(--bt-ink)}
9206
9542
  .bt-us-dot{flex:none;width:8px;height:8px;border-radius:50%}
9207
- .bt-us-pair{display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:14px}
9543
+ .bt-us-pair{display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:4px}
9208
9544
  .bt-us-rows{display:flex;flex-direction:column;margin:0 -8px}
9209
9545
  .bt-us-row{display:flex;align-items:center;gap:12px;width:100%;box-sizing:border-box;padding:8px;border:0;border-radius:12px;background:none;color:var(--bt-ink);font:inherit;text-align:left;animation:bt-us-in .4s var(--bt-us-ease) backwards;animation-delay:calc(var(--i,0) * 35ms + 90ms)}
9210
9546
  button.bt-us-row{cursor:pointer;transition:background-color .12s ease}
@@ -9238,7 +9574,10 @@ button.bt-us-row:hover{background:var(--bt-hover)}
9238
9574
  .bt-us-heat-months,.bt-us-heat-grid{display:grid;grid-template-columns:repeat(${HEAT_WEEKS},1fr);gap:3px}
9239
9575
  .bt-us-heat-months{height:14px;font-size:10px;line-height:14px;color:var(--bt-ink-3)}
9240
9576
  .bt-us-heat-months span{white-space:nowrap}
9577
+ .bt-us-heat-plot{position:relative}
9241
9578
  .bt-us-heat-grid{grid-template-rows:repeat(7,auto);grid-auto-flow:column}
9579
+ .bt-us-heat-grid .bt-us-cell[data-on]{outline:1.5px solid var(--bt-ink-2);outline-offset:1px}
9580
+ .bt-us-heat-tip{min-width:0;white-space:nowrap}
9242
9581
  .bt-us-cell{position:relative;aspect-ratio:1;border-radius:3px;background:var(--bt-hover);overflow:hidden}
9243
9582
  .bt-us-heat-grid .bt-us-cell{animation:bt-us-cell .5s var(--bt-us-ease) backwards;animation-delay:calc(var(--w,0) * 7ms + 120ms)}
9244
9583
  .bt-us-cell>i{position:absolute;inset:0}
@@ -9247,6 +9586,10 @@ button.bt-us-row:hover{background:var(--bt-hover)}
9247
9586
  .bt-us-heat-scale{display:flex;align-items:center;justify-content:flex-end;gap:4px;font-size:11px;line-height:14px;color:var(--bt-ink-3)}
9248
9587
  .bt-us-heat-scale .bt-us-cell{width:10px}
9249
9588
  .bt-us-skel{border-radius:16px;background:linear-gradient(90deg,var(--bt-hover) 0%,var(--bt-active) 50%,var(--bt-hover) 100%);background-size:200% 100%;animation:bt-us-shine 1.4s ease-in-out infinite}
9589
+ .bt-us-skel-sheet>.bt-us-skel:first-child{border-radius:16px 16px 0 0}
9590
+ .bt-us-skel-sheet>.bt-us-skel:not(:first-child){border-radius:0}
9591
+ .bt-us-skel-sheet>.bt-us-pair>.bt-us-skel:first-child{border-radius:0 0 0 16px}
9592
+ .bt-us-skel-sheet>.bt-us-pair>.bt-us-skel:last-child{border-radius:0 0 16px 0}
9250
9593
  .bt-us-empty{display:flex;flex-direction:column;align-items:center;gap:8px;padding:72px 16px;text-align:center;font-size:13px;line-height:18px;color:var(--bt-ink-3)}
9251
9594
  .bt-us-empty b{margin-top:6px;font-size:15px;font-weight:600;color:var(--bt-ink)}
9252
9595
  @keyframes bt-us-in{from{opacity:0;transform:translateY(6px)}}
@@ -9369,8 +9712,8 @@ var copy = (text) => navigator.clipboard === void 0 ? Promise.resolve(false) : n
9369
9712
  function whenText(time) {
9370
9713
  const date = new Date(time);
9371
9714
  const today = (/* @__PURE__ */ new Date()).toDateString() === date.toDateString();
9372
- const clock3 = date.toLocaleTimeString(dateLocale(), { hour: "2-digit", minute: "2-digit" });
9373
- return today ? clock3 : `${date.toLocaleDateString(dateLocale(), { month: "short", day: "numeric" })} ${clock3}`;
9715
+ const clock4 = date.toLocaleTimeString(dateLocale(), { hour: "2-digit", minute: "2-digit" });
9716
+ return today ? clock4 : `${date.toLocaleDateString(dateLocale(), { month: "short", day: "numeric" })} ${clock4}`;
9374
9717
  }
9375
9718
  function entrySummary(entry) {
9376
9719
  if (entry.source === "turn") return failureLabel(entry.code, entry.text);
@@ -10143,8 +10486,142 @@ function migrateStorage(storage = globalThis.localStorage) {
10143
10486
  }
10144
10487
  }
10145
10488
 
10146
- // src/client/details-drawer.js
10489
+ // src/client/member-status.js
10147
10490
  var import_react28 = require("react");
10491
+ var NO_WORK = { bots: {}, approvals: [] };
10492
+ function memberWork(room2, work = NO_WORK) {
10493
+ return room2.members.map((id) => {
10494
+ const approvals = (work.approvals ?? []).filter((entry) => entry.botId === id);
10495
+ const doing = work.bots?.[id];
10496
+ return { id, approvals, doing, state: approvals.length > 0 ? "ask" : doing ? "work" : "idle" };
10497
+ });
10498
+ }
10499
+ function busyMembers(room2, work, roster) {
10500
+ const busy = memberWork(room2, work).filter((entry) => entry.state !== "idle" && roster.byId[entry.id]);
10501
+ return [...busy.filter((entry) => entry.state === "ask"), ...busy.filter((entry) => entry.state === "work")].map((entry) => ({ ...entry, bot: roster.byId[entry.id] }));
10502
+ }
10503
+ function summaryText(busy) {
10504
+ const asks = busy.filter((entry) => entry.state === "ask");
10505
+ const workers = busy.filter((entry) => entry.state === "work");
10506
+ const names = asks.slice(0, 2).map((entry) => entry.bot.name);
10507
+ const waiting = asks.length === 0 ? "" : asks.length === 1 ? t("{name} is waiting for your approval", { name: names[0] }) : asks.length === 2 ? t("{first} and {second} are waiting for your approval", { first: names[0], second: names[1] }) : t("{first}, {second} and {count} more are waiting for your approval", { first: names[0], second: names[1], count: asks.length - 2, total: asks.length });
10508
+ const first = workers[0];
10509
+ const doing = first ? `${first.bot.name} · ${workPhrase(first.doing.tool)}…${workers.length > 1 ? ` +${workers.length - 1}` : ""}` : "";
10510
+ return { waiting, doing };
10511
+ }
10512
+ function Face({ entry, size }) {
10513
+ return (0, import_react28.createElement)(
10514
+ "span",
10515
+ { className: "bt-mw-face", "data-ask": entry.state === "ask" || void 0 },
10516
+ (0, import_react28.createElement)(BotMark, { bot: entry.bot, size, state: entry.state === "ask" ? "alert" : toolState(entry.doing?.tool ?? "") })
10517
+ );
10518
+ }
10519
+ function MemberDock({ sessionId, useRoster, useWork, actions }) {
10520
+ const roster = useRoster((value) => value);
10521
+ const work = useWork((value) => value);
10522
+ const room2 = roster.roomsById[sessionId];
10523
+ if (!room2) return null;
10524
+ const busy = busyMembers(room2, work, roster);
10525
+ if (busy.length === 0) return null;
10526
+ const { waiting, doing } = summaryText(busy);
10527
+ return (0, import_react28.createElement)(
10528
+ "div",
10529
+ { className: "bt-event bt-mw-dock" },
10530
+ (0, import_react28.createElement)(
10531
+ "button",
10532
+ { type: "button", className: "bt-event-whole bt-mw-line", "aria-label": [waiting, doing].filter(Boolean).join(" · "), onClick: () => actions.showDetails(sessionId) },
10533
+ (0, import_react28.createElement)("span", { className: "bt-mw-stack", "aria-hidden": true }, busy.slice(0, 4).map((entry) => (0, import_react28.createElement)(Face, { key: entry.id, entry, size: 16 }))),
10534
+ waiting ? (0, import_react28.createElement)("span", { className: "bt-mw-ask" }, waiting) : null,
10535
+ waiting && doing ? (0, import_react28.createElement)("span", { "aria-hidden": true }, "·") : null,
10536
+ doing ? (0, import_react28.createElement)("span", { className: "bt-event-name" }, (0, import_react28.createElement)(Shimmer, null, doing)) : null
10537
+ )
10538
+ );
10539
+ }
10540
+ function useNow2(active) {
10541
+ const [now, setNow] = (0, import_react28.useState)(Date.now);
10542
+ (0, import_react28.useEffect)(() => {
10543
+ if (!active) return void 0;
10544
+ const timer = setInterval(() => setNow(Date.now()), 3e4);
10545
+ return () => clearInterval(timer);
10546
+ }, [active]);
10547
+ return now;
10548
+ }
10549
+ var since = (now, at) => typeof at === "number" && now - at >= 6e4 ? ` · ${elapsedLabel(now - at)}` : "";
10550
+ function ApprovalCard({ approval, bot, actions }) {
10551
+ const [busy, setBusy] = (0, import_react28.useState)(false);
10552
+ const answer = (choice) => {
10553
+ setBusy(true);
10554
+ void Promise.resolve(actions.answerApproval(approval.id, choice)).catch((error) => actions.reportError(error, "approval")).finally(() => setBusy(false));
10555
+ };
10556
+ return (0, import_react28.createElement)(
10557
+ "div",
10558
+ { className: "bt-mw-card", role: "group", "aria-label": t("{name} is waiting for your approval", { name: bot.name }) },
10559
+ (0, import_react28.createElement)("div", { className: "bt-mw-tool" }, approval.tool),
10560
+ approval.detail?.text ? (0, import_react28.createElement)("div", { className: "bt-mw-text" }, approval.detail.text) : null,
10561
+ approval.reason ? (0, import_react28.createElement)("div", { className: "bt-mw-reason" }, approval.reason) : null,
10562
+ approval.detail?.code ? (0, import_react28.createElement)("code", { className: "bt-mw-code", title: approval.detail.code }, approval.detail.code) : null,
10563
+ approval.answerable ? (0, import_react28.createElement)(
10564
+ "div",
10565
+ { className: "bt-mw-actions" },
10566
+ (0, import_react28.createElement)("button", { type: "button", className: "bt-q-btn", disabled: busy, onClick: () => answer("reject") }, t("Reject")),
10567
+ (0, import_react28.createElement)("button", { type: "button", className: "bt-q-submit", disabled: busy, onClick: () => answer("allow") }, t("Allow once"))
10568
+ ) : (0, import_react28.createElement)(
10569
+ "div",
10570
+ { className: "bt-mw-actions" },
10571
+ (0, import_react28.createElement)("button", { type: "button", className: "bt-q-btn", onClick: () => actions.openSession(bot.id) }, t("Open {name}'s chat", { name: bot.name }))
10572
+ )
10573
+ );
10574
+ }
10575
+ function MemberWork({ entry, bot, room: room2, roster, actions, now }) {
10576
+ if (entry.state === "ask") {
10577
+ return (0, import_react28.createElement)(
10578
+ "div",
10579
+ { className: "bt-mw-member" },
10580
+ (0, import_react28.createElement)("div", { className: "bt-mw-state bt-mw-ask" }, `${t("Waiting for your approval")}${since(now, entry.approvals[0].at)}`),
10581
+ entry.approvals.map((approval) => (0, import_react28.createElement)(ApprovalCard, { key: approval.id, approval, bot, actions }))
10582
+ );
10583
+ }
10584
+ if (entry.state !== "work") return null;
10585
+ const where = entry.doing.roomId === room2.id ? "" : entry.doing.roomId ? t("in {name}", { name: roster.roomsById[entry.doing.roomId]?.name ?? t("a group chat") }) : t("in its own chat");
10586
+ return (0, import_react28.createElement)(
10587
+ "div",
10588
+ { className: "bt-mw-member" },
10589
+ (0, import_react28.createElement)("div", { className: "bt-mw-state" }, (0, import_react28.createElement)(Shimmer, null, `${workPhrase(entry.doing.tool)}…`), where ? ` ${where}` : "", since(now, entry.doing.since))
10590
+ );
10591
+ }
10592
+ function useMemberWork(room2, useWork) {
10593
+ const work = useWork?.((value) => value) ?? NO_WORK;
10594
+ const entries2 = room2 ? memberWork(room2, work) : [];
10595
+ const now = useNow2(entries2.some((entry) => entry.state !== "idle"));
10596
+ return { entries: entries2, now };
10597
+ }
10598
+ var MEMBER_CSS = `
10599
+ .bt-event.bt-mw-dock{margin:0 0 6px;pointer-events:none}
10600
+ .bt-mw-dock>button{pointer-events:auto;animation:bt-mw-in .32s cubic-bezier(.23,1,.32,1) backwards}
10601
+ .bt-mw-line{gap:6px!important}
10602
+ .bt-mw-stack{display:inline-flex;align-items:center;flex:none;line-height:0}
10603
+ .bt-mw-stack>*+*{margin-left:-4px}
10604
+ .bt-mw-face{position:relative;display:inline-flex;flex:none;border-radius:50%}
10605
+ .bt-mw-stack .bt-mw-face{box-shadow:0 0 0 1.5px var(--bt-main,#fff)}
10606
+ .bt-mw-face[data-ask]::after{content:"";position:absolute;right:-2px;bottom:-2px;width:7px;height:7px;border-radius:50%;background:#e3a008;box-shadow:0 0 0 1.5px var(--bt-main,#fff)}
10607
+ .bt-mw-ask{color:#b7791f;flex:none;white-space:nowrap}
10608
+ body[data-ds-dark-theme] .bt-mw-ask{color:#f0b84a}
10609
+ .bt-mw-member{padding:0 6px 8px 42px;display:flex;flex-direction:column;gap:6px}
10610
+ .bt-mw-state{font-size:12px;line-height:16px;color:var(--bt-ink-2);margin-top:-4px}
10611
+ .bt-mw-card{display:flex;flex-direction:column;gap:4px;padding:10px 12px;border-radius:12px;background:var(--bt-hover);font-size:12px;line-height:17px;color:var(--bt-ink-2)}
10612
+ .bt-mw-tool{font-weight:600;color:var(--bt-ink)}
10613
+ .bt-mw-text{color:var(--bt-ink);overflow-wrap:anywhere}
10614
+ .bt-mw-reason{overflow-wrap:anywhere;display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden}
10615
+ .bt-mw-code{display:block;font:11px/16px ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;color:var(--bt-ink);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
10616
+ .bt-mw-actions{display:flex;justify-content:flex-end;gap:6px;margin-top:4px}
10617
+ .bt-mw-actions>button{height:28px;padding:0 12px;font-size:12px}
10618
+ .bt-mw-actions>button:disabled{opacity:.5;cursor:default}
10619
+ @keyframes bt-mw-in{from{opacity:0;transform:translateY(4px)}}
10620
+ @media (prefers-reduced-motion:reduce){.bt-mw-dock>button{animation:none}}
10621
+ `;
10622
+
10623
+ // src/client/details-drawer.js
10624
+ var import_react29 = require("react");
10148
10625
  function transcriptOf(list, id, name, hiddenTurns = []) {
10149
10626
  const outline = list.projectionsBySession?.[id]?.values?.turnOutline ?? list.byId[id]?.projectionValues?.turnOutline;
10150
10627
  if (!Array.isArray(outline)) return "";
@@ -10169,10 +10646,10 @@ var EMPTY_TABS = { ids: [], active: null };
10169
10646
  var PERMISSION_LABELS = { "read-only": "Read only", "workspace-write": "Workspace", "danger-full-access": "Full access" };
10170
10647
  var DANGER = /* @__PURE__ */ new Set(["danger-full-access"]);
10171
10648
  function PermissionRow({ bot, roster, actions, run }) {
10172
- const [pending, setPending] = (0, import_react28.useState)(null);
10649
+ const [pending, setPending] = (0, import_react29.useState)(null);
10173
10650
  const catalog = roster.permissions;
10174
10651
  const saved = bot.permission ?? catalog?.defaultPreset;
10175
- (0, import_react28.useEffect)(() => {
10652
+ (0, import_react29.useEffect)(() => {
10176
10653
  if (pending === saved) setPending(null);
10177
10654
  }, [pending, saved]);
10178
10655
  if (!catalog?.options?.length) return null;
@@ -10182,14 +10659,14 @@ function PermissionRow({ bot, roster, actions, run }) {
10182
10659
  setPending(value);
10183
10660
  void run(actions.updateBot(bot.id, { permission: value })).catch(() => setPending(null));
10184
10661
  };
10185
- return (0, import_react28.createElement)(
10662
+ return (0, import_react29.createElement)(
10186
10663
  "div",
10187
10664
  { className: "bt-drawer-permission" },
10188
- (0, import_react28.createElement)("div", { className: "bt-section-title" }, t("Permissions")),
10189
- (0, import_react28.createElement)(
10665
+ (0, import_react29.createElement)("div", { className: "bt-section-title" }, t("Permissions")),
10666
+ (0, import_react29.createElement)(
10190
10667
  Segmented,
10191
10668
  { label: t("Permissions"), value: current },
10192
- catalog.options.map((option) => (0, import_react28.createElement)("button", {
10669
+ catalog.options.map((option) => (0, import_react29.createElement)("button", {
10193
10670
  key: option.value,
10194
10671
  type: "button",
10195
10672
  "aria-pressed": option.value === current,
@@ -10203,42 +10680,42 @@ function PermissionRow({ bot, roster, actions, run }) {
10203
10680
  function BrowserCards({ bot, sessionId, roster, actions, useBrowserTabs, useBrowserPages }) {
10204
10681
  const state = useBrowserTabs((map) => map[bot.id] ?? EMPTY_TABS);
10205
10682
  const pages = useBrowserPages((map) => map);
10206
- (0, import_react28.useEffect)(() => {
10683
+ (0, import_react29.useEffect)(() => {
10207
10684
  actions.browserEnsure?.(bot.id);
10208
10685
  }, [bot.id]);
10209
10686
  if (roster.browser !== true || !actions.browserShow) {
10210
- return (0, import_react28.createElement)("div", { className: "bt-muted" }, t("The browser is in the DSH Desktop app."));
10687
+ return (0, import_react29.createElement)("div", { className: "bt-muted" }, t("The browser is in the DSH Desktop app."));
10211
10688
  }
10212
10689
  if (state.ids.length === 0) {
10213
- return (0, import_react28.createElement)(
10690
+ return (0, import_react29.createElement)(
10214
10691
  "div",
10215
10692
  { className: "bt-browser-empty-mark" },
10216
- (0, import_react28.createElement)(BotMark, { bot, size: 40, state: "idle", live: true }),
10217
- (0, import_react28.createElement)("div", { className: "bt-browser-empty-title" }, t("{name}'s browser", { name: bot.name })),
10218
- (0, import_react28.createElement)("div", { className: "bt-muted" }, t("Open a page here and {name} can read it with you. Logins and cookies stay with {name}; other Bots never see them.", { name: bot.name })),
10219
- (0, import_react28.createElement)("button", { type: "button", className: "bt-browser-retry", onClick: () => actions.openBrowser(sessionId) }, t("Open browser"))
10693
+ (0, import_react29.createElement)(BotMark, { bot, size: 40, state: "idle", live: true }),
10694
+ (0, import_react29.createElement)("div", { className: "bt-browser-empty-title" }, t("{name}'s browser", { name: bot.name })),
10695
+ (0, import_react29.createElement)("div", { className: "bt-muted" }, t("Open a page here and {name} can read it with you. Logins and cookies stay with {name}; other Bots never see them.", { name: bot.name })),
10696
+ (0, import_react29.createElement)("button", { type: "button", className: "bt-browser-retry", onClick: () => actions.openBrowser(sessionId) }, t("Open browser"))
10220
10697
  );
10221
10698
  }
10222
- return (0, import_react28.createElement)(
10699
+ return (0, import_react29.createElement)(
10223
10700
  "div",
10224
10701
  { className: "bt-bcards" },
10225
10702
  state.ids.map((tabId, index) => {
10226
10703
  const page = pages[tabId] ?? {};
10227
10704
  const label = page.title || shortUrl(page.url) || t("New tab");
10228
- return (0, import_react28.createElement)(
10705
+ return (0, import_react29.createElement)(
10229
10706
  "div",
10230
10707
  { key: tabId, className: "bt-bcard", "data-active": state.active === tabId || void 0, style: { animationDelay: `${index * 30}ms` } },
10231
- (0, import_react28.createElement)(
10708
+ (0, import_react29.createElement)(
10232
10709
  "button",
10233
10710
  { type: "button", className: "bt-bcard-shot", "aria-label": t("Open {title}", { title: label }), title: page.url, onClick: () => actions.browserPick(sessionId, tabId) },
10234
- page.excerpt ? (0, import_react28.createElement)(
10711
+ page.excerpt ? (0, import_react29.createElement)(
10235
10712
  "span",
10236
10713
  { className: "bt-bcard-preview" },
10237
- (0, import_react28.createElement)("span", { className: "bt-bcard-ptitle" }, label),
10238
- (0, import_react28.createElement)("span", { className: "bt-bcard-excerpt" }, page.excerpt)
10239
- ) : (0, import_react28.createElement)("span", { className: "bt-bcard-blank" }, (0, import_react28.createElement)(GlobeIcon), (0, import_react28.createElement)("span", null, label))
10714
+ (0, import_react29.createElement)("span", { className: "bt-bcard-ptitle" }, label),
10715
+ (0, import_react29.createElement)("span", { className: "bt-bcard-excerpt" }, page.excerpt)
10716
+ ) : (0, import_react29.createElement)("span", { className: "bt-bcard-blank" }, (0, import_react29.createElement)(GlobeIcon), (0, import_react29.createElement)("span", null, label))
10240
10717
  ),
10241
- (0, import_react28.createElement)("button", {
10718
+ (0, import_react29.createElement)("button", {
10242
10719
  type: "button",
10243
10720
  className: "bt-bcard-x",
10244
10721
  "aria-label": t("Close tab"),
@@ -10247,78 +10724,87 @@ function BrowserCards({ bot, sessionId, roster, actions, useBrowserTabs, useBrow
10247
10724
  void actions.browserCloseTab(bot.id, tabId);
10248
10725
  }
10249
10726
  }, "×"),
10250
- (0, import_react28.createElement)(
10727
+ (0, import_react29.createElement)(
10251
10728
  "div",
10252
10729
  { className: "bt-bcard-row" },
10253
- (0, import_react28.createElement)(Favicon, { src: page.favicon, className: "bt-bcard-fav" }),
10254
- (0, import_react28.createElement)("span", { className: "bt-bcard-title" }, label),
10255
- page.url ? (0, import_react28.createElement)("span", { className: "bt-bcard-host" }, hostOf(page.url)) : null
10730
+ (0, import_react29.createElement)(Favicon, { src: page.favicon, className: "bt-bcard-fav" }),
10731
+ (0, import_react29.createElement)("span", { className: "bt-bcard-title" }, label),
10732
+ page.url ? (0, import_react29.createElement)("span", { className: "bt-bcard-host" }, hostOf(page.url)) : null
10256
10733
  )
10257
10734
  );
10258
10735
  }),
10259
- (0, import_react28.createElement)("button", { type: "button", className: "bt-bcard-new", style: { animationDelay: `${state.ids.length * 30}ms` }, onClick: () => actions.browserNewTab(sessionId) }, (0, import_react28.createElement)(PlusIcon), t("New tab"))
10736
+ (0, import_react29.createElement)("button", { type: "button", className: "bt-bcard-new", style: { animationDelay: `${state.ids.length * 30}ms` }, onClick: () => actions.browserNewTab(sessionId) }, (0, import_react29.createElement)(PlusIcon), t("New tab"))
10260
10737
  );
10261
10738
  }
10262
10739
  function About({ text, title = "About" }) {
10263
- const [open, setOpen] = (0, import_react28.useState)(false);
10740
+ const [open, setOpen] = (0, import_react29.useState)(false);
10264
10741
  const long = text.split("\n").length > 5 || text.length > 240;
10265
- const body = (0, import_react28.useRef)(null);
10742
+ const body = (0, import_react29.useRef)(null);
10266
10743
  useMorph(body, open);
10267
- return (0, import_react28.createElement)(
10744
+ return (0, import_react29.createElement)(
10268
10745
  "div",
10269
10746
  null,
10270
- (0, import_react28.createElement)("div", { className: "bt-section-title" }, t(title)),
10271
- (0, import_react28.createElement)("div", { ref: body, className: "bt-about", "data-open": open || void 0 }, text),
10272
- long ? (0, import_react28.createElement)("button", { type: "button", className: "bt-more", onClick: () => setOpen((value) => !value) }, open ? t("Show less") : t("Show more")) : null
10747
+ (0, import_react29.createElement)("div", { className: "bt-section-title" }, t(title)),
10748
+ (0, import_react29.createElement)("div", { ref: body, className: "bt-about", "data-open": open || void 0 }, text),
10749
+ long ? (0, import_react29.createElement)("button", { type: "button", className: "bt-more", onClick: () => setOpen((value) => !value) }, open ? t("Show less") : t("Show more")) : null
10273
10750
  );
10274
10751
  }
10275
- function GroupOverview({ room: room2, roster, actions, usage, openSettings }) {
10752
+ function GroupOverview({ room: room2, roster, actions, usage, useWork, openSettings }) {
10276
10753
  const members = room2.members.map((id) => roster.byId[id]).filter(Boolean);
10754
+ const { entries: entries2, now } = useMemberWork(room2, useWork);
10277
10755
  return [
10278
- room2.notice ? (0, import_react28.createElement)(About, { key: "notice", title: "Notice", text: room2.notice }) : (0, import_react28.createElement)("button", { key: "notice", type: "button", className: "bt-more", onClick: openSettings }, t("Add a notice in group settings")),
10279
- (0, import_react28.createElement)(
10756
+ room2.notice ? (0, import_react29.createElement)(About, { key: "notice", title: "Notice", text: room2.notice }) : (0, import_react29.createElement)("button", { key: "notice", type: "button", className: "bt-more", onClick: openSettings }, t("Add a notice in group settings")),
10757
+ (0, import_react29.createElement)(
10280
10758
  "div",
10281
10759
  { key: "members" },
10282
- (0, import_react28.createElement)("div", { className: "bt-section-title" }, t("Members · {count}", { count: members.length })),
10283
- (0, import_react28.createElement)("div", { className: "bt-members", role: "list" }, members.map((member) => (0, import_react28.createElement)(
10284
- "div",
10285
- { key: member.id, className: "bt-member", role: "listitem" },
10286
- (0, import_react28.createElement)(
10287
- "button",
10288
- { type: "button", className: "bt-member-name", title: t("Open {name}'s chat", { name: member.name }), onClick: () => actions.openSession(member.id) },
10289
- (0, import_react28.createElement)(BotAvatar, { bot: member, size: 26, main: isMainOf(roster, member.id), admin: room2.admin === member.id, live: false }),
10290
- (0, import_react28.createElement)("span", null, member.name)
10291
- ),
10292
- room2.admin === member.id ? (0, import_react28.createElement)("span", { className: "bt-chip bt-chip-admin" }, t("Admin")) : null
10293
- )))
10760
+ (0, import_react29.createElement)("div", { className: "bt-section-title" }, t("Members · {count}", { count: members.length })),
10761
+ (0, import_react29.createElement)("div", { className: "bt-members", role: "list" }, members.map((member) => {
10762
+ const entry = entries2.find((item) => item.id === member.id);
10763
+ return (0, import_react29.createElement)(
10764
+ "div",
10765
+ { key: member.id, role: "listitem" },
10766
+ (0, import_react29.createElement)(
10767
+ "div",
10768
+ { className: "bt-member" },
10769
+ (0, import_react29.createElement)(
10770
+ "button",
10771
+ { type: "button", className: "bt-member-name", title: t("Open {name}'s chat", { name: member.name }), onClick: () => actions.openSession(member.id) },
10772
+ (0, import_react29.createElement)(BotAvatar, { bot: member, size: 26, main: isMainOf(roster, member.id), admin: room2.admin === member.id, live: false }),
10773
+ (0, import_react29.createElement)("span", null, member.name)
10774
+ ),
10775
+ room2.admin === member.id ? (0, import_react29.createElement)("span", { className: "bt-chip bt-chip-admin" }, t("Admin")) : null
10776
+ ),
10777
+ entry ? (0, import_react29.createElement)(MemberWork, { entry, bot: member, room: room2, roster, actions, now }) : null
10778
+ );
10779
+ }))
10294
10780
  ),
10295
- (0, import_react28.createElement)(
10781
+ (0, import_react29.createElement)(
10296
10782
  "div",
10297
10783
  { key: "mode" },
10298
- (0, import_react28.createElement)("div", { className: "bt-section-title" }, t("Who answers")),
10299
- (0, import_react28.createElement)("div", { className: "bt-kv" }, (0, import_react28.createElement)("span", null, t(ROOM_MODES.find(([id]) => id === shownMode(room2, roster))?.[1] ?? "Everyone")))
10784
+ (0, import_react29.createElement)("div", { className: "bt-section-title" }, t("Who answers")),
10785
+ (0, import_react29.createElement)("div", { className: "bt-kv" }, (0, import_react29.createElement)("span", null, t(ROOM_MODES.find(([id]) => id === shownMode(room2, roster))?.[1] ?? "Everyone")))
10300
10786
  ),
10301
- usage ? (0, import_react28.createElement)(
10787
+ usage ? (0, import_react29.createElement)(
10302
10788
  "div",
10303
10789
  { key: "usage" },
10304
- (0, import_react28.createElement)("div", { className: "bt-section-title" }, t("Usage")),
10305
- (0, import_react28.createElement)("div", { className: "bt-kv" }, (0, import_react28.createElement)("span", null, room2.name), (0, import_react28.createElement)("span", null, usage))
10790
+ (0, import_react29.createElement)("div", { className: "bt-section-title" }, t("Usage")),
10791
+ (0, import_react29.createElement)("div", { className: "bt-kv" }, (0, import_react29.createElement)("span", null, room2.name), (0, import_react29.createElement)("span", null, usage))
10306
10792
  ) : null
10307
10793
  ];
10308
10794
  }
10309
- function DetailsDrawer({ sessionId, startRenaming, startTab, hasStrip, roster, actions, useSessions, useSessionStatus, useActivity, useBrowserTabs, useBrowserPages }) {
10795
+ function DetailsDrawer({ sessionId, startRenaming, startTab, hasStrip, roster, actions, useSessions, useSessionStatus, useActivity, useWork, useBrowserTabs, useBrowserPages }) {
10310
10796
  const owner = roster.byId[sessionId];
10311
10797
  const part = owner ? currentPart(owner) : sessionId;
10312
10798
  const isMainBot = isMainOf(roster, owner?.id);
10313
- const [tab, setTab] = (0, import_react28.useState)("details");
10799
+ const [tab, setTab] = (0, import_react29.useState)("details");
10314
10800
  const room2 = roster.roomsById[sessionId];
10315
- const [view, setView] = (0, import_react28.useState)(startTab === "settings" && (owner || room2) ? "settings" : "overview");
10316
- const [editing, setEditing] = (0, import_react28.useState)(Boolean(startRenaming && (roster.byId[sessionId] || roster.roomsById[sessionId])));
10317
- const [draftName, setDraftName] = (0, import_react28.useState)(roster.byId[sessionId]?.name ?? (roster.roomsById[sessionId]?.named ? roster.roomsById[sessionId].name : ""));
10318
- const [copied, setCopied] = (0, import_react28.useState)(false);
10319
- const [error, setError] = (0, import_react28.useState)("");
10801
+ const [view, setView] = (0, import_react29.useState)(startTab === "settings" && (owner || room2) ? "settings" : "overview");
10802
+ const [editing, setEditing] = (0, import_react29.useState)(Boolean(startRenaming && (roster.byId[sessionId] || roster.roomsById[sessionId])));
10803
+ const [draftName, setDraftName] = (0, import_react29.useState)(roster.byId[sessionId]?.name ?? (roster.roomsById[sessionId]?.named ? roster.roomsById[sessionId].name : ""));
10804
+ const [copied, setCopied] = (0, import_react29.useState)(false);
10805
+ const [error, setError] = (0, import_react29.useState)("");
10320
10806
  const glide = useGlide(tab);
10321
- const last = (0, import_react28.useRef)({ view, tab, move: void 0 });
10807
+ const last = (0, import_react29.useRef)({ view, tab, move: void 0 });
10322
10808
  if (last.current.view !== view) last.current = { view, tab, move: view === "settings" ? "in" : "out" };
10323
10809
  else if (last.current.tab !== tab) last.current = { view, tab, move: "tab" };
10324
10810
  const move = last.current.move;
@@ -10343,36 +10829,36 @@ function DetailsDrawer({ sessionId, startRenaming, startTab, hasStrip, roster, a
10343
10829
  setTimeout(() => setCopied(false), 1600);
10344
10830
  });
10345
10831
  };
10346
- const errorLine = error ? (0, import_react28.createElement)("div", { className: "bt-error", role: "alert", style: { padding: 0 } }, error) : null;
10832
+ const errorLine = error ? (0, import_react29.createElement)("div", { className: "bt-error", role: "alert", style: { padding: 0 } }, error) : null;
10347
10833
  if ((bot || room2) && view === "settings") {
10348
10834
  return [
10349
- (0, import_react28.createElement)(
10835
+ (0, import_react29.createElement)(
10350
10836
  "div",
10351
10837
  { key: "head", className: "bt-subhead", "data-move": move },
10352
- (0, import_react28.createElement)("button", { type: "button", className: "bt-icon-btn", "aria-label": t("Back to details"), onClick: () => setView("overview") }, (0, import_react28.createElement)(ChevronLeftIcon)),
10353
- (0, import_react28.createElement)("span", { className: "bt-subhead-title" }, bot ? t("Settings") : t("Group settings")),
10354
- hasStrip ? null : (0, import_react28.createElement)("button", { type: "button", className: "bt-icon-btn", "aria-label": t("Close details"), onClick: close }, (0, import_react28.createElement)(CloseIcon))
10838
+ (0, import_react29.createElement)("button", { type: "button", className: "bt-icon-btn", "aria-label": t("Back to details"), onClick: () => setView("overview") }, (0, import_react29.createElement)(ChevronLeftIcon)),
10839
+ (0, import_react29.createElement)("span", { className: "bt-subhead-title" }, bot ? t("Settings") : t("Group settings")),
10840
+ hasStrip ? null : (0, import_react29.createElement)("button", { type: "button", className: "bt-icon-btn", "aria-label": t("Close details"), onClick: close }, (0, import_react29.createElement)(CloseIcon))
10355
10841
  ),
10356
- (0, import_react28.createElement)(
10842
+ (0, import_react29.createElement)(
10357
10843
  "div",
10358
10844
  { key: "set-body", className: "bt-drawer-body bt-set-body", "data-move": move },
10359
10845
  errorLine,
10360
- bot ? (0, import_react28.createElement)(BotSettingsForm, { bot, roster, actions, run }) : (0, import_react28.createElement)(GroupSettingsForm, { room: room2, roster, actions, run, onDeleted: close })
10846
+ bot ? (0, import_react29.createElement)(BotSettingsForm, { bot, roster, actions, run }) : (0, import_react29.createElement)(GroupSettingsForm, { room: room2, roster, actions, run, onDeleted: close })
10361
10847
  )
10362
10848
  ];
10363
10849
  }
10364
- const top = (0, import_react28.createElement)(
10850
+ const top = (0, import_react29.createElement)(
10365
10851
  "div",
10366
10852
  { className: "bt-drawer-top", "data-move": side },
10367
- bot || room2 ? (0, import_react28.createElement)("button", { type: "button", className: "bt-round", "aria-label": bot ? t("Edit Bot") : t("Group settings"), title: bot ? t("Edit Bot") : t("Group settings"), "data-info-row": "settings", onClick: () => setView("settings") }, (0, import_react28.createElement)(GearIcon)) : null,
10368
- bot && roster.browser === true && actions.openBrowser ? (0, import_react28.createElement)("button", { type: "button", className: "bt-round", "aria-label": t("Open browser"), title: t("Open this Bot's browser"), onClick: () => actions.openBrowser(sessionId) }, (0, import_react28.createElement)(GlobeIcon)) : null,
10369
- bot || room2 ? (0, import_react28.createElement)("button", { type: "button", className: "bt-round", "aria-label": copied ? t("Copied conversation") : t("Share conversation"), title: copied ? t("Copied") : t("Copy conversation as Markdown"), onClick: share }, copied ? (0, import_react28.createElement)("span", { key: "copied", className: "bt-pop-in" }, "✓") : (0, import_react28.createElement)(ShareIcon)) : null,
10370
- hasStrip ? null : (0, import_react28.createElement)("button", { type: "button", className: "bt-round", "aria-label": t("Close details"), onClick: close }, (0, import_react28.createElement)(CloseIcon))
10853
+ bot || room2 ? (0, import_react29.createElement)("button", { type: "button", className: "bt-round", "aria-label": bot ? t("Edit Bot") : t("Group settings"), title: bot ? t("Edit Bot") : t("Group settings"), "data-info-row": "settings", onClick: () => setView("settings") }, (0, import_react29.createElement)(GearIcon)) : null,
10854
+ bot && roster.browser === true && actions.openBrowser ? (0, import_react29.createElement)("button", { type: "button", className: "bt-round", "aria-label": t("Open browser"), title: t("Open this Bot's browser"), onClick: () => actions.openBrowser(sessionId) }, (0, import_react29.createElement)(GlobeIcon)) : null,
10855
+ bot || room2 ? (0, import_react29.createElement)("button", { type: "button", className: "bt-round", "aria-label": copied ? t("Copied conversation") : t("Share conversation"), title: copied ? t("Copied") : t("Copy conversation as Markdown"), onClick: share }, copied ? (0, import_react29.createElement)("span", { key: "copied", className: "bt-pop-in" }, "✓") : (0, import_react29.createElement)(ShareIcon)) : null,
10856
+ hasStrip ? null : (0, import_react29.createElement)("button", { type: "button", className: "bt-round", "aria-label": t("Close details"), onClick: close }, (0, import_react29.createElement)(CloseIcon))
10371
10857
  );
10372
10858
  if (!bot && !room2) {
10373
10859
  return [
10374
- (0, import_react28.createElement)(import_react28.Fragment, { key: "top" }, top),
10375
- (0, import_react28.createElement)("div", { key: "body", className: "bt-drawer-body" }, (0, import_react28.createElement)("div", { className: "bt-muted" }, t("This conversation is not part of your Bot team.")))
10860
+ (0, import_react29.createElement)(import_react29.Fragment, { key: "top" }, top),
10861
+ (0, import_react29.createElement)("div", { key: "body", className: "bt-drawer-body" }, (0, import_react29.createElement)("div", { className: "bt-muted" }, t("This conversation is not part of your Bot team.")))
10376
10862
  ];
10377
10863
  }
10378
10864
  const current = bot?.name ?? room2.name;
@@ -10386,12 +10872,12 @@ function DetailsDrawer({ sessionId, startRenaming, startTab, hasStrip, roster, a
10386
10872
  const subtitle = bot ? bot.role : roomSubtitle(room2, roster);
10387
10873
  const tabs = bot ? ["details", "browser"] : ["details"];
10388
10874
  return [
10389
- (0, import_react28.createElement)(import_react28.Fragment, { key: "top" }, top),
10390
- (0, import_react28.createElement)(
10875
+ (0, import_react29.createElement)(import_react29.Fragment, { key: "top" }, top),
10876
+ (0, import_react29.createElement)(
10391
10877
  "div",
10392
10878
  { key: "id", className: "bt-drawer-id", "data-move": side },
10393
- bot ? (0, import_react28.createElement)(BotAvatar, { bot, size: 64, state: botState(running, doing, roster.questions?.[part] !== void 0), main: isMainBot, badge: false, gaze: true, pokeable: true }) : (0, import_react28.createElement)(RoomAvatar, { room: room2, roster, size: 64 }),
10394
- editing ? (0, import_react28.createElement)("input", {
10879
+ bot ? (0, import_react29.createElement)(BotAvatar, { bot, size: 64, state: botState(running, doing, roster.questions?.[part] !== void 0), main: isMainBot, badge: false, gaze: true, pokeable: true }) : (0, import_react29.createElement)(RoomAvatar, { room: room2, roster, size: 64 }),
10880
+ editing ? (0, import_react29.createElement)("input", {
10395
10881
  className: "bt-drawer-name-input",
10396
10882
  autoFocus: true,
10397
10883
  value: draftName,
@@ -10406,49 +10892,49 @@ function DetailsDrawer({ sessionId, startRenaming, startTab, hasStrip, roster, a
10406
10892
  setEditing(false);
10407
10893
  }
10408
10894
  }
10409
- }) : (0, import_react28.createElement)("button", { type: "button", className: "bt-drawer-name", "aria-label": t("Rename {name}", { name: current }), title: t("Rename"), onClick: () => {
10895
+ }) : (0, import_react29.createElement)("button", { type: "button", className: "bt-drawer-name", "aria-label": t("Rename {name}", { name: current }), title: t("Rename"), onClick: () => {
10410
10896
  setDraftName(room2 && !room2.named ? "" : current);
10411
10897
  setEditing(true);
10412
10898
  } }, current),
10413
- subtitle ? (0, import_react28.createElement)("span", { className: "bt-drawer-role" }, subtitle) : null
10899
+ subtitle ? (0, import_react29.createElement)("span", { className: "bt-drawer-role" }, subtitle) : null
10414
10900
  ),
10415
- (0, import_react28.createElement)(
10901
+ (0, import_react29.createElement)(
10416
10902
  "div",
10417
10903
  { key: "tabs", ref: glide.box, className: "bt-tabs", role: "tablist", "data-move": side },
10418
- (0, import_react28.createElement)("span", { ref: glide.thumb, className: "bt-thumb", "aria-hidden": true }),
10419
- tabs.map((key) => (0, import_react28.createElement)("button", { key, type: "button", role: "tab", className: "bt-tab", "aria-selected": tab === key, onClick: () => setTab(key) }, t(TAB_LABELS[key])))
10904
+ (0, import_react29.createElement)("span", { ref: glide.thumb, className: "bt-thumb", "aria-hidden": true }),
10905
+ tabs.map((key) => (0, import_react29.createElement)("button", { key, type: "button", role: "tab", className: "bt-tab", "aria-selected": tab === key, onClick: () => setTab(key) }, t(TAB_LABELS[key])))
10420
10906
  ),
10421
- (0, import_react28.createElement)(
10907
+ (0, import_react29.createElement)(
10422
10908
  "div",
10423
10909
  { key: `body:${tab}`, className: "bt-drawer-body", "data-move": move },
10424
10910
  errorLine,
10425
- room2 && tab === "details" ? (0, import_react28.createElement)(GroupOverview, { room: room2, roster, actions, usage, openSettings: () => setView("settings") }) : null,
10911
+ room2 && tab === "details" ? (0, import_react29.createElement)(GroupOverview, { room: room2, roster, actions, usage, useWork, openSettings: () => setView("settings") }) : null,
10426
10912
  bot && tab === "details" ? [
10427
- (0, import_react28.createElement)(
10913
+ (0, import_react29.createElement)(
10428
10914
  "div",
10429
10915
  { key: "model", className: "bt-drawer-model" },
10430
- (0, import_react28.createElement)("div", { className: "bt-section-title" }, t("Model")),
10431
- (0, import_react28.createElement)(ModelMenu, { bot, roster, actions, run, wide: true }),
10432
- modelOf(bot, roster) ? null : (0, import_react28.createElement)("div", { className: "bt-note" }, t("Pick a model and {name} can start.", { name: bot.name }))
10916
+ (0, import_react29.createElement)("div", { className: "bt-section-title" }, t("Model")),
10917
+ (0, import_react29.createElement)(ModelMenu, { bot, roster, actions, run, wide: true }),
10918
+ modelOf(bot, roster) ? null : (0, import_react29.createElement)("div", { className: "bt-note" }, t("Pick a model and {name} can start.", { name: bot.name }))
10433
10919
  ),
10434
- (0, import_react28.createElement)(PermissionRow, { key: "permission", bot, roster, actions, run }),
10435
- bot.instructions ? (0, import_react28.createElement)(About, { key: "about", text: bot.instructions }) : (0, import_react28.createElement)("button", { key: "about", type: "button", className: "bt-more", onClick: () => setView("settings") }, t("Add instructions in Bot settings")),
10436
- roster.memory === false ? null : (0, import_react28.createElement)(MemoryRow, { key: "memory", bot, actions }),
10437
- usage ? (0, import_react28.createElement)(
10920
+ (0, import_react29.createElement)(PermissionRow, { key: "permission", bot, roster, actions, run }),
10921
+ bot.instructions ? (0, import_react29.createElement)(About, { key: "about", text: bot.instructions }) : (0, import_react29.createElement)("button", { key: "about", type: "button", className: "bt-more", onClick: () => setView("settings") }, t("Add instructions in Bot settings")),
10922
+ roster.memory === false ? null : (0, import_react29.createElement)(MemoryRow, { key: "memory", bot, actions }),
10923
+ usage ? (0, import_react29.createElement)(
10438
10924
  "div",
10439
10925
  { key: "usage" },
10440
- (0, import_react28.createElement)("div", { className: "bt-section-title" }, t("Usage")),
10441
- (0, import_react28.createElement)("div", { className: "bt-kv" }, (0, import_react28.createElement)("span", null, bot.name), (0, import_react28.createElement)("span", null, usage))
10926
+ (0, import_react29.createElement)("div", { className: "bt-section-title" }, t("Usage")),
10927
+ (0, import_react29.createElement)("div", { className: "bt-kv" }, (0, import_react29.createElement)("span", null, bot.name), (0, import_react29.createElement)("span", null, usage))
10442
10928
  ) : null,
10443
- (0, import_react28.createElement)(ScheduleSummary, { key: "routines", bot, actions })
10929
+ (0, import_react29.createElement)(ScheduleSummary, { key: "routines", bot, actions })
10444
10930
  ] : null,
10445
- bot && tab === "browser" ? (0, import_react28.createElement)(BrowserCards, { bot, sessionId, roster, actions, useBrowserTabs, useBrowserPages }) : null
10931
+ bot && tab === "browser" ? (0, import_react29.createElement)(BrowserCards, { bot, sessionId, roster, actions, useBrowserTabs, useBrowserPages }) : null
10446
10932
  )
10447
10933
  ];
10448
10934
  }
10449
10935
 
10450
10936
  // src/client/browser-pane.js
10451
- var import_react29 = require("react");
10937
+ var import_react30 = require("react");
10452
10938
  var import_react_dom7 = require("react-dom");
10453
10939
  var PANEL_LINGER_MS = 300;
10454
10940
  var WIDTH_KEY = STORAGE.browserWidth;
@@ -10463,10 +10949,10 @@ var EMPTY_TABS2 = { ids: [], active: null };
10463
10949
  var SPRING_LEAD2 = springEasing(576, 39.4);
10464
10950
  var SPRING_TRAIL2 = springEasing(144, 19.7);
10465
10951
  var icon = (paths) => function BrowserIcon() {
10466
- return (0, import_react29.createElement)(
10952
+ return (0, import_react30.createElement)(
10467
10953
  "svg",
10468
10954
  { width: 16, height: 16, viewBox: "0 0 20 20", fill: "none", stroke: "currentColor", strokeWidth: 1.5, strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true },
10469
- paths.map((d, index) => (0, import_react29.createElement)("path", { key: index, d }))
10955
+ paths.map((d, index) => (0, import_react30.createElement)("path", { key: index, d }))
10470
10956
  );
10471
10957
  };
10472
10958
  var GlobeIcon = icon(["M10 3a7 7 0 1 0 0 14 7 7 0 0 0 0-14z", "M3 10h14", "M10 3c2 2.2 2.9 4.5 2.9 7S12 14.8 10 17c-2-2.2-2.9-4.5-2.9-7S8 5.2 10 3z"]);
@@ -10478,9 +10964,9 @@ var CloseIcon2 = icon(["M5.5 5.5l9 9M14.5 5.5l-9 9"]);
10478
10964
  var PlusTabIcon = icon(["M10 5v10M5 10h10"]);
10479
10965
  var LockIcon2 = icon(["M6.5 9V7a3.5 3.5 0 0 1 7 0v2", "M5.5 9h9v7h-9z"]);
10480
10966
  function Favicon({ src, className }) {
10481
- const [failed, setFailed] = (0, import_react29.useState)(null);
10482
- if (!src || failed === src) return (0, import_react29.createElement)("span", { className: `${className} bt-ptab-globe` }, (0, import_react29.createElement)(GlobeIcon));
10483
- return (0, import_react29.createElement)("img", { className, src, alt: "", "aria-hidden": true, onError: () => setFailed(src) });
10967
+ const [failed, setFailed] = (0, import_react30.useState)(null);
10968
+ if (!src || failed === src) return (0, import_react30.createElement)("span", { className: `${className} bt-ptab-globe` }, (0, import_react30.createElement)(GlobeIcon));
10969
+ return (0, import_react30.createElement)("img", { className, src, alt: "", "aria-hidden": true, onError: () => setFailed(src) });
10484
10970
  }
10485
10971
  var savedWidth = () => {
10486
10972
  try {
@@ -10539,11 +11025,11 @@ var reducedMotion2 = () => typeof matchMedia === "function" && matchMedia("(pref
10539
11025
  var tabLabel = (page) => page.title || shortAddress(page.url) || t("New tab");
10540
11026
  function PanelStrip({ bot, subject, view, ids, active, reading, onView, actions, useBrowserPages }) {
10541
11027
  const pages = useBrowserPages((map) => map);
10542
- const wrap = (0, import_react29.useRef)(null);
10543
- const ind = (0, import_react29.useRef)(null);
10544
- const spot = (0, import_react29.useRef)(null);
10545
- const under = (0, import_react29.useRef)(null);
10546
- (0, import_react29.useLayoutEffect)(() => {
11028
+ const wrap = (0, import_react30.useRef)(null);
11029
+ const ind = (0, import_react30.useRef)(null);
11030
+ const spot = (0, import_react30.useRef)(null);
11031
+ const under = (0, import_react30.useRef)(null);
11032
+ (0, import_react30.useLayoutEffect)(() => {
10547
11033
  const box = wrap.current;
10548
11034
  const capsule = ind.current;
10549
11035
  if (!box || !capsule) return void 0;
@@ -10572,7 +11058,7 @@ function PanelStrip({ bot, subject, view, ids, active, reading, onView, actions,
10572
11058
  trail.cancel();
10573
11059
  };
10574
11060
  });
10575
- (0, import_react29.useEffect)(() => {
11061
+ (0, import_react30.useEffect)(() => {
10576
11062
  const box = wrap.current;
10577
11063
  if (!box) return void 0;
10578
11064
  const observer = new ResizeObserver(() => {
@@ -10586,13 +11072,13 @@ function PanelStrip({ bot, subject, view, ids, active, reading, onView, actions,
10586
11072
  observer.observe(box);
10587
11073
  return () => observer.disconnect();
10588
11074
  }, []);
10589
- return (0, import_react29.createElement)(
11075
+ return (0, import_react30.createElement)(
10590
11076
  "div",
10591
11077
  { className: "bt-ptabs", role: "tablist", "aria-label": t("Panel tabs") },
10592
- (0, import_react29.createElement)(
11078
+ (0, import_react30.createElement)(
10593
11079
  "div",
10594
11080
  { className: "bt-ptabs-in", ref: wrap },
10595
- (0, import_react29.createElement)(
11081
+ (0, import_react30.createElement)(
10596
11082
  "button",
10597
11083
  {
10598
11084
  type: "button",
@@ -10602,13 +11088,13 @@ function PanelStrip({ bot, subject, view, ids, active, reading, onView, actions,
10602
11088
  title: bot.name,
10603
11089
  onClick: () => onView("details")
10604
11090
  },
10605
- (0, import_react29.createElement)(BotMark, { bot, size: 20, state: reading ? "searching" : "idle" }),
10606
- view === "details" ? (0, import_react29.createElement)("span", { className: "bt-ptab-name" }, bot.name) : null
11091
+ (0, import_react30.createElement)(BotMark, { bot, size: 20, state: reading ? "searching" : "idle" }),
11092
+ view === "details" ? (0, import_react30.createElement)("span", { className: "bt-ptab-name" }, bot.name) : null
10607
11093
  ),
10608
11094
  ids.map((tabId) => {
10609
11095
  const page = pages[tabId] ?? EMPTY_PAGE;
10610
11096
  const on = view === "browser" && tabId === active;
10611
- return (0, import_react29.createElement)(
11097
+ return (0, import_react30.createElement)(
10612
11098
  "button",
10613
11099
  {
10614
11100
  key: tabId,
@@ -10629,9 +11115,9 @@ function PanelStrip({ bot, subject, view, ids, active, reading, onView, actions,
10629
11115
  }
10630
11116
  }
10631
11117
  },
10632
- (0, import_react29.createElement)(Favicon, { src: page.favicon, className: "bt-ptab-fav" }),
10633
- (0, import_react29.createElement)("span", { className: "bt-ptab-title" }, tabLabel(page)),
10634
- (0, import_react29.createElement)("span", {
11118
+ (0, import_react30.createElement)(Favicon, { src: page.favicon, className: "bt-ptab-fav" }),
11119
+ (0, import_react30.createElement)("span", { className: "bt-ptab-title" }, tabLabel(page)),
11120
+ (0, import_react30.createElement)("span", {
10635
11121
  className: "bt-ptab-x",
10636
11122
  role: "button",
10637
11123
  "aria-label": t("Close tab"),
@@ -10643,20 +11129,20 @@ function PanelStrip({ bot, subject, view, ids, active, reading, onView, actions,
10643
11129
  }, "×")
10644
11130
  );
10645
11131
  }),
10646
- (0, import_react29.createElement)("span", { ref: ind, className: "bt-ptab-ind", "aria-hidden": true })
11132
+ (0, import_react30.createElement)("span", { ref: ind, className: "bt-ptab-ind", "aria-hidden": true })
10647
11133
  ),
10648
11134
  // New tab and Close stay outside the scrolling tabs, so a narrow panel keeps both.
10649
- (0, import_react29.createElement)("button", { type: "button", className: "bt-ptab-new", "aria-label": t("New tab"), title: t("New tab"), onClick: () => actions.browserNewTab(subject) }, (0, import_react29.createElement)(PlusTabIcon)),
10650
- (0, import_react29.createElement)("button", { type: "button", className: "bt-icon-btn bt-ptab-close", "aria-label": t("Close panel"), title: t("Close panel"), onClick: () => actions.closePanel() }, (0, import_react29.createElement)(CloseIcon2))
11135
+ (0, import_react30.createElement)("button", { type: "button", className: "bt-ptab-new", "aria-label": t("New tab"), title: t("New tab"), onClick: () => actions.browserNewTab(subject) }, (0, import_react30.createElement)(PlusTabIcon)),
11136
+ (0, import_react30.createElement)("button", { type: "button", className: "bt-icon-btn bt-ptab-close", "aria-label": t("Close panel"), title: t("Close panel"), onClick: () => actions.closePanel() }, (0, import_react30.createElement)(CloseIcon2))
10651
11137
  );
10652
11138
  }
10653
11139
  function BrowserView({ bot, tabId, leaving, landed, actions, useBrowserPages }) {
10654
11140
  const page = useBrowserPages((pages) => pages[tabId ?? ""]) ?? EMPTY_PAGE;
10655
- const frame = (0, import_react29.useRef)(null);
10656
- const input = (0, import_react29.useRef)(null);
10657
- const [draft, setDraft] = (0, import_react29.useState)(null);
11141
+ const frame = (0, import_react30.useRef)(null);
11142
+ const input = (0, import_react30.useRef)(null);
11143
+ const [draft, setDraft] = (0, import_react30.useState)(null);
10658
11144
  const showGuest = landed && !leaving && Boolean(page.url) && !page.error;
10659
- (0, import_react29.useLayoutEffect)(() => {
11145
+ (0, import_react30.useLayoutEffect)(() => {
10660
11146
  const element = frame.current;
10661
11147
  if (!element || leaving || !tabId) {
10662
11148
  void actions.browserShow(null);
@@ -10682,7 +11168,7 @@ function BrowserView({ bot, tabId, leaving, landed, actions, useBrowserPages })
10682
11168
  window.removeEventListener("resize", schedule);
10683
11169
  };
10684
11170
  }, [tabId, showGuest, leaving]);
10685
- (0, import_react29.useEffect)(() => {
11171
+ (0, import_react30.useEffect)(() => {
10686
11172
  setDraft(null);
10687
11173
  if (landed && !page.url) input.current?.focus();
10688
11174
  }, [tabId, landed]);
@@ -10698,27 +11184,27 @@ function BrowserView({ bot, tabId, leaving, landed, actions, useBrowserPages })
10698
11184
  };
10699
11185
  const editing = draft !== null;
10700
11186
  const secure = /^https:/i.test(page.url);
10701
- return (0, import_react29.createElement)(
11187
+ return (0, import_react30.createElement)(
10702
11188
  "div",
10703
11189
  { className: "bt-panel-view bt-panel-browser", "aria-label": t("{name}'s browser", { name: bot.name }) },
10704
- (0, import_react29.createElement)(
11190
+ (0, import_react30.createElement)(
10705
11191
  "div",
10706
11192
  { className: "bt-browser-bar" },
10707
- (0, import_react29.createElement)("button", { type: "button", className: "bt-icon-btn", "aria-label": t("Back"), title: t("Back"), disabled: !page.canGoBack, onClick: () => actions.browserCommand(tabId, "back") }, (0, import_react29.createElement)(BackIcon)),
10708
- (0, import_react29.createElement)("button", { type: "button", className: "bt-icon-btn", "aria-label": t("Forward"), title: t("Forward"), disabled: !page.canGoForward, onClick: () => actions.browserCommand(tabId, "forward") }, (0, import_react29.createElement)(ForwardIcon)),
10709
- (0, import_react29.createElement)("button", {
11193
+ (0, import_react30.createElement)("button", { type: "button", className: "bt-icon-btn", "aria-label": t("Back"), title: t("Back"), disabled: !page.canGoBack, onClick: () => actions.browserCommand(tabId, "back") }, (0, import_react30.createElement)(BackIcon)),
11194
+ (0, import_react30.createElement)("button", { type: "button", className: "bt-icon-btn", "aria-label": t("Forward"), title: t("Forward"), disabled: !page.canGoForward, onClick: () => actions.browserCommand(tabId, "forward") }, (0, import_react30.createElement)(ForwardIcon)),
11195
+ (0, import_react30.createElement)("button", {
10710
11196
  type: "button",
10711
11197
  className: "bt-icon-btn",
10712
11198
  disabled: !page.url,
10713
11199
  "aria-label": page.loading ? t("Stop") : t("Reload"),
10714
11200
  title: page.loading ? t("Stop") : t("Reload"),
10715
11201
  onClick: () => actions.browserCommand(tabId, page.loading ? "stop" : "reload")
10716
- }, (0, import_react29.createElement)(page.loading ? StopIcon : ReloadIcon)),
10717
- (0, import_react29.createElement)(
11202
+ }, (0, import_react30.createElement)(page.loading ? StopIcon : ReloadIcon)),
11203
+ (0, import_react30.createElement)(
10718
11204
  "form",
10719
11205
  { className: "bt-browser-address", onSubmit: go, "data-editing": editing || void 0 },
10720
- (0, import_react29.createElement)("span", { className: "bt-browser-scheme", "aria-hidden": true }, (0, import_react29.createElement)(secure && !editing ? LockIcon2 : GlobeIcon)),
10721
- (0, import_react29.createElement)("input", {
11206
+ (0, import_react30.createElement)("span", { className: "bt-browser-scheme", "aria-hidden": true }, (0, import_react30.createElement)(secure && !editing ? LockIcon2 : GlobeIcon)),
11207
+ (0, import_react30.createElement)("input", {
10722
11208
  ref: input,
10723
11209
  type: "text",
10724
11210
  spellCheck: false,
@@ -10742,71 +11228,71 @@ function BrowserView({ bot, tabId, leaving, landed, actions, useBrowserPages })
10742
11228
  }
10743
11229
  })
10744
11230
  ),
10745
- page.loading ? (0, import_react29.createElement)("span", { className: "bt-browser-progress", "aria-hidden": true }) : null
11231
+ page.loading ? (0, import_react30.createElement)("span", { className: "bt-browser-progress", "aria-hidden": true }) : null
10746
11232
  ),
10747
- (0, import_react29.createElement)(
11233
+ (0, import_react30.createElement)(
10748
11234
  "div",
10749
11235
  { ref: frame, className: "bt-browser-frame", "data-guest": showGuest || void 0 },
10750
- page.error ? (0, import_react29.createElement)(
11236
+ page.error ? (0, import_react30.createElement)(
10751
11237
  "div",
10752
11238
  { className: "bt-browser-empty" },
10753
- (0, import_react29.createElement)("div", { className: "bt-browser-empty-title" }, t("This page didn’t load")),
10754
- (0, import_react29.createElement)("div", { className: "bt-browser-empty-text" }, t(page.error)),
10755
- page.url ? (0, import_react29.createElement)("button", { type: "button", className: "bt-browser-retry", onClick: () => {
11239
+ (0, import_react30.createElement)("div", { className: "bt-browser-empty-title" }, t("This page didn’t load")),
11240
+ (0, import_react30.createElement)("div", { className: "bt-browser-empty-text" }, t(page.error)),
11241
+ page.url ? (0, import_react30.createElement)("button", { type: "button", className: "bt-browser-retry", onClick: () => {
10756
11242
  void actions.browserNavigate(tabId, page.url);
10757
11243
  } }, t("Try again")) : null
10758
- ) : !page.url ? (0, import_react29.createElement)(
11244
+ ) : !page.url ? (0, import_react30.createElement)(
10759
11245
  "div",
10760
11246
  { className: "bt-browser-empty" },
10761
- (0, import_react29.createElement)(BotMark, { bot, size: 48, state: "idle", live: true }),
10762
- (0, import_react29.createElement)("div", { className: "bt-browser-empty-title" }, t("{name}'s browser", { name: bot.name })),
10763
- (0, import_react29.createElement)("div", { className: "bt-browser-empty-text" }, t("Open a page here and {name} can read it with you. Logins and cookies stay with {name}; other Bots never see them.", { name: bot.name }))
11247
+ (0, import_react30.createElement)(BotMark, { bot, size: 48, state: "idle", live: true }),
11248
+ (0, import_react30.createElement)("div", { className: "bt-browser-empty-title" }, t("{name}'s browser", { name: bot.name })),
11249
+ (0, import_react30.createElement)("div", { className: "bt-browser-empty-text" }, t("Open a page here and {name} can read it with you. Logins and cookies stay with {name}; other Bots never see them.", { name: bot.name }))
10764
11250
  ) : null
10765
11251
  )
10766
11252
  );
10767
11253
  }
10768
- function BotPanel({ pane, leaving, roster, actions, useSessions, useSessionStatus, useActivity, useBrowserTabs, useBrowserPages }) {
11254
+ function BotPanel({ pane, leaving, roster, actions, useSessions, useSessionStatus, useActivity, useWork, useBrowserTabs, useBrowserPages }) {
10769
11255
  const bot = roster.byId[pane.id];
10770
11256
  const canBrowse = Boolean(bot && roster.browser === true && actions.browserShow);
10771
11257
  const view = pane.view === "browser" && canBrowse ? "browser" : "details";
10772
11258
  const tabState = useBrowserTabs((map) => bot ? map[bot.id] ?? EMPTY_TABS2 : EMPTY_TABS2);
10773
11259
  const active = tabState.active;
10774
11260
  const page = useBrowserPages((pages) => (active ? pages[active] : null) ?? EMPTY_PAGE);
10775
- const root = (0, import_react29.useRef)(null);
10776
- const fly = (0, import_react29.useRef)(null);
10777
- const [landed, setLanded] = (0, import_react29.useState)(false);
10778
- const [settling, setSettling] = (0, import_react29.useState)(false);
10779
- const [width, setWidth] = (0, import_react29.useState)(savedWidth);
10780
- const [viewport, setViewport] = (0, import_react29.useState)(() => window.innerWidth);
10781
- const [dragging, setDragging] = (0, import_react29.useState)(false);
10782
- const [resizing, setResizing] = (0, import_react29.useState)(false);
11261
+ const root = (0, import_react30.useRef)(null);
11262
+ const fly = (0, import_react30.useRef)(null);
11263
+ const [landed, setLanded] = (0, import_react30.useState)(false);
11264
+ const [settling, setSettling] = (0, import_react30.useState)(false);
11265
+ const [width, setWidth] = (0, import_react30.useState)(savedWidth);
11266
+ const [viewport, setViewport] = (0, import_react30.useState)(() => window.innerWidth);
11267
+ const [dragging, setDragging] = (0, import_react30.useState)(false);
11268
+ const [resizing, setResizing] = (0, import_react30.useState)(false);
10783
11269
  const instant = dragging || resizing;
10784
- const [reading, setReading] = (0, import_react29.useState)(false);
11270
+ const [reading, setReading] = (0, import_react30.useState)(false);
10785
11271
  const panelWidth = fitWidth(width ?? (view === "browser" ? DEFAULT_WIDTH : DETAILS_WIDTH), widthRange(view, viewport));
10786
- (0, import_react29.useLayoutEffect)(() => {
11272
+ (0, import_react30.useLayoutEffect)(() => {
10787
11273
  if (view === "browser" && width === null) setWidth(DEFAULT_WIDTH);
10788
11274
  }, [view, width]);
10789
- (0, import_react29.useLayoutEffect)(() => {
11275
+ (0, import_react30.useLayoutEffect)(() => {
10790
11276
  chatRoom.hold(instant);
10791
11277
  }, [instant]);
10792
- const [last, setLast] = (0, import_react29.useState)({ view, width: panelWidth });
11278
+ const [last, setLast] = (0, import_react30.useState)({ view, width: panelWidth });
10793
11279
  if (last.view !== view || last.width !== panelWidth) {
10794
11280
  setLast({ view, width: panelWidth });
10795
11281
  if (last.view !== view) setSettling(last.width !== panelWidth && !instant);
10796
11282
  }
10797
- const shownView = (0, import_react29.useRef)(view);
10798
- (0, import_react29.useLayoutEffect)(() => {
11283
+ const shownView = (0, import_react30.useRef)(view);
11284
+ (0, import_react30.useLayoutEffect)(() => {
10799
11285
  const switched = settling && shownView.current !== view;
10800
11286
  shownView.current = view;
10801
11287
  if (switched) chatRoom.hold(true);
10802
11288
  chatRoom.set(leaving ? null : panelWidth);
10803
11289
  if (switched) chatRoom.hold(false);
10804
11290
  }, [view, panelWidth, leaving]);
10805
- (0, import_react29.useEffect)(() => () => {
11291
+ (0, import_react30.useEffect)(() => () => {
10806
11292
  chatRoom.set(null);
10807
11293
  chatRoom.hold(false);
10808
11294
  }, []);
10809
- (0, import_react29.useEffect)(() => {
11295
+ (0, import_react30.useEffect)(() => {
10810
11296
  let timer;
10811
11297
  const onResize = () => {
10812
11298
  setResizing(true);
@@ -10820,28 +11306,28 @@ function BotPanel({ pane, leaving, roster, actions, useSessions, useSessionStatu
10820
11306
  window.removeEventListener("resize", onResize);
10821
11307
  };
10822
11308
  }, []);
10823
- (0, import_react29.useEffect)(() => {
11309
+ (0, import_react30.useEffect)(() => {
10824
11310
  if (!settling) return void 0;
10825
11311
  const timer = setTimeout(() => setSettling(false), SPRING_SHAPE.duration + 40);
10826
11312
  return () => clearTimeout(timer);
10827
11313
  }, [settling, view]);
10828
- (0, import_react29.useEffect)(() => {
11314
+ (0, import_react30.useEffect)(() => {
10829
11315
  const timer = setTimeout(() => setLanded(true), 360);
10830
11316
  return () => clearTimeout(timer);
10831
11317
  }, []);
10832
- (0, import_react29.useEffect)(() => {
11318
+ (0, import_react30.useEffect)(() => {
10833
11319
  if (canBrowse) actions.browserEnsure(bot.id);
10834
11320
  }, [bot?.id, canBrowse]);
10835
- (0, import_react29.useEffect)(() => {
11321
+ (0, import_react30.useEffect)(() => {
10836
11322
  if (view === "browser" && (active === null || !tabState.ids.includes(active))) actions.setPanelView("details");
10837
11323
  }, [view, active, tabState.ids.length]);
10838
- (0, import_react29.useEffect)(() => {
11324
+ (0, import_react30.useEffect)(() => {
10839
11325
  if (view !== "browser" || leaving) void actions.browserShow?.(null);
10840
11326
  }, [view, leaving]);
10841
- (0, import_react29.useEffect)(() => () => {
11327
+ (0, import_react30.useEffect)(() => () => {
10842
11328
  void actions.browserShow?.(null);
10843
11329
  }, []);
10844
- (0, import_react29.useEffect)(() => {
11330
+ (0, import_react30.useEffect)(() => {
10845
11331
  if (!page.readAt || Date.now() - page.readAt > READ_FLASH_MS) return void 0;
10846
11332
  setReading(true);
10847
11333
  const timer = setTimeout(() => setReading(false), READ_FLASH_MS);
@@ -10878,7 +11364,7 @@ function BotPanel({ pane, leaving, roster, actions, useSessions, useSessionStatu
10878
11364
  return actions[name](...args);
10879
11365
  };
10880
11366
  }
10881
- (0, import_react29.useLayoutEffect)(() => {
11367
+ (0, import_react30.useLayoutEffect)(() => {
10882
11368
  const move = fly.current;
10883
11369
  if (!move) return;
10884
11370
  fly.current = null;
@@ -10961,7 +11447,7 @@ function BotPanel({ pane, leaving, roster, actions, useSessions, useSessionStatu
10961
11447
  for (const name of ends) grip.addEventListener(name, onEnd);
10962
11448
  };
10963
11449
  const hasStrip = canBrowse && tabState.ids.length > 0;
10964
- return (0, import_react29.createElement)(
11450
+ return (0, import_react30.createElement)(
10965
11451
  "aside",
10966
11452
  {
10967
11453
  ref: root,
@@ -10978,15 +11464,15 @@ function BotPanel({ pane, leaving, roster, actions, useSessions, useSessionStatu
10978
11464
  if (event.target === event.currentTarget) setLanded(true);
10979
11465
  }
10980
11466
  },
10981
- leaving ? null : (0, import_react29.createElement)("div", { className: "bt-panel-grip", role: "separator", "aria-orientation": "vertical", "aria-label": t("Resize panel"), onPointerDown: startResize }),
11467
+ leaving ? null : (0, import_react30.createElement)("div", { className: "bt-panel-grip", role: "separator", "aria-orientation": "vertical", "aria-label": t("Resize panel"), onPointerDown: startResize }),
10982
11468
  // Over the whole window while dragging: the cursor stays a resize cursor and the
10983
11469
  // pages underneath (the chat, the guest) see no pointer.
10984
- dragging ? (0, import_react_dom7.createPortal)((0, import_react29.createElement)("div", { className: "bt-drag-shield", "aria-hidden": true }), document.body) : null,
10985
- hasStrip ? (0, import_react29.createElement)(PanelStrip, { bot, subject: pane.id, view, ids: tabState.ids, active, reading, onView: switchView, actions: panelActions, useBrowserPages }) : null,
10986
- view === "browser" && bot ? (0, import_react29.createElement)(BrowserView, { key: `browser:${active}`, bot, tabId: active, leaving, landed: landed && !settling, actions, useBrowserPages }) : (0, import_react29.createElement)(
11470
+ dragging ? (0, import_react_dom7.createPortal)((0, import_react30.createElement)("div", { className: "bt-drag-shield", "aria-hidden": true }), document.body) : null,
11471
+ hasStrip ? (0, import_react30.createElement)(PanelStrip, { bot, subject: pane.id, view, ids: tabState.ids, active, reading, onView: switchView, actions: panelActions, useBrowserPages }) : null,
11472
+ view === "browser" && bot ? (0, import_react30.createElement)(BrowserView, { key: `browser:${active}`, bot, tabId: active, leaving, landed: landed && !settling, actions, useBrowserPages }) : (0, import_react30.createElement)(
10987
11473
  "div",
10988
11474
  { key: `details:${pane.id}:${pane.renaming ? "rename" : ""}:${pane.tab ?? ""}`, className: "bt-panel-view" },
10989
- (0, import_react29.createElement)(DetailsDrawer, {
11475
+ (0, import_react30.createElement)(DetailsDrawer, {
10990
11476
  sessionId: pane.id,
10991
11477
  startRenaming: pane.renaming,
10992
11478
  startTab: pane.tab,
@@ -10996,6 +11482,7 @@ function BotPanel({ pane, leaving, roster, actions, useSessions, useSessionStatu
10996
11482
  useSessions,
10997
11483
  useSessionStatus,
10998
11484
  useActivity,
11485
+ useWork,
10999
11486
  useBrowserTabs,
11000
11487
  useBrowserPages
11001
11488
  })
@@ -11082,46 +11569,46 @@ var BROWSER_CSS = `
11082
11569
  `;
11083
11570
 
11084
11571
  // src/client/overlays.js
11085
- var import_react30 = require("react");
11572
+ var import_react31 = require("react");
11086
11573
  var import_react_dom8 = require("react-dom");
11087
11574
  var DIALOG_EXIT_MS = 160;
11088
11575
  var MENU_EXIT_MS3 = 120;
11089
- function Overlays({ useRoster, useUi, useSessions, useSessionStatus, useActivity, useRegistry, useBrowserPages, useBrowserTabs, useUpdate, actions }) {
11576
+ function Overlays({ useRoster, useUi, useSessions, useSessionStatus, useActivity, useWork, useRegistry, useBrowserPages, useBrowserTabs, useUpdate, actions }) {
11090
11577
  const roster = useRoster((value) => value);
11091
- const ui = useUi((value) => value);
11578
+ const ui2 = useUi((value) => value);
11092
11579
  useRegistry((value) => value);
11093
11580
  const current = useSessions((list) => Object.values(list.byId).find((session) => (session?.retainedBy?.mainView ?? 0) > 0)?.id ?? null);
11094
- const previous = (0, import_react30.useRef)(current);
11095
- (0, import_react30.useEffect)(() => {
11581
+ const previous = (0, import_react31.useRef)(current);
11582
+ (0, import_react31.useEffect)(() => {
11096
11583
  if (previous.current === current) return;
11097
11584
  previous.current = current;
11098
11585
  if (current) actions.followPanel?.(current);
11099
11586
  }, [current]);
11100
- const open = ui.details ? { id: ui.details, view: ui.view ?? "details", renaming: ui.renaming, tab: ui.tab } : null;
11101
- const [kept, setKept] = (0, import_react30.useState)(open);
11102
- (0, import_react30.useEffect)(() => {
11587
+ const open = ui2.details ? { id: ui2.details, view: ui2.view ?? "details", renaming: ui2.renaming, tab: ui2.tab } : null;
11588
+ const [kept, setKept] = (0, import_react31.useState)(open);
11589
+ (0, import_react31.useEffect)(() => {
11103
11590
  if (open) {
11104
11591
  setKept(open);
11105
11592
  return void 0;
11106
11593
  }
11107
11594
  const timer = setTimeout(() => setKept(null), PANEL_LINGER_MS);
11108
11595
  return () => clearTimeout(timer);
11109
- }, [ui.details, ui.view, ui.renaming, ui.tab]);
11596
+ }, [ui2.details, ui2.view, ui2.renaming, ui2.tab]);
11110
11597
  const pane = open ?? kept;
11111
- const [settings, settingsLeaving] = useLinger(ui.settings, DIALOG_EXIT_MS);
11112
- const [memory, memoryLeaving] = useLinger(ui.memory && roster.byId[ui.memory] ? ui.memory : null, DIALOG_EXIT_MS);
11113
- const [menu, menuLeaving] = useLinger(ui.menu, MENU_EXIT_MS3);
11114
- return (0, import_react30.createElement)(
11115
- import_react30.Fragment,
11598
+ const [settings, settingsLeaving] = useLinger(ui2.settings, DIALOG_EXIT_MS);
11599
+ const [memory, memoryLeaving] = useLinger(ui2.memory && roster.byId[ui2.memory] ? ui2.memory : null, DIALOG_EXIT_MS);
11600
+ const [menu, menuLeaving] = useLinger(ui2.menu, MENU_EXIT_MS3);
11601
+ return (0, import_react31.createElement)(
11602
+ import_react31.Fragment,
11116
11603
  null,
11117
- pane ? (0, import_react_dom8.createPortal)((0, import_react30.createElement)(BotPanel, { pane, leaving: open === null, roster, actions, useSessions, useSessionStatus, useActivity, useBrowserTabs, useBrowserPages }), document.body) : null,
11118
- ui.newChat ? (0, import_react_dom8.createPortal)((0, import_react30.createElement)(NewChat, { key: ui.newChat, mode: ui.newChat, roster, actions }), document.body) : null,
11119
- ui.palette ? (0, import_react_dom8.createPortal)((0, import_react30.createElement)(CommandPalette, { key: ui.palette.token, start: ui.palette.query, token: ui.palette.token, roster, actions, useSessions }), document.body) : null,
11120
- ui.exchange ? (0, import_react_dom8.createPortal)((0, import_react30.createElement)(ExchangeDialog, { pair: ui.exchange, roster, actions }), document.body) : null,
11121
- ui.voice ? (0, import_react_dom8.createPortal)((0, import_react30.createElement)(VoiceMode, { key: ui.voice, sessionId: ui.voice, roster, actions, useSessions, useSessionStatus }), document.body) : null,
11122
- menu ? (0, import_react_dom8.createPortal)((0, import_react30.createElement)(ContextMenu, { key: `${menu.kind ?? ""}:${menu.id}:${menu.x}:${menu.y}`, menu, roster, actions, leaving: menuLeaving }), document.body) : null,
11123
- settings ? (0, import_react_dom8.createPortal)((0, import_react30.createElement)(SettingsDialog, { page: settings.page, id: settings.id, roster, actions, useUpdate, leaving: settingsLeaving }), document.body) : null,
11124
- memory && roster.byId[memory] ? (0, import_react_dom8.createPortal)((0, import_react30.createElement)(MemoryDialog, { key: memory, bot: roster.byId[memory], actions, leaving: memoryLeaving }), document.body) : null
11604
+ pane ? (0, import_react_dom8.createPortal)((0, import_react31.createElement)(BotPanel, { pane, leaving: open === null, roster, actions, useSessions, useSessionStatus, useActivity, useWork, useBrowserTabs, useBrowserPages }), document.body) : null,
11605
+ ui2.newChat ? (0, import_react_dom8.createPortal)((0, import_react31.createElement)(NewChat, { key: ui2.newChat, mode: ui2.newChat, roster, actions }), document.body) : null,
11606
+ ui2.palette ? (0, import_react_dom8.createPortal)((0, import_react31.createElement)(CommandPalette, { key: ui2.palette.token, start: ui2.palette.query, token: ui2.palette.token, roster, actions, useSessions }), document.body) : null,
11607
+ ui2.exchange ? (0, import_react_dom8.createPortal)((0, import_react31.createElement)(ExchangeDialog, { pair: ui2.exchange, roster, actions }), document.body) : null,
11608
+ ui2.voice ? (0, import_react_dom8.createPortal)((0, import_react31.createElement)(VoiceMode, { key: ui2.voice, sessionId: ui2.voice, roster, actions, useSessions, useSessionStatus }), document.body) : null,
11609
+ menu ? (0, import_react_dom8.createPortal)((0, import_react31.createElement)(ContextMenu, { key: `${menu.kind ?? ""}:${menu.id}:${menu.x}:${menu.y}`, menu, roster, actions, leaving: menuLeaving }), document.body) : null,
11610
+ settings ? (0, import_react_dom8.createPortal)((0, import_react31.createElement)(SettingsDialog, { page: settings.page, id: settings.id, roster, actions, useUpdate, leaving: settingsLeaving }), document.body) : null,
11611
+ memory && roster.byId[memory] ? (0, import_react_dom8.createPortal)((0, import_react31.createElement)(MemoryDialog, { key: memory, bot: roster.byId[memory], actions, leaving: memoryLeaving }), document.body) : null
11125
11612
  );
11126
11613
  }
11127
11614
 
@@ -11951,6 +12438,117 @@ function writeMode(mode, storage = globalThis.localStorage) {
11951
12438
  }
11952
12439
  }
11953
12440
 
12441
+ // src/client/user-message.js
12442
+ var import_react32 = require("react");
12443
+ var ui = __toESM(require("@deepseek-ai/dsh-client-ui-primitives"), 1);
12444
+ var canDrawUserMessages = () => typeof ui.projectUserText === "function";
12445
+ function contentParts(content) {
12446
+ const texts2 = [];
12447
+ const attachments = [];
12448
+ const rest = [];
12449
+ for (const block of Array.isArray(content) ? content : []) {
12450
+ if (block?.type === "text" && typeof block.text === "string") texts2.push(block.text);
12451
+ else if ((block?.type === "image" || block?.type === "file") && block.attachment !== void 0) attachments.push(block);
12452
+ else rest.push(block);
12453
+ }
12454
+ return { text: texts2.join("\n"), attachments, rest };
12455
+ }
12456
+ function mentionableBots(roster, sessionId) {
12457
+ const room2 = roster.roomsById[sessionId];
12458
+ if (room2) return room2.members.map((id) => roster.byId[id]).filter(Boolean);
12459
+ return roster.byId[sessionId] ? roster.bots : [];
12460
+ }
12461
+ function userTextParts(text, bots, referenceLabels = []) {
12462
+ const parts = [];
12463
+ let cursor = 0;
12464
+ for (const span of mentionSpans(text, bots, { references: referenceLabels })) {
12465
+ if (!span.bot) continue;
12466
+ if (span.start > cursor) parts.push({ text: text.slice(cursor, span.start) });
12467
+ parts.push({ bot: span.bot, label: text.slice(span.start + 1, span.end) });
12468
+ cursor = span.end;
12469
+ }
12470
+ if (cursor < text.length) parts.push({ text: text.slice(cursor) });
12471
+ return parts;
12472
+ }
12473
+ function UserText({ text, bots, referenceLabels, skillNames, references, actions }) {
12474
+ return userTextParts(text, bots, referenceLabels).map((part, index) => part.bot ? (0, import_react32.createElement)(
12475
+ "button",
12476
+ { key: index, type: "button", className: "bt-umention", title: `@${part.bot.name}`, onClick: () => actions.openSession(part.bot.id) },
12477
+ (0, import_react32.createElement)(BotMark, { bot: part.bot, size: 16 }),
12478
+ part.label
12479
+ ) : (0, import_react32.createElement)(import_react32.Fragment, { key: index }, ui.projectUserText(part.text, referenceLabels, skillNames, "skill", references)));
12480
+ }
12481
+ function Attachments({ attachments, renderMessageImages }) {
12482
+ const compact2 = attachments.length > 1;
12483
+ return (0, import_react32.createElement)("div", { className: "bt-uattach" }, attachments.map((block, index) => {
12484
+ if (block.type === "image") {
12485
+ return renderMessageImages ? (0, import_react32.createElement)(import_react32.Fragment, { key: index }, renderMessageImages({ images: [{ attachment: block.attachment }], align: "end", compact: compact2 })) : null;
12486
+ }
12487
+ const file = block.attachment;
12488
+ const meta = [ui.fileExtension?.(file.name)?.toUpperCase().slice(0, 8), ui.fileSizeText?.(file.bytes)].filter(Boolean).join(" · ");
12489
+ return (0, import_react32.createElement)(
12490
+ "span",
12491
+ { key: index, className: "bt-ufile", title: file.name },
12492
+ ui.FileTypeIcon ? (0, import_react32.createElement)(ui.FileTypeIcon, { path: file.name, className: "bt-ufile-icon" }) : null,
12493
+ (0, import_react32.createElement)("span", { className: "bt-ufile-text" }, (0, import_react32.createElement)("span", { className: "bt-ufile-name" }, file.name), meta ? (0, import_react32.createElement)("span", { className: "bt-ufile-meta" }, meta) : null)
12494
+ );
12495
+ }));
12496
+ }
12497
+ function CopyButton({ text }) {
12498
+ const [copied, setCopied] = (0, import_react32.useState)(false);
12499
+ const copy2 = () => {
12500
+ void navigator.clipboard?.writeText(text).then(() => {
12501
+ setCopied(true);
12502
+ setTimeout(() => setCopied(false), 1e3);
12503
+ });
12504
+ };
12505
+ return (0, import_react32.createElement)("button", { type: "button", className: "bt-tool", "aria-label": copied ? t("Copied") : t("Copy"), title: copied ? t("Copied") : t("Copy"), onClick: copy2 }, copied ? (0, import_react32.createElement)(CheckIcon) : (0, import_react32.createElement)(CopyIcon));
12506
+ }
12507
+ var clock3 = (time) => typeof time === "number" ? new Date(time).toLocaleTimeString(dateLocale(), { hour: "numeric", minute: "2-digit" }) : void 0;
12508
+ function UserMessageCell({ node, sessionId, useRoster, actions, renderMessageImages, openFile, openSkill }) {
12509
+ const roster = useRoster((value) => value);
12510
+ const data = node.data ?? {};
12511
+ const { text, attachments, rest } = contentParts(data.content);
12512
+ const referenceLabels = data.referenceLabels ?? [];
12513
+ const references = openFile && openSkill ? { openFile, openSkill } : void 0;
12514
+ const steering = node.kind === "steering";
12515
+ return (0, import_react32.createElement)(
12516
+ "div",
12517
+ { className: "bt-umsg", "data-steering": steering || void 0 },
12518
+ (0, import_react32.createElement)(
12519
+ "div",
12520
+ { className: "bt-ustack" },
12521
+ attachments.length > 0 ? (0, import_react32.createElement)(Attachments, { attachments, renderMessageImages }) : null,
12522
+ text !== "" || rest.length > 0 ? (0, import_react32.createElement)(
12523
+ "div",
12524
+ { className: "bt-ubody", title: clock3(data.time) },
12525
+ (0, import_react32.createElement)(UserText, { text, bots: mentionableBots(roster, sessionId), referenceLabels, skillNames: data.skillNames ?? [], references, actions }),
12526
+ rest.map((block, index) => ui.JsonBlock ? (0, import_react32.createElement)(ui.JsonBlock, { key: index, label: t("Content"), payload: block }) : null)
12527
+ ) : null,
12528
+ referenceLabels.length > 0 ? (0, import_react32.createElement)("div", { className: "bt-urefs" }, t("Referenced {labels}", { labels: referenceLabels.join(t(", ")) })) : null
12529
+ ),
12530
+ text === "" ? null : (0, import_react32.createElement)("div", { className: "bt-utools" }, (0, import_react32.createElement)(CopyButton, { text }))
12531
+ );
12532
+ }
12533
+ var USER_MESSAGE_CSS = `
12534
+ .bt-umsg{display:flex;flex-direction:row-reverse;align-items:center;justify-content:flex-start;gap:4px}
12535
+ .bt-ustack{min-width:0;max-width:min(calc(var(--dsh-chat-content-width,748px) * .702),82%);display:flex;flex-direction:column;align-items:flex-end;gap:8px}
12536
+ .bt-ubody{max-width:100%;box-sizing:border-box;background:var(--dsw-specific-bubble,var(--bt-user));color:var(--bt-user-ink);border-radius:18px;padding:7px 12px;font-size:var(--dsh-content-font-size,14px);line-height:calc(var(--dsh-content-font-size,14px) + 8px);white-space:pre-wrap;word-break:break-word}
12537
+ .bt-ubody a{color:inherit}
12538
+ .bt-umention{display:inline-flex;align-items:center;gap:3px;vertical-align:-3px;max-width:100%;padding:0 1px;border:0;background:none;color:inherit;font:inherit;font-weight:600;line-height:inherit;cursor:pointer;border-radius:6px;white-space:nowrap}
12539
+ .bt-umention:hover{text-decoration:underline;text-underline-offset:2px}
12540
+ .bt-umention>:first-child{flex:none}
12541
+ .bt-uattach{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:8px;max-width:100%}
12542
+ .bt-ufile{display:inline-flex;align-items:center;gap:10px;box-sizing:border-box;width:240px;min-height:64px;padding:8px 12px;border-radius:18px;border:.5px solid var(--bt-line-2);background:var(--bt-card)}
12543
+ .bt-ufile-icon{flex:none;width:28px;height:28px}
12544
+ .bt-ufile-text{display:flex;flex-direction:column;min-width:0}
12545
+ .bt-ufile-name{font-size:14px;line-height:22px;font-weight:500;color:var(--bt-ink);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
12546
+ .bt-ufile-meta{font-size:12px;line-height:15px;color:var(--bt-ink-3);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
12547
+ .bt-urefs{font-size:13px;line-height:18px;color:var(--bt-ink-3)}
12548
+ .bt-utools{display:flex;flex:none;gap:2px;opacity:0;visibility:hidden;transition:opacity .12s ease,visibility .12s}
12549
+ .bt-umsg:hover .bt-utools,.bt-umsg:focus-within .bt-utools{opacity:1;visibility:visible}
12550
+ `;
12551
+
11954
12552
  // src/client/index.js
11955
12553
  var inject = ["sessions", "uiWorkspace", "uiConversation", "slots", "theme", "layout", "configForms"];
11956
12554
  function apply(ctx) {
@@ -11978,7 +12576,7 @@ function apply(ctx) {
11978
12576
  } catch {
11979
12577
  seenAt = {};
11980
12578
  }
11981
- const ui = createSource({ details: null, view: "details", renaming: false, tab: null, newChat: null, exchange: null, menu: null, voice: null, palette: null, quote: null, settings: null, memory: null, toast: null, signIn: null, renameAgent: null, unread: savedUnread, reactions: savedReactions, visits: {} });
12579
+ const ui2 = createSource({ details: null, view: "details", renaming: false, tab: null, newChat: null, exchange: null, menu: null, voice: null, palette: null, quote: null, settings: null, memory: null, toast: null, signIn: null, renameAgent: null, unread: savedUnread, reactions: savedReactions, visits: {} });
11982
12580
  const surface = createSource({ mode: readMode(), plain: false });
11983
12581
  let agentSessionCache = /* @__PURE__ */ new Set();
11984
12582
  try {
@@ -11996,7 +12594,7 @@ function apply(ctx) {
11996
12594
  };
11997
12595
  let expected = null;
11998
12596
  const setPlain = (plain) => surface.set((state) => state.plain === plain ? state : { ...state, plain });
11999
- const dropPanels = () => ui.set((state) => state.details || state.renaming ? { ...state, details: null, view: "details", renaming: false, tab: null } : state);
12597
+ const dropPanels = () => ui2.set((state) => state.details || state.renaming ? { ...state, details: null, view: "details", renaming: false, tab: null } : state);
12000
12598
  const seenKey = (id) => roster.getSnapshot().byId?.[id]?.id ?? id;
12001
12599
  const leaveVisits = (ids) => {
12002
12600
  const now = Date.now();
@@ -12007,11 +12605,13 @@ function apply(ctx) {
12007
12605
  }
12008
12606
  };
12009
12607
  ctx.effect(() => {
12010
- const onPageHide = () => leaveVisits(Object.keys(ui.getSnapshot().visits ?? {}));
12608
+ const onPageHide = () => leaveVisits(Object.keys(ui2.getSnapshot().visits ?? {}));
12011
12609
  window.addEventListener("pagehide", onPageHide);
12012
12610
  return () => window.removeEventListener("pagehide", onPageHide);
12013
12611
  }, "ds-bot: remember visits on page hide");
12014
12612
  const activity = createSource({});
12613
+ const work = createSource({ bots: {}, approvals: [] });
12614
+ let workKey = "";
12015
12615
  const live = createSource({});
12016
12616
  const turnClock = createSource({});
12017
12617
  let activityKey = "{}";
@@ -12034,7 +12634,7 @@ function apply(ctx) {
12034
12634
  } catch {
12035
12635
  savedPrefs = {};
12036
12636
  }
12037
- const setUnread = (id, value) => ui.set((state) => {
12637
+ const setUnread = (id, value) => ui2.set((state) => {
12038
12638
  if (Boolean(state.unread[id]) === value) return state;
12039
12639
  const unread = { ...state.unread };
12040
12640
  if (value) unread[id] = true;
@@ -12059,6 +12659,11 @@ function apply(ctx) {
12059
12659
  activityKey = nextActivity;
12060
12660
  activity.set(value.activity ?? {});
12061
12661
  }
12662
+ const nextWork = JSON.stringify(value.work ?? null);
12663
+ if (nextWork !== workKey) {
12664
+ workKey = nextWork;
12665
+ work.set(value.work ?? { bots: {}, approvals: [] });
12666
+ }
12062
12667
  noteMemoryNews(value.memoryNews ?? {});
12063
12668
  const current = roster.getSnapshot();
12064
12669
  if (value.connectors !== void 0 && current.ready && JSON.stringify(value.connectors) !== JSON.stringify(current.connectors ?? [])) {
@@ -12165,7 +12770,7 @@ function apply(ctx) {
12165
12770
  stopReads = null;
12166
12771
  }
12167
12772
  void browsers.retain(snapshot.bots.map((bot) => bot.id));
12168
- ui.set((state) => state.details && state.view === "browser" && (!on || !snapshot.byId[state.details]) ? { ...state, view: "details" } : state);
12773
+ ui2.set((state) => state.details && state.view === "browser" && (!on || !snapshot.byId[state.details]) ? { ...state, view: "details" } : state);
12169
12774
  };
12170
12775
  sync();
12171
12776
  const offRoster = roster.subscribe(sync);
@@ -12185,9 +12790,12 @@ function apply(ctx) {
12185
12790
  const render = () => {
12186
12791
  const next = roster.getSnapshot().bots.map((bot) => {
12187
12792
  const anchor = `.bt-bubble a[href="${MENTION_ORIGIN}${bot.id}"]`;
12793
+ const chip = /\s/.test(bot.name) ? "" : `[class*="_userRow"] [data-ref-chip="file"][title="@${bot.name.replace(/["\\]/g, "\\$&")}" i]`;
12188
12794
  const look = lookOf(bot);
12189
- const mark = look.image ? `${anchor}::before{background:center/cover url("${look.image}");-webkit-mask:none;mask:none;border-radius:50%}` : `${anchor}::before{background:${paintCss(look.color)};-webkit-mask-image:${maskOf(look.shape)};mask-image:${maskOf(look.shape)}}`;
12190
- return `${anchor}{color:${inkText(look.color)}}${mark}`;
12795
+ const marks = chip ? `${anchor}::before,${chip}::before` : `${anchor}::before`;
12796
+ const mark = look.image ? `${marks}{background:center/cover url("${look.image}");-webkit-mask:none;mask:none;border-radius:50%}` : `${marks}{background:${paintCss(look.color)};-webkit-mask-image:${maskOf(look.shape)};mask-image:${maskOf(look.shape)}}`;
12797
+ const chipBase = chip ? `${chip}{color:inherit;font-weight:600}${chip}>svg{display:none}${chip}::before{content:"";display:inline-block;flex:none;width:16px;height:16px;margin-right:3px;vertical-align:-3px;-webkit-mask:${DEFAULT_MASK} center/contain no-repeat;mask:${DEFAULT_MASK} center/contain no-repeat}` : "";
12798
+ return `${anchor}{color:${inkText(look.color)}}${chipBase}${mark}`;
12191
12799
  }).join("\n");
12192
12800
  if (next !== last) style.textContent = last = next;
12193
12801
  };
@@ -12302,7 +12910,7 @@ function apply(ctx) {
12302
12910
  isAgentSession: (id) => isAgentSessionId(id),
12303
12911
  openSession: (id) => {
12304
12912
  if (isAgentSessionId(id)) return actions.openAgentSession(id);
12305
- ui.set((state) => state.newChat || state.exchange || state.menu || state.palette ? { ...state, newChat: null, exchange: null, menu: null, palette: null } : state);
12913
+ ui2.set((state) => state.newChat || state.exchange || state.menu || state.palette ? { ...state, newChat: null, exchange: null, menu: null, palette: null } : state);
12306
12914
  setPlain(false);
12307
12915
  const target = partFor(id);
12308
12916
  expected = target;
@@ -12311,7 +12919,7 @@ function apply(ctx) {
12311
12919
  // The conversation replacements withdraw for a plain Session; set the surface
12312
12920
  // before the navigation so the Bot skin never flashes.
12313
12921
  openAgentSession: (sessionId) => {
12314
- ui.set((state) => ({ ...state, newChat: null, exchange: null, menu: null, palette: null, details: null, view: "details", renaming: false, tab: null }));
12922
+ ui2.set((state) => ({ ...state, newChat: null, exchange: null, menu: null, palette: null, details: null, view: "details", renaming: false, tab: null }));
12315
12923
  setPlain(true);
12316
12924
  expected = sessionId;
12317
12925
  ctx.uiWorkspace.openSession(sessionId);
@@ -12352,8 +12960,8 @@ function apply(ctx) {
12352
12960
  else if (roster.getSnapshot().mainBotId) actions.openSession(roster.getSnapshot().mainBotId);
12353
12961
  },
12354
12962
  renameAgentSession: (agentId, sessionId, title) => after(call("agent-rename", { agentId, sessionId, title })),
12355
- beginAgentRename: (sessionId) => ui.set((state) => ({ ...state, renameAgent: sessionId })),
12356
- endAgentRename: () => ui.set((state) => state.renameAgent ? { ...state, renameAgent: null } : state),
12963
+ beginAgentRename: (sessionId) => ui2.set((state) => ({ ...state, renameAgent: sessionId })),
12964
+ endAgentRename: () => ui2.set((state) => state.renameAgent ? { ...state, renameAgent: null } : state),
12357
12965
  // Removing the Agent archives every Session it owns; one of them may fill the
12358
12966
  // main view, and it must not stay there wearing the Bot surface afterwards.
12359
12967
  removeAgent: async (agentId) => {
@@ -12379,26 +12987,26 @@ function apply(ctx) {
12379
12987
  ctx.layout.selectPanel(id);
12380
12988
  },
12381
12989
  openNewChat: (mode) => {
12382
- ui.set((state) => ({ ...state, newChat: mode, menu: null, palette: null }));
12990
+ ui2.set((state) => ({ ...state, newChat: mode, menu: null, palette: null }));
12383
12991
  },
12384
12992
  openPalette: (query = "") => {
12385
- ui.set((state) => ({ ...state, palette: { query, token: Date.now() }, newChat: null, menu: null }));
12993
+ ui2.set((state) => ({ ...state, palette: { query, token: Date.now() }, newChat: null, menu: null }));
12386
12994
  },
12387
12995
  // The token keeps a late exit timer from closing a palette opened again since.
12388
12996
  closePalette: (token) => {
12389
- ui.set((state) => state.palette && (token === void 0 || state.palette.token === token) ? { ...state, palette: null } : state);
12997
+ ui2.set((state) => state.palette && (token === void 0 || state.palette.token === token) ? { ...state, palette: null } : state);
12390
12998
  },
12391
12999
  openExchange: (a, b) => {
12392
- if (a && b) ui.set((state) => ({ ...state, exchange: [a, b] }));
13000
+ if (a && b) ui2.set((state) => ({ ...state, exchange: [a, b] }));
12393
13001
  },
12394
13002
  openVoice: (id) => {
12395
- if (id) ui.set((state) => ({ ...state, voice: id, menu: null }));
13003
+ if (id) ui2.set((state) => ({ ...state, voice: id, menu: null }));
12396
13004
  },
12397
13005
  closeVoice: () => {
12398
- ui.set((state) => state.voice ? { ...state, voice: null } : state);
13006
+ ui2.set((state) => state.voice ? { ...state, voice: null } : state);
12399
13007
  },
12400
13008
  toggleDetails: (id, force) => {
12401
- ui.set((state) => {
13009
+ ui2.set((state) => {
12402
13010
  const details = force || state.details !== id || state.view !== "details" ? id : null;
12403
13011
  return { ...state, renaming: false, tab: null, details, view: "details" };
12404
13012
  });
@@ -12406,34 +13014,34 @@ function apply(ctx) {
12406
13014
  // The open panel follows the main view; a subject with no browser (a group
12407
13015
  // chat, an outside Session) settles on the details view.
12408
13016
  followPanel: (id) => {
12409
- ui.set((state) => {
13017
+ ui2.set((state) => {
12410
13018
  if (!state.details || state.details === id) return state;
12411
13019
  const view = state.view === "browser" && roster.getSnapshot().byId[id] === void 0 ? "details" : state.view;
12412
13020
  return { ...state, details: id, view, renaming: false, tab: null };
12413
13021
  });
12414
13022
  },
12415
13023
  showDetails: (id, tab = null) => {
12416
- ui.set((state) => ({ ...state, details: id, view: "details", renaming: false, tab }));
13024
+ ui2.set((state) => ({ ...state, details: id, view: "details", renaming: false, tab }));
12417
13025
  },
12418
13026
  // Opening the conversation would move focus to its composer and blur the name field.
12419
13027
  renameBot: (id) => {
12420
- ui.set((state) => ({ ...state, details: id, view: "details", renaming: true, tab: null }));
13028
+ ui2.set((state) => ({ ...state, details: id, view: "details", renaming: true, tab: null }));
12421
13029
  },
12422
13030
  // `id` opens one Bot (page 'bots') or one group chat (page 'groups').
12423
13031
  openSettings: (page = "general", id = null) => {
12424
- ui.set((state) => ({ ...state, settings: { page, id }, menu: null, palette: null, newChat: null }));
13032
+ ui2.set((state) => ({ ...state, settings: { page, id }, menu: null, palette: null, newChat: null }));
12425
13033
  },
12426
13034
  closeSettings: () => {
12427
- ui.set((state) => state.settings ? { ...state, settings: null } : state);
13035
+ ui2.set((state) => state.settings ? { ...state, settings: null } : state);
12428
13036
  },
12429
13037
  openMemory: (botId) => {
12430
- ui.set((state) => ({ ...state, memory: botId, menu: null, palette: null }));
13038
+ ui2.set((state) => ({ ...state, memory: botId, menu: null, palette: null }));
12431
13039
  },
12432
13040
  closeMemory: () => {
12433
- ui.set((state) => state.memory ? { ...state, memory: null } : state);
13041
+ ui2.set((state) => state.memory ? { ...state, memory: null } : state);
12434
13042
  },
12435
13043
  openConnectors: () => {
12436
- ui.set((state) => ({ ...state, settings: { page: "connectors", id: null }, menu: null, palette: null, newChat: null }));
13044
+ ui2.set((state) => ({ ...state, settings: { page: "connectors", id: null }, menu: null, palette: null, newChat: null }));
12437
13045
  },
12438
13046
  // Agent mode hands the surface back to the shell; everything open on the Bot
12439
13047
  // surface closes first so nothing stale reopens on the way back.
@@ -12441,7 +13049,7 @@ function apply(ctx) {
12441
13049
  if (mode !== "bot" && mode !== "agent") return;
12442
13050
  if (surface.getSnapshot().mode === mode) return;
12443
13051
  if (mode === "agent") {
12444
- ui.set((state) => ({ ...state, details: null, view: "details", settings: null, palette: null, newChat: null, menu: null, voice: null, exchange: null, memory: null, toast: null }));
13052
+ ui2.set((state) => ({ ...state, details: null, view: "details", settings: null, palette: null, newChat: null, menu: null, voice: null, exchange: null, memory: null, toast: null }));
12445
13053
  }
12446
13054
  writeMode(mode);
12447
13055
  surface.set((state) => ({ ...state, mode }));
@@ -12468,18 +13076,18 @@ function apply(ctx) {
12468
13076
  beginVisit: (id) => {
12469
13077
  const made = roster.getSnapshot().byId[id] ?? roster.getSnapshot().roomsById[id];
12470
13078
  const seen = seenAt[seenKey(id)] ?? (made?.createdBy && Number.isFinite(made.createdAt) ? made.createdAt : null);
12471
- ui.set((state) => ({ ...state, visits: { ...state.visits, [id]: { seen, entered: Date.now() } } }));
13079
+ ui2.set((state) => ({ ...state, visits: { ...state.visits, [id]: { seen, entered: Date.now() } } }));
12472
13080
  },
12473
13081
  endVisit: (id) => {
12474
13082
  leaveVisits([id]);
12475
- ui.set((state) => {
13083
+ ui2.set((state) => {
12476
13084
  if (!(id in (state.visits ?? {}))) return state;
12477
13085
  const visits = { ...state.visits };
12478
13086
  delete visits[id];
12479
13087
  return { ...state, visits };
12480
13088
  });
12481
13089
  },
12482
- toggleReaction: (key, index, emoji) => ui.set((state) => {
13090
+ toggleReaction: (key, index, emoji) => ui2.set((state) => {
12483
13091
  const entry = { ...state.reactions?.[key] ?? {} };
12484
13092
  const list = entry[index] ?? [];
12485
13093
  entry[index] = list.includes(emoji) ? list.filter((item) => item !== emoji) : [...list, emoji];
@@ -12493,19 +13101,19 @@ function apply(ctx) {
12493
13101
  }
12494
13102
  return { ...state, reactions };
12495
13103
  }),
12496
- quote: (sessionId, text) => ui.set((state) => ({ ...state, quote: { sessionId, text, token: Date.now() } })),
12497
- clearQuote: (token) => ui.set((state) => state.quote?.token === token ? { ...state, quote: null } : state),
13104
+ quote: (sessionId, text) => ui2.set((state) => ({ ...state, quote: { sessionId, text, token: Date.now() } })),
13105
+ clearQuote: (token) => ui2.set((state) => state.quote?.token === token ? { ...state, quote: null } : state),
12498
13106
  clearUnread: (id) => setUnread(id, false),
12499
13107
  setFlags: (id, flags) => after(call("set-flags", { id, ...flags })),
12500
13108
  deleteRoom: (id) => after(call("delete-room", { id })),
12501
13109
  openMenu: (menu) => {
12502
- ui.set((state) => ({ ...state, menu }));
13110
+ ui2.set((state) => ({ ...state, menu }));
12503
13111
  },
12504
13112
  closeMenu: () => {
12505
- ui.set((state) => state.menu ? { ...state, menu: null } : state);
13113
+ ui2.set((state) => state.menu ? { ...state, menu: null } : state);
12506
13114
  },
12507
13115
  closeOverlay: () => {
12508
- ui.set((state) => ({ ...state, newChat: null, exchange: null, details: state.newChat || state.exchange ? state.details : null }));
13116
+ ui2.set((state) => ({ ...state, newChat: null, exchange: null, details: state.newChat || state.exchange ? state.details : null }));
12509
13117
  },
12510
13118
  createBot: (payload) => after(call("create-bot", payload)),
12511
13119
  createRoom: (members, options = {}) => after(call("create-room", { members, ...options })),
@@ -12521,6 +13129,7 @@ function apply(ctx) {
12521
13129
  send: (sessionId, text) => after(call("send", { sessionId: partFor(sessionId), text })),
12522
13130
  answerQuestion: (sessionId, questionId, answer) => after(call("answer-question", { sessionId: partFor(sessionId), questionId, answer })),
12523
13131
  dismissQuestion: (sessionId) => after(call("dismiss-question", { sessionId })),
13132
+ answerApproval: (id, choice) => after(call("approval-answer", { id, choice })),
12524
13133
  // Values go out in these requests and never come back in a response.
12525
13134
  secretSet: (payload) => after(call("secret-set", payload)),
12526
13135
  secretAllow: (requestId) => after(call("secret-allow", { requestId })),
@@ -12548,9 +13157,9 @@ function apply(ctx) {
12548
13157
  // A failed action away from any form (a menu, the palette) shows as a toast.
12549
13158
  reportError: (error, where = "action") => {
12550
13159
  noteClientError(where, error);
12551
- ui.set((state) => ({ ...state, toast: { text: error?.message ?? String(error), token: Date.now() } }));
13160
+ ui2.set((state) => ({ ...state, toast: { text: error?.message ?? String(error), token: Date.now() } }));
12552
13161
  },
12553
- dismissToast: (token) => ui.set((state) => state.toast && (token === void 0 || state.toast.token === token) ? { ...state, toast: null } : state)
13162
+ dismissToast: (token) => ui2.set((state) => state.toast && (token === void 0 || state.toast.token === token) ? { ...state, toast: null } : state)
12554
13163
  };
12555
13164
  if (browsers) {
12556
13165
  Object.assign(actions, {
@@ -12560,7 +13169,7 @@ function apply(ctx) {
12560
13169
  if (!bot) return;
12561
13170
  browsers.ensureTabs(bot.id);
12562
13171
  if (browsers.tabs.getSnapshot()[bot.id]?.active == null) browsers.openTab(bot.id);
12563
- ui.set((state) => ({ ...state, details: id, view: "browser", renaming: false, tab: null }));
13172
+ ui2.set((state) => ({ ...state, details: id, view: "browser", renaming: false, tab: null }));
12564
13173
  },
12565
13174
  // '+' on the strip or the drawer's new-tab card: a blank tab in the browser view.
12566
13175
  browserNewTab: (id) => {
@@ -12568,7 +13177,7 @@ function apply(ctx) {
12568
13177
  if (!bot) return null;
12569
13178
  browsers.ensureTabs(bot.id);
12570
13179
  const tabId = browsers.openTab(bot.id);
12571
- ui.set((state) => ({ ...state, details: id, view: "browser", renaming: false, tab: null }));
13180
+ ui2.set((state) => ({ ...state, details: id, view: "browser", renaming: false, tab: null }));
12572
13181
  return tabId;
12573
13182
  },
12574
13183
  // A drawer card: its tab active, the panel on the browser view.
@@ -12576,7 +13185,7 @@ function apply(ctx) {
12576
13185
  const bot = roster.getSnapshot().byId[id];
12577
13186
  if (!bot) return;
12578
13187
  browsers.activate(bot.id, tabId);
12579
- ui.set((state) => ({ ...state, details: id, view: "browser" }));
13188
+ ui2.set((state) => ({ ...state, details: id, view: "browser" }));
12580
13189
  },
12581
13190
  browserEnsure: (botId) => browsers.ensureTabs(botId),
12582
13191
  browserCloseTab: (botId, tabId) => browsers.closeTab(botId, tabId),
@@ -12589,22 +13198,22 @@ function apply(ctx) {
12589
13198
  }
12590
13199
  actions.setPanelView = (view) => {
12591
13200
  if (view !== "details" && view !== "browser") return;
12592
- ui.set((state) => state.details && state.view !== view ? { ...state, view } : state);
13201
+ ui2.set((state) => state.details && state.view !== view ? { ...state, view } : state);
12593
13202
  };
12594
13203
  actions.closePanel = () => {
12595
- ui.set((state) => state.details ? { ...state, details: null, view: "details", renaming: false, tab: null } : state);
13204
+ ui2.set((state) => state.details ? { ...state, details: null, view: "details", renaming: false, tab: null } : state);
12596
13205
  };
12597
13206
  actions.signInEntry = () => ctx.slots.entries?.("settings.models.sign-in")?.[0] ?? null;
12598
13207
  actions.canSignIn = () => Boolean(actions.signInEntry());
12599
- actions.signIn = () => ui.set((state) => ({ ...state, signIn: (state.signIn ?? 0) + 1 }));
12600
- actions.closeSignIn = () => ui.set((state) => ({ ...state, signIn: null }));
13208
+ actions.signIn = () => ui2.set((state) => ({ ...state, signIn: (state.signIn ?? 0) + 1 }));
13209
+ actions.closeSignIn = () => ui2.set((state) => ({ ...state, signIn: null }));
12601
13210
  actions.accountT = () => ctx.get("locale").bind("settings.account");
12602
13211
  actions.speech = () => ctx.get("remote.speech") ?? void 0;
12603
13212
  actions.voiceSetup = () => {
12604
13213
  const navigation = ctx.get("pluginNavigation");
12605
13214
  return navigation ? () => navigation.openBundle(VOICE_BUNDLE) : null;
12606
13215
  };
12607
- actions.seatAccount = (on) => ui.set((state) => state.accountSeated === on ? state : { ...state, accountSeated: on });
13216
+ actions.seatAccount = (on) => ui2.set((state) => state.accountSeated === on ? state : { ...state, accountSeated: on });
12608
13217
  actions.installUpdate = async () => {
12609
13218
  const value = await call("update-install");
12610
13219
  update.set((previous) => ({ ...previous, ...value }));
@@ -12665,7 +13274,7 @@ function apply(ctx) {
12665
13274
  });
12666
13275
  const browserPages = browsers?.pages ?? createSource({});
12667
13276
  const browserTabs = browsers?.tabs ?? createSource({});
12668
- const face = () => ({ actions, hooks: { roster, ui, activity, memoryNews, registry, turnClock, live, browserPages, browserTabs, surface, update } });
13277
+ const face = () => ({ actions, hooks: { roster, ui: ui2, activity, work, memoryNews, registry, turnClock, live, browserPages, browserTabs, surface, update } });
12669
13278
  const assistantFace = (sessionId) => {
12670
13279
  const binding = ctx.sessions.binding(sessionId);
12671
13280
  if (binding === void 0) throw new Error(`ds-bot: unknown session "${sessionId}"`);
@@ -12675,10 +13284,41 @@ function apply(ctx) {
12675
13284
  const snapshot = chat.getSnapshot();
12676
13285
  if (snapshot === void 0) throw new Error("ds-bot: Chat target is unavailable");
12677
13286
  return snapshot.nodes.turnDataSource(Number(turn), "assistant-step");
13287
+ },
13288
+ turnContext: (turn) => {
13289
+ const snapshot = chat.getSnapshot();
13290
+ if (snapshot === void 0) throw new Error("ds-bot: Chat target is unavailable");
13291
+ return snapshot.nodes.turnDataSource(Number(turn), "context");
12678
13292
  }
12679
13293
  } };
12680
13294
  };
12681
13295
  const shadow = (name, options, component) => ctx.slots.inject(name, () => ctx.slots.register({ name, ...options }, component));
13296
+ let atMenu = null;
13297
+ const installAtMenu = () => {
13298
+ const fiber = ctx.inject(["inputTriggers"], (scope) => {
13299
+ scope.effect(() => {
13300
+ const triggers = scope.inputTriggers;
13301
+ const controllerOf = (sessionId) => {
13302
+ const binding = ctx.sessions.binding(sessionId);
13303
+ return binding === void 0 ? void 0 : triggers.sessionOf(binding.ctx);
13304
+ };
13305
+ const launched = (sessionId) => controllerOf(sessionId)?.launcher.getSnapshot() === CHATS_SOURCE;
13306
+ const offs = atSources({ roster, sessions: ctx.sessions, launched, iconFor: menuIcons(roster) }).map((source) => triggers.registerSource(source));
13307
+ atMenu = { controllerOf };
13308
+ return () => {
13309
+ atMenu = null;
13310
+ for (const off of offs) off();
13311
+ };
13312
+ }, "ds-bot: @ menu");
13313
+ });
13314
+ return () => fiber.dispose();
13315
+ };
13316
+ actions.canReferenceChat = () => atMenu !== null;
13317
+ actions.referenceChat = (sessionId, span) => {
13318
+ const controller = atMenu?.controllerOf(sessionId);
13319
+ if (controller === void 0 || span === void 0) return;
13320
+ controller.toggleSource(CHATS_SOURCE, { trigger: "@", query: "", quoted: false, position: "inline", span });
13321
+ };
12682
13322
  const installShell = () => [
12683
13323
  installCompiledStyles(SHELL_CSS, "shell"),
12684
13324
  installCompiledStyles(SIDEBAR_CSS, "sidebar"),
@@ -12688,6 +13328,8 @@ function apply(ctx) {
12688
13328
  installCompiledStyles(SCHEDULE_CSS, "schedules"),
12689
13329
  installCompiledStyles(FEEDBACK_CSS, "feedback"),
12690
13330
  installCompiledStyles(ABOUT_CSS, "about"),
13331
+ installCompiledStyles(MEMBER_CSS, "members"),
13332
+ installCompiledStyles(USER_MESSAGE_CSS, "user-messages"),
12691
13333
  browsers ? installCompiledStyles(BROWSER_CSS, "browser") : null,
12692
13334
  installTheme(),
12693
13335
  shadow("sidebar.workspaces", { priority: -10, inject: face }, TeamSidebar),
@@ -12709,6 +13351,7 @@ function apply(ctx) {
12709
13351
  installCompiledStyles(CONVERSATION_CSS, "conversation"),
12710
13352
  installBubbleTokens(),
12711
13353
  installMentions(),
13354
+ installAtMenu(),
12712
13355
  // Bot conversations are always flat, so the work-details mode is not a choice:
12713
13356
  // the mode is held at `verbose` while this group is mounted.
12714
13357
  holdFlatTranscript(ctx.configForms.get("ui-chat")),
@@ -12722,6 +13365,7 @@ function apply(ctx) {
12722
13365
  // every message.
12723
13366
  shadow("conversation.chat.node", { key: "question-reply", priority: -10, locale: "chat" }, AnsweredCell),
12724
13367
  shadow("conversation.chat.node", { key: "compaction", priority: -10, locale: "chat" }, Nothing),
13368
+ ...canDrawUserMessages() ? ["user", "steering"].map((key) => shadow("conversation.chat.node", { key, priority: -10, locale: "chat", inject: face }, UserMessageCell)) : [],
12725
13369
  shadow("conversation.input.permission", { priority: -10 }, Nothing),
12726
13370
  shadow("conversation.input.plan", { priority: -10 }, Nothing),
12727
13371
  // The composer carries no model picker or run statistics; a Bot's model lives
@@ -12735,6 +13379,7 @@ function apply(ctx) {
12735
13379
  priority: -10,
12736
13380
  select: (owner) => owner.pendingInteraction?.kind === "question" ? owner.pendingInteraction : null
12737
13381
  }, QuestionCard),
13382
+ shadow("conversation.input.dock", { id: "bot-members", order: -120, inject: face }, MemberDock),
12738
13383
  shadow("conversation.input.dock", { id: "bot-model", order: -110, inject: face }, ModelChoiceDock),
12739
13384
  shadow("conversation.input.dock", { id: "bot-question", order: -100, inject: face }, QuestionDock),
12740
13385
  shadow("conversation.input.dock", { id: "bot-secret", order: -90, inject: face }, SecretDock)