switchroom 0.14.13 → 0.14.15

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.
@@ -6532,6 +6532,24 @@ var require_mod3 = __commonJS((exports) => {
6532
6532
  __exportStar(require_worker(), exports);
6533
6533
  });
6534
6534
 
6535
+ // card-format.ts
6536
+ function formatDuration(ms) {
6537
+ if (ms < 1000)
6538
+ return `${ms}ms`;
6539
+ const s = Math.floor(ms / 1000);
6540
+ if (s < 60)
6541
+ return `00:${s.toString().padStart(2, "0")}`;
6542
+ const m = Math.floor(s / 60);
6543
+ const r = s % 60;
6544
+ return `${m.toString().padStart(2, "0")}:${r.toString().padStart(2, "0")}`;
6545
+ }
6546
+ function escapeHtml(s) {
6547
+ return s.replace(/[&<>]/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;" })[c]);
6548
+ }
6549
+ function truncate(s, n) {
6550
+ return s.length > n ? s.slice(0, n - 1) + "\u2026" : s;
6551
+ }
6552
+
6535
6553
  // ../node_modules/.bun/@grammyjs+runner@2.0.3+c6be0243b1bbec89/node_modules/@grammyjs/runner/out/mod.js
6536
6554
  var require_mod4 = __commonJS((exports) => {
6537
6555
  var __createBinding = exports && exports.__createBinding || (Object.create ? function(o, m, k, k2) {
@@ -27851,14 +27869,6 @@ var init_secretlint_source = __esm(() => {
27851
27869
  init_suppressor();
27852
27870
  });
27853
27871
 
27854
- // card-format.ts
27855
- function escapeHtml8(s) {
27856
- return s.replace(/[&<>]/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;" })[c]);
27857
- }
27858
- function truncate5(s, n) {
27859
- return s.length > n ? s.slice(0, n - 1) + "\u2026" : s;
27860
- }
27861
-
27862
27872
  // gateway/auth-line.ts
27863
27873
  function escapeHtml9(s) {
27864
27874
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
@@ -28947,13 +28957,13 @@ function diffSnapshots(current, previous) {
28947
28957
  function renderConfigChangeDim(dim) {
28948
28958
  switch (dim.field) {
28949
28959
  case "model": {
28950
- const from = escapeHtml8(dim.from ?? "(default)");
28951
- const to = escapeHtml8(dim.to ?? "(default)");
28960
+ const from = escapeHtml(dim.from ?? "(default)");
28961
+ const to = escapeHtml(dim.to ?? "(default)");
28952
28962
  return `\u2699\ufe0f <b>Config</b> model: ${from} \u2192 ${to}`;
28953
28963
  }
28954
28964
  case "memoryBackend": {
28955
- const from = escapeHtml8(dim.from ?? "(default)");
28956
- const to = escapeHtml8(dim.to ?? "(default)");
28965
+ const from = escapeHtml(dim.from ?? "(default)");
28966
+ const to = escapeHtml(dim.to ?? "(default)");
28957
28967
  return `\u2699\ufe0f <b>Config</b> memory backend: ${from} \u2192 ${to}`;
28958
28968
  }
28959
28969
  case "tools":
@@ -29040,14 +29050,14 @@ function shouldSkipDuplicateBootCard(gate, site) {
29040
29050
  function renderNextStep(text) {
29041
29051
  const parts = text.split("`");
29042
29052
  if (parts.length % 2 === 0)
29043
- return escapeHtml8(text);
29044
- return parts.map((p, i) => i % 2 === 0 ? escapeHtml8(p) : `<code>${escapeHtml8(p)}</code>`).join("");
29053
+ return escapeHtml(text);
29054
+ return parts.map((p, i) => i % 2 === 0 ? escapeHtml(p) : `<code>${escapeHtml(p)}</code>`).join("");
29045
29055
  }
29046
29056
  function renderBootCard(opts) {
29047
29057
  const { agentName: agentName3, version: version2, probes, restartReason, restartAgeMs } = opts;
29048
29058
  const agentSlug = opts.agentSlug ?? agentName3;
29049
29059
  const ackEmoji = restartReason ? REASON_EMOJI[restartReason] : "\u2705";
29050
- const ack = `${ackEmoji} <b>${escapeHtml8(agentName3)}</b> back up \u00b7 ${escapeHtml8(version2)}`;
29060
+ const ack = `${ackEmoji} <b>${escapeHtml(agentName3)}</b> back up \u00b7 ${escapeHtml(version2)}`;
29051
29061
  const degradedRows = [];
29052
29062
  const snoozeSet = new Set(opts.snoozeRows ?? []);
29053
29063
  if (opts.resolvedRows && opts.resolvedRows.length > 0) {
@@ -29055,13 +29065,13 @@ function renderBootCard(opts) {
29055
29065
  const lbl = PROBE_LABELS[key];
29056
29066
  if (!lbl)
29057
29067
  continue;
29058
- degradedRows.push(`\u2705 <b>${escapeHtml8(lbl)}</b> resolved`);
29068
+ degradedRows.push(`\u2705 <b>${escapeHtml(lbl)}</b> resolved`);
29059
29069
  }
29060
29070
  }
29061
29071
  if (restartReason === "crash") {
29062
29072
  const ageStr = restartAgeMs != null && restartAgeMs > 0 ? ` \u00b7 ${(restartAgeMs / 1000).toFixed(1)}s ago` : "";
29063
- degradedRows.push(`\u26a0\ufe0f <b>Restart</b> ${escapeHtml8(REASON_LABEL.crash)}${ageStr}`);
29064
- const tailCmd = process.env.SWITCHROOM_RUNTIME === "docker" ? `docker logs --tail 100 switchroom-${escapeHtml8(agentSlug)}` : `journalctl --user -u switchroom-${escapeHtml8(agentSlug)} -n 100`;
29073
+ degradedRows.push(`\u26a0\ufe0f <b>Restart</b> ${escapeHtml(REASON_LABEL.crash)}${ageStr}`);
29074
+ const tailCmd = process.env.SWITCHROOM_RUNTIME === "docker" ? `docker logs --tail 100 switchroom-${escapeHtml(agentSlug)}` : `journalctl --user -u switchroom-${escapeHtml(agentSlug)} -n 100`;
29065
29075
  degradedRows.push(` \u21b3 Tail logs: <code>${tailCmd}</code>`);
29066
29076
  }
29067
29077
  if (probes) {
@@ -29074,7 +29084,7 @@ function renderBootCard(opts) {
29074
29084
  if (snoozeSet.has(key))
29075
29085
  continue;
29076
29086
  const dot = DOT[r.status] ?? DOT.fail;
29077
- degradedRows.push(`${dot} <b>${PROBE_LABELS[key]}</b> ${escapeHtml8(r.detail)}`);
29087
+ degradedRows.push(`${dot} <b>${PROBE_LABELS[key]}</b> ${escapeHtml(r.detail)}`);
29078
29088
  if (r.nextStep) {
29079
29089
  degradedRows.push(` \u21b3 ${renderNextStep(r.nextStep)}`);
29080
29090
  }
@@ -31658,6 +31668,7 @@ class StatusReactionController {
31658
31668
  stallSoftTimer = null;
31659
31669
  stallHardTimer = null;
31660
31670
  finished = false;
31671
+ held = false;
31661
31672
  debounceMs;
31662
31673
  stallSoftMs;
31663
31674
  stallHardMs;
@@ -31697,9 +31708,22 @@ class StatusReactionController {
31697
31708
  if (this.finished)
31698
31709
  return;
31699
31710
  this.finished = true;
31711
+ this.held = false;
31700
31712
  this.clearDebounceTimer();
31701
31713
  this.clearStallTimers();
31702
31714
  }
31715
+ hold() {
31716
+ if (this.finished)
31717
+ return;
31718
+ this.held = true;
31719
+ this.clearStallTimers();
31720
+ const working = this.resolveEmoji("tool");
31721
+ if (working != null && working !== this.currentEmoji && working !== this.pendingEmoji) {
31722
+ this.clearDebounceTimer();
31723
+ this.pendingEmoji = working;
31724
+ this.enqueue(working);
31725
+ }
31726
+ }
31703
31727
  scheduleState(state, opts = {}) {
31704
31728
  if (this.finished)
31705
31729
  return;
@@ -31731,6 +31755,7 @@ class StatusReactionController {
31731
31755
  if (this.finished)
31732
31756
  return;
31733
31757
  this.finished = true;
31758
+ this.held = false;
31734
31759
  this.clearStallTimers();
31735
31760
  const flushPending = this.debounceTimer != null && this.pendingEmoji != null ? this.pendingEmoji : null;
31736
31761
  this.clearDebounceTimer();
@@ -31773,7 +31798,7 @@ class StatusReactionController {
31773
31798
  }
31774
31799
  resetStallTimers() {
31775
31800
  this.clearStallTimers();
31776
- if (this.finished)
31801
+ if (this.finished || this.held)
31777
31802
  return;
31778
31803
  this.stallSoftTimer = setTimeout(() => {
31779
31804
  this.stallSoftTimer = null;
@@ -31802,6 +31827,206 @@ class StatusReactionController {
31802
31827
  }
31803
31828
  }
31804
31829
 
31830
+ // reaction-defer.ts
31831
+ class DeferredDoneReactions {
31832
+ deps;
31833
+ map = new Map;
31834
+ constructor(deps) {
31835
+ this.deps = deps;
31836
+ }
31837
+ tryDefer(key, ctrl) {
31838
+ if (this.deps.countRunningWorkers() > 0) {
31839
+ ctrl.hold();
31840
+ this.map.set(key, { ctrl });
31841
+ if (this.deps.countRunningWorkers() === 0)
31842
+ this.promote();
31843
+ return true;
31844
+ }
31845
+ this.map.delete(key);
31846
+ return false;
31847
+ }
31848
+ drop(key) {
31849
+ this.map.delete(key);
31850
+ }
31851
+ promote() {
31852
+ if (this.map.size === 0)
31853
+ return;
31854
+ if (this.deps.countRunningWorkers() > 0)
31855
+ return;
31856
+ for (const [key, { ctrl }] of this.map) {
31857
+ ctrl.finalize("done");
31858
+ if (this.deps.getActive(key) === ctrl)
31859
+ this.deps.purge(key);
31860
+ }
31861
+ this.map.clear();
31862
+ }
31863
+ has(key) {
31864
+ return this.map.has(key);
31865
+ }
31866
+ get size() {
31867
+ return this.map.size;
31868
+ }
31869
+ }
31870
+
31871
+ // worker-activity-feed.ts
31872
+ var DESC_MAX = 80;
31873
+ var TOOL_ARG_MAX = 64;
31874
+ var SUMMARY_MAX = 100;
31875
+ function renderWorkerActivity(v) {
31876
+ const desc = truncate(v.description.trim() || "background task", DESC_MAX);
31877
+ const elapsed = formatDuration(v.elapsedMs);
31878
+ const toolWord = v.toolCount === 1 ? "tool" : "tools";
31879
+ if (v.state === "done" || v.state === "failed") {
31880
+ const head = v.state === "done" ? `\u2705 <b>Worker done</b> \u00b7 <i>${escapeHtml(desc)}</i>` : `\u26a0\ufe0f <b>Worker failed</b> \u00b7 <i>${escapeHtml(desc)}</i>`;
31881
+ return `${head}
31882
+ <i>${v.toolCount} ${toolWord} \u00b7 ${elapsed}</i>`;
31883
+ }
31884
+ const header = `\uD83D\uDD27 <b>Worker</b> \u00b7 <i>${escapeHtml(desc)}</i>`;
31885
+ let activity;
31886
+ if (v.lastTool != null) {
31887
+ const arg = v.lastTool.sanitisedArg.trim();
31888
+ const argPart = arg.length > 0 ? ` ${escapeHtml(truncate(arg, TOOL_ARG_MAX))}` : "";
31889
+ activity = `\u26a1 <code>${escapeHtml(v.lastTool.name)}</code>${argPart} <i>(${v.toolCount} ${toolWord} \u00b7 ${elapsed})</i>`;
31890
+ } else {
31891
+ activity = `<i>starting\u2026 (${elapsed})</i>`;
31892
+ }
31893
+ const summary = v.latestSummary.trim();
31894
+ const lines = [header, activity];
31895
+ if (summary.length > 0) {
31896
+ lines.push(` \u21b3 <i>${escapeHtml(truncate(summary, SUMMARY_MAX))}</i>`);
31897
+ }
31898
+ return lines.join(`
31899
+ `);
31900
+ }
31901
+ var COOLDOWN_JITTER_MS = 500;
31902
+ function extractRetryAfterSecs(err) {
31903
+ if (err == null || typeof err !== "object")
31904
+ return null;
31905
+ const e = err;
31906
+ if (e.error_code !== 429)
31907
+ return null;
31908
+ const ra = e.parameters?.retry_after;
31909
+ if (typeof ra === "number" && Number.isFinite(ra) && ra > 0)
31910
+ return ra;
31911
+ return null;
31912
+ }
31913
+ function createWorkerActivityFeed(opts) {
31914
+ const log = opts.log ?? (() => {});
31915
+ const nowFn = opts.now ?? Date.now;
31916
+ const minEditInterval = opts.minEditIntervalMs ?? 2500;
31917
+ const firstPaintMin = opts.firstPaintMinMs ?? 8000;
31918
+ const handles = new Map;
31919
+ function sendOptsFor(h) {
31920
+ return {
31921
+ parse_mode: "HTML",
31922
+ disable_web_page_preview: true,
31923
+ ...h.threadId != null ? { message_thread_id: h.threadId } : {}
31924
+ };
31925
+ }
31926
+ function noteRateLimited(h, err, label) {
31927
+ const retryAfter = extractRetryAfterSecs(err);
31928
+ if (retryAfter == null)
31929
+ return;
31930
+ h.cooldownUntil = nowFn() + retryAfter * 1000 + COOLDOWN_JITTER_MS;
31931
+ log(`worker-feed: ${label} 429 \u2014 backing off ${retryAfter}s`);
31932
+ }
31933
+ async function doUpdate(h, view) {
31934
+ if (nowFn() < h.cooldownUntil)
31935
+ return;
31936
+ const body = renderWorkerActivity(view);
31937
+ if (h.messageId == null) {
31938
+ if (view.elapsedMs < firstPaintMin)
31939
+ return;
31940
+ try {
31941
+ const sent = await opts.bot.sendMessage(h.chatId, body, sendOptsFor(h));
31942
+ h.messageId = sent.message_id;
31943
+ h.lastBody = body;
31944
+ h.lastEditAt = nowFn();
31945
+ } catch (err) {
31946
+ noteRateLimited(h, err, "send");
31947
+ log(`worker-feed: send failed: ${err.message}`);
31948
+ }
31949
+ return;
31950
+ }
31951
+ if (body === h.lastBody)
31952
+ return;
31953
+ if (nowFn() - h.lastEditAt < minEditInterval)
31954
+ return;
31955
+ try {
31956
+ await opts.bot.editMessageText(h.chatId, h.messageId, body, sendOptsFor(h));
31957
+ h.lastBody = body;
31958
+ h.lastEditAt = nowFn();
31959
+ } catch (err) {
31960
+ noteRateLimited(h, err, "edit");
31961
+ log(`worker-feed: edit failed, will re-post: ${err.message}`);
31962
+ h.messageId = null;
31963
+ h.lastBody = null;
31964
+ }
31965
+ }
31966
+ async function doFinish(h, view) {
31967
+ if (h.messageId == null)
31968
+ return;
31969
+ if (nowFn() < h.cooldownUntil) {
31970
+ return;
31971
+ }
31972
+ const body = renderWorkerActivity(view);
31973
+ if (body === h.lastBody)
31974
+ return;
31975
+ try {
31976
+ await opts.bot.editMessageText(h.chatId, h.messageId, body, sendOptsFor(h));
31977
+ h.lastBody = body;
31978
+ h.lastEditAt = nowFn();
31979
+ } catch (err) {
31980
+ noteRateLimited(h, err, "finish");
31981
+ log(`worker-feed: finish edit failed: ${err.message}`);
31982
+ }
31983
+ }
31984
+ return {
31985
+ has(agentId) {
31986
+ return handles.get(agentId)?.messageId != null;
31987
+ },
31988
+ get size() {
31989
+ return handles.size;
31990
+ },
31991
+ update(agentId, chatId, view, threadId) {
31992
+ if (chatId.length === 0)
31993
+ return Promise.resolve();
31994
+ let h = handles.get(agentId);
31995
+ if (h == null) {
31996
+ h = {
31997
+ chatId,
31998
+ threadId,
31999
+ messageId: null,
32000
+ lastBody: null,
32001
+ lastEditAt: 0,
32002
+ cooldownUntil: 0,
32003
+ chain: Promise.resolve()
32004
+ };
32005
+ handles.set(agentId, h);
32006
+ }
32007
+ const handle = h;
32008
+ handle.chain = handle.chain.then(() => doUpdate(handle, view)).catch((err) => {
32009
+ log(`worker-feed: update chain error ${agentId}: ${err.message}`);
32010
+ });
32011
+ return handle.chain;
32012
+ },
32013
+ finish(agentId, view) {
32014
+ const h = handles.get(agentId);
32015
+ if (h == null)
32016
+ return Promise.resolve();
32017
+ h.chain = h.chain.then(() => doFinish(h, view)).catch((err) => {
32018
+ log(`worker-feed: finish chain error ${agentId}: ${err.message}`);
32019
+ }).finally(() => {
32020
+ handles.delete(agentId);
32021
+ });
32022
+ return h.chain;
32023
+ },
32024
+ drop(agentId) {
32025
+ handles.delete(agentId);
32026
+ }
32027
+ };
32028
+ }
32029
+
31805
32030
  // tool-names.ts
31806
32031
  var TELEGRAM_TOOL_PREFIX_RE = /^mcp__[^_].*?telegram__/;
31807
32032
  function stripPrefix(toolName) {
@@ -31882,10 +32107,10 @@ function hostFromUrl(u) {
31882
32107
  try {
31883
32108
  return new URL(u).host;
31884
32109
  } catch {
31885
- return truncate(u);
32110
+ return truncate2(u);
31886
32111
  }
31887
32112
  }
31888
- function truncate(s, n = MAX_LABEL_CHARS) {
32113
+ function truncate2(s, n = MAX_LABEL_CHARS) {
31889
32114
  if (s.length <= n)
31890
32115
  return s;
31891
32116
  return s.slice(0, n - 1) + "\u2026";
@@ -31900,7 +32125,7 @@ function firstLine(s) {
31900
32125
  }
31901
32126
  function toolLabel(tool, input, preamble, precomputedLabel) {
31902
32127
  if (precomputedLabel && precomputedLabel.trim().length > 0) {
31903
- return truncate(firstLine(precomputedLabel.trim()), MAX_DESCRIPTION_CHARS);
32128
+ return truncate2(firstLine(precomputedLabel.trim()), MAX_DESCRIPTION_CHARS);
31904
32129
  }
31905
32130
  if (!input || typeof input !== "object")
31906
32131
  return "";
@@ -31926,26 +32151,26 @@ function toolLabel(tool, input, preamble, precomputedLabel) {
31926
32151
  const pre = preambleLabel();
31927
32152
  if (pre)
31928
32153
  return pre;
31929
- return truncate(basename(str("file_path") ?? ""));
32154
+ return truncate2(basename(str("file_path") ?? ""));
31930
32155
  }
31931
32156
  case "Bash":
31932
32157
  case "BashOutput": {
31933
32158
  const description = str("description");
31934
32159
  if (description)
31935
- return truncate(firstLine(description), MAX_DESCRIPTION_CHARS);
32160
+ return truncate2(firstLine(description), MAX_DESCRIPTION_CHARS);
31936
32161
  const pre = preambleLabel();
31937
32162
  if (pre)
31938
32163
  return pre;
31939
32164
  const cmd = str("command") ?? str("bash_id") ?? "";
31940
- return truncate(firstLine(cmd), MAX_BASH_CHARS);
32165
+ return truncate2(firstLine(cmd), MAX_BASH_CHARS);
31941
32166
  }
31942
32167
  case "KillShell":
31943
- return truncate(str("shell_id") ?? "");
32168
+ return truncate2(str("shell_id") ?? "");
31944
32169
  case "Glob": {
31945
32170
  const pre = preambleLabel();
31946
32171
  if (pre)
31947
32172
  return pre;
31948
- return truncate(str("pattern") ?? "");
32173
+ return truncate2(str("pattern") ?? "");
31949
32174
  }
31950
32175
  case "Grep": {
31951
32176
  const pre = preambleLabel();
@@ -31956,18 +32181,18 @@ function toolLabel(tool, input, preamble, precomputedLabel) {
31956
32181
  return "";
31957
32182
  const path = str("path");
31958
32183
  const where = shortenGrepPath(path ?? "");
31959
- return truncate(`"${pat}" (in ${where})`);
32184
+ return truncate2(`"${pat}" (in ${where})`);
31960
32185
  }
31961
32186
  case "WebFetch":
31962
- return truncate(hostFromUrl(str("url") ?? ""));
32187
+ return truncate2(hostFromUrl(str("url") ?? ""));
31963
32188
  case "WebSearch": {
31964
32189
  const q = str("query") ?? "";
31965
- return q ? truncate(`"${q}"`) : "";
32190
+ return q ? truncate2(`"${q}"`) : "";
31966
32191
  }
31967
32192
  case "Task":
31968
32193
  case "Agent": {
31969
32194
  const desc = str("description") ?? str("subagent_type") ?? "";
31970
- return truncate(desc);
32195
+ return truncate2(desc);
31971
32196
  }
31972
32197
  case "TodoWrite":
31973
32198
  case "TaskCreate":
@@ -31978,9 +32203,9 @@ function toolLabel(tool, input, preamble, precomputedLabel) {
31978
32203
  case "TaskOutput":
31979
32204
  return "";
31980
32205
  case "Skill":
31981
- return truncate(str("skill") ?? "");
32206
+ return truncate2(str("skill") ?? "");
31982
32207
  case "SlashCommand":
31983
- return truncate(str("command") ?? "");
32208
+ return truncate2(str("command") ?? "");
31984
32209
  case "ToolSearch": {
31985
32210
  const q = str("query") ?? "";
31986
32211
  if (!q)
@@ -31988,35 +32213,35 @@ function toolLabel(tool, input, preamble, precomputedLabel) {
31988
32213
  const selectMatch = q.match(/^\s*select\s*:\s*(.+)$/i);
31989
32214
  if (selectMatch) {
31990
32215
  const names = selectMatch[1].split(",").map((n) => n.trim()).filter((n) => n.length > 0).join(", ");
31991
- return truncate(`Loading schema: ${names}`);
32216
+ return truncate2(`Loading schema: ${names}`);
31992
32217
  }
31993
- return truncate(`Searching tools: ${q}`);
32218
+ return truncate2(`Searching tools: ${q}`);
31994
32219
  }
31995
32220
  default:
31996
32221
  if (tool.startsWith("mcp__")) {
31997
32222
  const description = str("description");
31998
32223
  if (description)
31999
- return truncate(firstLine(stripHtml(description)), MAX_DESCRIPTION_CHARS);
32224
+ return truncate2(firstLine(stripHtml(description)), MAX_DESCRIPTION_CHARS);
32000
32225
  const label = mcpBaseLabel(tool);
32001
32226
  const query = str("query") ?? str("text") ?? str("name");
32002
32227
  if (label && query) {
32003
32228
  const budget = Math.max(8, MAX_LABEL_CHARS - label.length - 4);
32004
- const preview = truncate(firstLine(stripHtml(query)), budget);
32229
+ const preview = truncate2(firstLine(stripHtml(query)), budget);
32005
32230
  return `${label} (${preview})`;
32006
32231
  }
32007
32232
  if (label)
32008
- return truncate(label);
32233
+ return truncate2(label);
32009
32234
  }
32010
32235
  for (const k of ["description", "file_path", "path", "url", "query", "pattern", "command"]) {
32011
32236
  const v = str(k);
32012
32237
  if (v != null && v.length > 0) {
32013
32238
  if (k === "file_path" || k === "path")
32014
- return truncate(basename(v));
32239
+ return truncate2(basename(v));
32015
32240
  if (k === "url")
32016
- return truncate(hostFromUrl(v));
32241
+ return truncate2(hostFromUrl(v));
32017
32242
  if (k === "description")
32018
- return truncate(firstLine(v), MAX_DESCRIPTION_CHARS);
32019
- return truncate(firstLine(v));
32243
+ return truncate2(firstLine(v), MAX_DESCRIPTION_CHARS);
32244
+ return truncate2(firstLine(v));
32020
32245
  }
32021
32246
  }
32022
32247
  return "";
@@ -38936,16 +39161,16 @@ function renderAccountRow(snap, opts) {
38936
39161
  const lines = [];
38937
39162
  const marker = snap.isActive ? "\u25cf " : "";
38938
39163
  if (!snap.quota) {
38939
- lines.push(`${marker}<code>${escapeHtml(snap.label)}</code> <i>quota probe failed</i>`);
39164
+ lines.push(`${marker}<code>${escapeHtml2(snap.label)}</code> <i>quota probe failed</i>`);
38940
39165
  if (snap.quotaError) {
38941
- lines.push(` <i>${escapeHtml(snap.quotaError)}</i>`);
39166
+ lines.push(` <i>${escapeHtml2(snap.quotaError)}</i>`);
38942
39167
  }
38943
39168
  return lines;
38944
39169
  }
38945
39170
  const q = snap.quota;
38946
39171
  const fiveStr = fmtPct(q.fiveHourUtilizationPct);
38947
39172
  const sevenStr = fmtPct(q.sevenDayUtilizationPct);
38948
- lines.push(`${marker}<code>${escapeHtml(snap.label)}</code> ${fiveStr} / ${sevenStr}`);
39173
+ lines.push(`${marker}<code>${escapeHtml2(snap.label)}</code> ${fiveStr} / ${sevenStr}`);
38949
39174
  const health = classifyHealth(snap);
38950
39175
  if (health === "blocked") {
38951
39176
  const win = bindingWindow(q);
@@ -39051,13 +39276,13 @@ function renderFallbackAnnouncement(input) {
39051
39276
  const limitWord = input.oldQuota ? limitWordFor(input.oldQuota) : "quota";
39052
39277
  const headerLimit = limitWord === "quota" ? "quota cap" : `${limitWord} limit`;
39053
39278
  if (!input.newLabel) {
39054
- lines.push(`\uD83D\uDD34 <b>All accounts blocked \u00b7 ${headerLimit} on ${escapeHtml(input.oldLabel)}</b>`);
39279
+ lines.push(`\uD83D\uDD34 <b>All accounts blocked \u00b7 ${headerLimit} on ${escapeHtml2(input.oldLabel)}</b>`);
39055
39280
  lines.push("");
39056
- lines.push(`Triggered by: agent <b>${escapeHtml(input.triggerAgent)}</b>`);
39281
+ lines.push(`Triggered by: agent <b>${escapeHtml2(input.triggerAgent)}</b>`);
39057
39282
  if (input.oldQuota) {
39058
39283
  const recovery = recoveryAtFor(input.oldQuota);
39059
39284
  if (recovery) {
39060
- lines.push(`${escapeHtml(input.oldLabel)} recovers ${formatAbsolute(recovery, tz)} ` + `(in ${formatRelative(recovery, now)})`);
39285
+ lines.push(`${escapeHtml2(input.oldLabel)} recovers ${formatAbsolute(recovery, tz)} ` + `(in ${formatRelative(recovery, now)})`);
39061
39286
  }
39062
39287
  }
39063
39288
  lines.push("");
@@ -39065,15 +39290,15 @@ function renderFallbackAnnouncement(input) {
39065
39290
  return lines.join(`
39066
39291
  `);
39067
39292
  }
39068
- lines.push(`\u2713 <b>Switched fleet \u00b7 ${headerLimit} on ${escapeHtml(input.oldLabel)}</b>`);
39293
+ lines.push(`\u2713 <b>Switched fleet \u00b7 ${headerLimit} on ${escapeHtml2(input.oldLabel)}</b>`);
39069
39294
  lines.push("");
39070
- lines.push(`<code>${escapeHtml(input.oldLabel)}</code> \u2192 <code>${escapeHtml(input.newLabel)}</code>`);
39071
- lines.push(`Triggered by: agent <b>${escapeHtml(input.triggerAgent)}</b>`);
39295
+ lines.push(`<code>${escapeHtml2(input.oldLabel)}</code> \u2192 <code>${escapeHtml2(input.newLabel)}</code>`);
39296
+ lines.push(`Triggered by: agent <b>${escapeHtml2(input.triggerAgent)}</b>`);
39072
39297
  lines.push("");
39073
39298
  if (input.oldQuota) {
39074
39299
  const recovery = recoveryAtFor(input.oldQuota);
39075
39300
  if (recovery) {
39076
- lines.push(`<code>${escapeHtml(input.oldLabel)}</code> recovers ` + `${formatAbsolute(recovery, tz)} (in ${formatRelative(recovery, now)})`);
39301
+ lines.push(`<code>${escapeHtml2(input.oldLabel)}</code> recovers ` + `${formatAbsolute(recovery, tz)} (in ${formatRelative(recovery, now)})`);
39077
39302
  }
39078
39303
  }
39079
39304
  if (input.newQuota) {
@@ -39081,7 +39306,7 @@ function renderFallbackAnnouncement(input) {
39081
39306
  const sevenStr = fmtPct(input.newQuota.sevenDayUtilizationPct);
39082
39307
  const hasHeadroom = input.newQuota.fiveHourUtilizationPct < THROTTLING_THRESHOLD_PCT && input.newQuota.sevenDayUtilizationPct < THROTTLING_THRESHOLD_PCT;
39083
39308
  const headroomStr = hasHeadroom ? "<i>(plenty of headroom)</i>" : "<i>(near limit \u2014 watch this)</i>";
39084
- lines.push(`<code>${escapeHtml(input.newLabel)}</code> now: ${fiveStr} of 5h \u00b7 ${sevenStr} of 7d ${headroomStr}`);
39309
+ lines.push(`<code>${escapeHtml2(input.newLabel)}</code> now: ${fiveStr} of 5h \u00b7 ${sevenStr} of 7d ${headroomStr}`);
39085
39310
  } else {
39086
39311
  lines.push(`<i>(quota probe for new account is pending \u2014 will reflect on next /auth)</i>`);
39087
39312
  }
@@ -39140,7 +39365,7 @@ function switchPriority(s) {
39140
39365
  return 2;
39141
39366
  return 3;
39142
39367
  }
39143
- function escapeHtml(s) {
39368
+ function escapeHtml2(s) {
39144
39369
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
39145
39370
  }
39146
39371
  function buildSnapshotsFromState(state3, quotas) {
@@ -39233,7 +39458,7 @@ function parseAuthCommand(text) {
39233
39458
  if (tail.length > 0) {
39234
39459
  return {
39235
39460
  kind: "help",
39236
- reason: `Unknown <code>rm</code> modifier: <code>${escapeHtml2(tail)}</code>. Use <code>/auth rm &lt;label&gt; confirm</code> to confirm.`
39461
+ reason: `Unknown <code>rm</code> modifier: <code>${escapeHtml3(tail)}</code>. Use <code>/auth rm &lt;label&gt; confirm</code> to confirm.`
39237
39462
  };
39238
39463
  }
39239
39464
  return { kind: "rm-prompt", label };
@@ -39253,7 +39478,7 @@ function parseAuthCommand(text) {
39253
39478
  if (sub !== "override") {
39254
39479
  return {
39255
39480
  kind: "help",
39256
- reason: `Unknown <code>agent</code> subcommand: <code>${escapeHtml2(sub || "(none)")}</code>. Try <code>/auth agent override &lt;agent&gt; &lt;label|clear&gt;</code>.`
39481
+ reason: `Unknown <code>agent</code> subcommand: <code>${escapeHtml3(sub || "(none)")}</code>. Try <code>/auth agent override &lt;agent&gt; &lt;label|clear&gt;</code>.`
39257
39482
  };
39258
39483
  }
39259
39484
  const agent = parts[2];
@@ -39275,7 +39500,7 @@ function parseAuthCommand(text) {
39275
39500
  case "help":
39276
39501
  return { kind: "help" };
39277
39502
  default:
39278
- return { kind: "help", reason: `Unknown verb: <code>${escapeHtml2(verb)}</code>` };
39503
+ return { kind: "help", reason: `Unknown verb: <code>${escapeHtml3(verb)}</code>` };
39279
39504
  }
39280
39505
  }
39281
39506
  async function handleAuthCommand(parsed, ctx) {
@@ -39329,7 +39554,7 @@ async function handleAuthCommand(parsed, ctx) {
39329
39554
  };
39330
39555
  } catch (err) {
39331
39556
  return {
39332
- text: `<b>/auth show failed:</b> ${escapeHtml2(err?.message ?? String(err))}`,
39557
+ text: `<b>/auth show failed:</b> ${escapeHtml3(err?.message ?? String(err))}`,
39333
39558
  html: true
39334
39559
  };
39335
39560
  }
@@ -39341,7 +39566,7 @@ async function handleAuthCommand(parsed, ctx) {
39341
39566
  const agent = state3.agents.find((a) => a.name === agentName3);
39342
39567
  if (!agent) {
39343
39568
  return {
39344
- text: `<b>/auth show:</b> no agent named <code>${escapeHtml2(agentName3)}</code> in broker view.
39569
+ text: `<b>/auth show:</b> no agent named <code>${escapeHtml3(agentName3)}</code> in broker view.
39345
39570
  ` + `Run <code>/auth show</code> for the fleet snapshot.`,
39346
39571
  html: true
39347
39572
  };
@@ -39349,7 +39574,7 @@ async function handleAuthCommand(parsed, ctx) {
39349
39574
  return { text: renderAgentDetail(state3, agent), html: true };
39350
39575
  } catch (err) {
39351
39576
  return {
39352
- text: `<b>/auth show failed:</b> ${escapeHtml2(err?.message ?? String(err))}`,
39577
+ text: `<b>/auth show failed:</b> ${escapeHtml3(err?.message ?? String(err))}`,
39353
39578
  html: true
39354
39579
  };
39355
39580
  }
@@ -39371,13 +39596,13 @@ async function handleAuthCommand(parsed, ctx) {
39371
39596
  try {
39372
39597
  const result = await ctx.client.setActive(parsed.label);
39373
39598
  return {
39374
- text: `<b>Active account \u2192</b> <code>${escapeHtml2(result.active)}</code>
39599
+ text: `<b>Active account \u2192</b> <code>${escapeHtml3(result.active)}</code>
39375
39600
  ` + `Re-mirrored credentials for ${result.fanned.length} agent${result.fanned.length === 1 ? "" : "s"}.`,
39376
39601
  html: true
39377
39602
  };
39378
39603
  } catch (err) {
39379
39604
  return {
39380
- text: `<b>/auth use failed:</b> ${escapeHtml2(err?.message ?? String(err))}`,
39605
+ text: `<b>/auth use failed:</b> ${escapeHtml3(err?.message ?? String(err))}`,
39381
39606
  html: true
39382
39607
  };
39383
39608
  }
@@ -39395,13 +39620,13 @@ async function handleAuthCommand(parsed, ctx) {
39395
39620
  }
39396
39621
  const result = await ctx.client.setActive(nextLabel);
39397
39622
  return {
39398
- text: `<b>Rotated:</b> active \u2192 <code>${escapeHtml2(result.active)}</code>
39623
+ text: `<b>Rotated:</b> active \u2192 <code>${escapeHtml3(result.active)}</code>
39399
39624
  ` + `Re-mirrored credentials for ${result.fanned.length} agent${result.fanned.length === 1 ? "" : "s"}.`,
39400
39625
  html: true
39401
39626
  };
39402
39627
  } catch (err) {
39403
39628
  return {
39404
- text: `<b>/auth rotate failed:</b> ${escapeHtml2(err?.message ?? String(err))}`,
39629
+ text: `<b>/auth rotate failed:</b> ${escapeHtml3(err?.message ?? String(err))}`,
39405
39630
  html: true
39406
39631
  };
39407
39632
  }
@@ -39412,20 +39637,20 @@ async function handleAuthCommand(parsed, ctx) {
39412
39637
  state3 = await ctx.client.listState();
39413
39638
  } catch (err) {
39414
39639
  return {
39415
- text: `<b>/auth rm failed:</b> ${escapeHtml2(err?.message ?? String(err))}`,
39640
+ text: `<b>/auth rm failed:</b> ${escapeHtml3(err?.message ?? String(err))}`,
39416
39641
  html: true
39417
39642
  };
39418
39643
  }
39419
39644
  const exists = state3.accounts.some((a) => a.label === parsed.label);
39420
39645
  if (!exists) {
39421
39646
  return {
39422
- text: `<b>/auth rm:</b> no account named <code>${escapeHtml2(parsed.label)}</code>. ` + `Run <code>/auth show</code> for the current list.`,
39647
+ text: `<b>/auth rm:</b> no account named <code>${escapeHtml3(parsed.label)}</code>. ` + `Run <code>/auth show</code> for the current list.`,
39423
39648
  html: true
39424
39649
  };
39425
39650
  }
39426
39651
  if (state3.active === parsed.label) {
39427
39652
  return {
39428
- text: `<b>/auth rm refused.</b> <code>${escapeHtml2(parsed.label)}</code> is the fleet active. ` + `Switch with <code>/auth use &lt;other&gt;</code> or <code>/auth rotate</code> first.`,
39653
+ text: `<b>/auth rm refused.</b> <code>${escapeHtml3(parsed.label)}</code> is the fleet active. ` + `Switch with <code>/auth use &lt;other&gt;</code> or <code>/auth rotate</code> first.`,
39429
39654
  html: true
39430
39655
  };
39431
39656
  }
@@ -39436,10 +39661,10 @@ async function handleAuthCommand(parsed, ctx) {
39436
39661
  });
39437
39662
  }
39438
39663
  return {
39439
- text: `<b>\u26a0 /auth rm</b> \u2014 about to remove <code>${escapeHtml2(parsed.label)}</code> from the broker.
39440
- ` + `The fleet active is unchanged. Any agent override pointing at <code>${escapeHtml2(parsed.label)}</code> will stop working.
39664
+ text: `<b>\u26a0 /auth rm</b> \u2014 about to remove <code>${escapeHtml3(parsed.label)}</code> from the broker.
39665
+ ` + `The fleet active is unchanged. Any agent override pointing at <code>${escapeHtml3(parsed.label)}</code> will stop working.
39441
39666
 
39442
- ` + `Send <code>/auth rm ${escapeHtml2(parsed.label)} confirm</code> within ${Math.round(AUTH_RM_CONFIRM_TTL_MS / 1000)}s to proceed.`,
39667
+ ` + `Send <code>/auth rm ${escapeHtml3(parsed.label)} confirm</code> within ${Math.round(AUTH_RM_CONFIRM_TTL_MS / 1000)}s to proceed.`,
39443
39668
  html: true
39444
39669
  };
39445
39670
  }
@@ -39451,7 +39676,7 @@ async function handleAuthCommand(parsed, ctx) {
39451
39676
  pendingAuthRmFlows.delete(ctx.chatId);
39452
39677
  }
39453
39678
  return {
39454
- text: `<b>/auth rm:</b> no pending confirm for <code>${escapeHtml2(parsed.label)}</code> (expired or not started). ` + `Send <code>/auth rm ${escapeHtml2(parsed.label)}</code> first.`,
39679
+ text: `<b>/auth rm:</b> no pending confirm for <code>${escapeHtml3(parsed.label)}</code> (expired or not started). ` + `Send <code>/auth rm ${escapeHtml3(parsed.label)}</code> first.`,
39455
39680
  html: true
39456
39681
  };
39457
39682
  }
@@ -39460,12 +39685,12 @@ async function handleAuthCommand(parsed, ctx) {
39460
39685
  try {
39461
39686
  const data = await ctx.client.rmAccount(parsed.label);
39462
39687
  return {
39463
- text: `<b>Removed</b> <code>${escapeHtml2(data.label)}</code> from the broker.`,
39688
+ text: `<b>Removed</b> <code>${escapeHtml3(data.label)}</code> from the broker.`,
39464
39689
  html: true
39465
39690
  };
39466
39691
  } catch (err) {
39467
39692
  return {
39468
- text: `<b>/auth rm failed:</b> ${escapeHtml2(err?.message ?? String(err))}`,
39693
+ text: `<b>/auth rm failed:</b> ${escapeHtml3(err?.message ?? String(err))}`,
39469
39694
  html: true
39470
39695
  };
39471
39696
  }
@@ -39476,7 +39701,7 @@ async function handleAuthCommand(parsed, ctx) {
39476
39701
  const targets = parsed.label ? state3.accounts.filter((a) => a.label === parsed.label).map((a) => a.label) : state3.accounts.map((a) => a.label);
39477
39702
  if (parsed.label && targets.length === 0) {
39478
39703
  return {
39479
- text: `<b>/auth refresh:</b> no account named <code>${escapeHtml2(parsed.label)}</code>.`,
39704
+ text: `<b>/auth refresh:</b> no account named <code>${escapeHtml3(parsed.label)}</code>.`,
39480
39705
  html: true
39481
39706
  };
39482
39707
  }
@@ -39495,10 +39720,10 @@ async function handleAuthCommand(parsed, ctx) {
39495
39720
  formatExpiryAbs(data.expiresAt)
39496
39721
  ]);
39497
39722
  } catch (err) {
39498
- failures.push(`${label}: ${escapeHtml2(err?.message ?? String(err))}`);
39723
+ failures.push(`${label}: ${escapeHtml3(err?.message ?? String(err))}`);
39499
39724
  }
39500
39725
  }
39501
- const head = targets.length === 1 ? `<b>Refreshed</b> <code>${escapeHtml2(targets[0])}</code>` : `<b>Refreshed</b> ${rows.length - 1}/${targets.length} account${targets.length === 1 ? "" : "s"}`;
39726
+ const head = targets.length === 1 ? `<b>Refreshed</b> <code>${escapeHtml3(targets[0])}</code>` : `<b>Refreshed</b> ${rows.length - 1}/${targets.length} account${targets.length === 1 ? "" : "s"}`;
39502
39727
  const table = rows.length > 1 ? `
39503
39728
  <pre>${alignTable(rows)}</pre>` : "";
39504
39729
  const failBlock = failures.length > 0 ? `
@@ -39508,7 +39733,7 @@ ${failures.map((f) => ` ${f}`).join(`
39508
39733
  return { text: head + table + failBlock, html: true };
39509
39734
  } catch (err) {
39510
39735
  return {
39511
- text: `<b>/auth refresh failed:</b> ${escapeHtml2(err?.message ?? String(err))}`,
39736
+ text: `<b>/auth refresh failed:</b> ${escapeHtml3(err?.message ?? String(err))}`,
39512
39737
  html: true
39513
39738
  };
39514
39739
  }
@@ -39517,12 +39742,12 @@ ${failures.map((f) => ` ${f}`).join(`
39517
39742
  try {
39518
39743
  const data = await ctx.client.setOverride(parsed.agent, parsed.label);
39519
39744
  return {
39520
- text: `<b>Override set.</b> <code>${escapeHtml2(data.agent)}</code> is now pinned to ` + `<code>${escapeHtml2(data.account ?? parsed.label)}</code>.`,
39745
+ text: `<b>Override set.</b> <code>${escapeHtml3(data.agent)}</code> is now pinned to ` + `<code>${escapeHtml3(data.account ?? parsed.label)}</code>.`,
39521
39746
  html: true
39522
39747
  };
39523
39748
  } catch (err) {
39524
39749
  return {
39525
- text: `<b>/auth agent override failed:</b> ${escapeHtml2(err?.message ?? String(err))}`,
39750
+ text: `<b>/auth agent override failed:</b> ${escapeHtml3(err?.message ?? String(err))}`,
39526
39751
  html: true
39527
39752
  };
39528
39753
  }
@@ -39531,12 +39756,12 @@ ${failures.map((f) => ` ${f}`).join(`
39531
39756
  try {
39532
39757
  const data = await ctx.client.setOverride(parsed.agent, null);
39533
39758
  return {
39534
- text: `<b>Override cleared</b> on <code>${escapeHtml2(data.agent)}</code> ` + `\u2014 back to fleet active.`,
39759
+ text: `<b>Override cleared</b> on <code>${escapeHtml3(data.agent)}</code> ` + `\u2014 back to fleet active.`,
39535
39760
  html: true
39536
39761
  };
39537
39762
  } catch (err) {
39538
39763
  return {
39539
- text: `<b>/auth agent override failed:</b> ${escapeHtml2(err?.message ?? String(err))}`,
39764
+ text: `<b>/auth agent override failed:</b> ${escapeHtml3(err?.message ?? String(err))}`,
39540
39765
  html: true
39541
39766
  };
39542
39767
  }
@@ -39615,7 +39840,7 @@ function formatAccountsTable(state3, now) {
39615
39840
  const status = isActive ? "active" : acc.exhausted ? "exhausted" : "available";
39616
39841
  const expires = acc.expiresAt != null ? formatRelativeMs(acc.expiresAt - now) : "\u2014";
39617
39842
  const quotaReset = acc.exhausted && acc.exhausted_until != null && acc.exhausted_until > now ? formatRelativeMs(acc.exhausted_until - now) : "\u2014";
39618
- rows.push([`${marker} ${escapeHtml2(acc.label)}`, status, expires, quotaReset]);
39843
+ rows.push([`${marker} ${escapeHtml3(acc.label)}`, status, expires, quotaReset]);
39619
39844
  }
39620
39845
  return alignTable(rows);
39621
39846
  }
@@ -39623,15 +39848,15 @@ function formatAgentsTable(state3) {
39623
39848
  const rows = [["AGENT", "ACTIVE", "SOURCE"]];
39624
39849
  for (const a of state3.agents) {
39625
39850
  const source = a.override ? "override" : a.account === state3.active ? "fleet-active" : "pinned";
39626
- rows.push([escapeHtml2(a.name), escapeHtml2(a.account), source]);
39851
+ rows.push([escapeHtml3(a.name), escapeHtml3(a.account), source]);
39627
39852
  }
39628
39853
  return alignTable(rows);
39629
39854
  }
39630
39855
  function renderAgentDetail(state3, agent, now = Date.now()) {
39631
39856
  const lines = [];
39632
- lines.push(`<b>${escapeHtml2(agent.name)}</b>`);
39857
+ lines.push(`<b>${escapeHtml3(agent.name)}</b>`);
39633
39858
  const source = agent.override ? "override" : "fleet-active";
39634
- lines.push(`Active account: <code>${escapeHtml2(agent.account)}</code> (${source})`);
39859
+ lines.push(`Active account: <code>${escapeHtml3(agent.account)}</code> (${source})`);
39635
39860
  const acct = state3.accounts.find((a) => a.label === agent.account);
39636
39861
  if (acct) {
39637
39862
  const expRel = acct.expiresAt != null ? formatRelativeMs(acct.expiresAt - now) : "\u2014";
@@ -39654,11 +39879,11 @@ function formatConsumersTable(state3, now) {
39654
39879
  const rows = [["CONSUMER", "ACTIVE", "STATUS"]];
39655
39880
  for (const c of state3.consumers) {
39656
39881
  const status = c.last_seen_at == null ? "socket bound" : `socket bound (last seen ${formatRelativeMs(now - c.last_seen_at)} ago)`;
39657
- rows.push([escapeHtml2(c.name), escapeHtml2(c.account), status]);
39882
+ rows.push([escapeHtml3(c.name), escapeHtml3(c.account), status]);
39658
39883
  }
39659
39884
  return alignTable(rows);
39660
39885
  }
39661
- function escapeHtml2(s) {
39886
+ function escapeHtml3(s) {
39662
39887
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
39663
39888
  }
39664
39889
  function formatRelativeMs(ms) {
@@ -40965,11 +41190,11 @@ function renderTable(headers, rows) {
40965
41190
  if (colCount <= 3 && rowCount <= 6) {
40966
41191
  const bullets = rows.map((row) => {
40967
41192
  const cells = headers.map((_, i) => (row[i] ?? "").trim());
40968
- const key = escapeHtml4(cells[0] || "\u2014");
40969
- const rest = cells.slice(1).filter((v) => v !== "").map((v) => ` \u2014 ${escapeHtml4(v)}`).join("");
41193
+ const key = escapeHtml5(cells[0] || "\u2014");
41194
+ const rest = cells.slice(1).filter((v) => v !== "").map((v) => ` \u2014 ${escapeHtml5(v)}`).join("");
40970
41195
  return `\u2022 <b>${key}</b>${rest}`;
40971
41196
  });
40972
- const headerLine = colCount >= 2 ? `<b>${headers.map((h) => escapeHtml4(h)).join(" / ")}</b>
41197
+ const headerLine = colCount >= 2 ? `<b>${headers.map((h) => escapeHtml5(h)).join(" / ")}</b>
40973
41198
  ` : "";
40974
41199
  return headerLine + bullets.join(`
40975
41200
  `);
@@ -40984,7 +41209,7 @@ function renderTable(headers, rows) {
40984
41209
  sepLine,
40985
41210
  ...rows.map((r) => formatRow(r))
40986
41211
  ];
40987
- return `<pre>${escapeHtml4(lines.join(`
41212
+ return `<pre>${escapeHtml5(lines.join(`
40988
41213
  `))}</pre>`;
40989
41214
  }
40990
41215
  function extractMarkdownTables(text, store2, placeholderPrefix) {
@@ -41032,7 +41257,7 @@ function markdownToHtml(text) {
41032
41257
  const INLINE_PH = "\x00CODEINLINE";
41033
41258
  const TABLE_PH = "\x00TABLEBLOCK";
41034
41259
  let result = text.replace(/```(\w*)\n([\s\S]*?)```/g, (_m, lang, code) => {
41035
- const escaped = escapeHtml4(code.replace(/\n$/, ""));
41260
+ const escaped = escapeHtml5(code.replace(/\n$/, ""));
41036
41261
  const cls = lang ? ` class="language-${lang}"` : "";
41037
41262
  const idx = codeBlocks.length;
41038
41263
  codeBlocks.push(`<pre><code${cls}>${escaped}</code></pre>`);
@@ -41045,7 +41270,7 @@ function markdownToHtml(text) {
41045
41270
  const inlineCodes = [];
41046
41271
  result = result.replace(/`([^`\n]+)`/g, (_m, code) => {
41047
41272
  const idx = inlineCodes.length;
41048
- inlineCodes.push(`<code>${escapeHtml4(code)}</code>`);
41273
+ inlineCodes.push(`<code>${escapeHtml5(code)}</code>`);
41049
41274
  return `${INLINE_PH}${idx}\x00`;
41050
41275
  });
41051
41276
  const htmlTags = [];
@@ -41057,24 +41282,24 @@ function markdownToHtml(text) {
41057
41282
  htmlTags.push(match);
41058
41283
  return `${HTMLTAG_PH}${idx}\x00`;
41059
41284
  });
41060
- result = escapeHtml4(result);
41285
+ result = escapeHtml5(result);
41061
41286
  result = result.replace(/\*\*(.+?)\*\*/g, "<b>$1</b>");
41062
41287
  result = result.replace(/(?<!\*)\*(?!\*)(.+?)(?<!\*)\*(?!\*)/g, "<i>$1</i>");
41063
41288
  result = result.replace(/(?<![\w_])_(?!_)([^_\n]+?)_(?![\w_])/g, "<i>$1</i>");
41064
41289
  result = result.replace(/~~(.+?)~~/g, "<s>$1</s>");
41065
- result = result.replace(new RegExp(`${escapeHtml4(BLOCK_PH)}(\\d+)${escapeHtml4("\x00")}`, "g"), (_m, idx) => codeBlocks[Number(idx)]);
41066
- result = result.replace(new RegExp(`${escapeHtml4(TABLE_PH)}(\\d+)${escapeHtml4("\x00")}`, "g"), (_m, idx) => codeBlocks[Number(idx)]);
41067
- result = result.replace(new RegExp(`${escapeHtml4(INLINE_PH)}(\\d+)${escapeHtml4("\x00")}`, "g"), (_m, idx) => inlineCodes[Number(idx)]);
41290
+ result = result.replace(new RegExp(`${escapeHtml5(BLOCK_PH)}(\\d+)${escapeHtml5("\x00")}`, "g"), (_m, idx) => codeBlocks[Number(idx)]);
41291
+ result = result.replace(new RegExp(`${escapeHtml5(TABLE_PH)}(\\d+)${escapeHtml5("\x00")}`, "g"), (_m, idx) => codeBlocks[Number(idx)]);
41292
+ result = result.replace(new RegExp(`${escapeHtml5(INLINE_PH)}(\\d+)${escapeHtml5("\x00")}`, "g"), (_m, idx) => inlineCodes[Number(idx)]);
41068
41293
  const ALLOWED_LINK_SCHEMES = /^(?:https?|mailto|tel|tg):/i;
41069
41294
  result = result.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_m, linkText, url) => {
41070
41295
  const safe = ALLOWED_LINK_SCHEMES.test(url.trim()) ? url.trim() : "#";
41071
- return `<a href="${escapeHtml4(safe)}">${linkText}</a>`;
41296
+ return `<a href="${escapeHtml5(safe)}">${linkText}</a>`;
41072
41297
  });
41073
41298
  result = result.replace(/(?<![<\/\w>])(\b[\w][\w.-]*\.(?:ts|js|py|rs|go|json|yaml|yml|toml|md|txt|sh|bash|zsh|css|html|xml|sql|env|cfg|conf|ini|log|csv|tsx|jsx|vue|svelte|rb|java|kt|swift|c|cpp|h|hpp|zig|asm|wasm|lock|mod|sum)\b)(?![^<]*>)/g, "<code>$1</code>");
41074
- result = result.replace(new RegExp(`${escapeHtml4(HTMLTAG_PH)}(\\d+)${escapeHtml4("\x00")}`, "g"), (_m, idx) => htmlTags[Number(idx)]);
41299
+ result = result.replace(new RegExp(`${escapeHtml5(HTMLTAG_PH)}(\\d+)${escapeHtml5("\x00")}`, "g"), (_m, idx) => htmlTags[Number(idx)]);
41075
41300
  return result;
41076
41301
  }
41077
- function escapeHtml4(text) {
41302
+ function escapeHtml5(text) {
41078
41303
  return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
41079
41304
  }
41080
41305
  function telegramHtmlToPlainText(html) {
@@ -41475,7 +41700,7 @@ async function finalizeCallback(ctx, opts) {
41475
41700
  }
41476
41701
 
41477
41702
  // welcome-text.ts
41478
- function escapeHtml5(text) {
41703
+ function escapeHtml6(text) {
41479
41704
  return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
41480
41705
  }
41481
41706
  function formatAuthLine(auth) {
@@ -41487,13 +41712,13 @@ function formatAuthLine(auth) {
41487
41712
  return "\u2717 not authenticated";
41488
41713
  }
41489
41714
  const sub = auth.subscription_type ?? "subscription";
41490
- const expires = auth.expires_in ? ` \u00b7 expires ${escapeHtml5(auth.expires_in)}` : "";
41491
- return `\u2713 ${escapeHtml5(sub)}${expires}`;
41715
+ const expires = auth.expires_in ? ` \u00b7 expires ${escapeHtml6(auth.expires_in)}` : "";
41716
+ return `\u2713 ${escapeHtml6(sub)}${expires}`;
41492
41717
  }
41493
41718
  function formatAgentLine(meta) {
41494
41719
  const m = meta.model && meta.model.length > 0 ? meta.model : "default";
41495
- const topic = meta.topicName ? ` \u00b7 topic: ${escapeHtml5([meta.topicEmoji, meta.topicName].filter(Boolean).join(" "))}` : "";
41496
- return `<b>${escapeHtml5(meta.agentName)}</b> \u00b7 model: <code>${escapeHtml5(m)}</code>${topic}`;
41720
+ const topic = meta.topicName ? ` \u00b7 topic: ${escapeHtml6([meta.topicEmoji, meta.topicName].filter(Boolean).join(" "))}` : "";
41721
+ return `<b>${escapeHtml6(meta.agentName)}</b> \u00b7 model: <code>${escapeHtml6(m)}</code>${topic}`;
41497
41722
  }
41498
41723
  function startText(agentName3, dmDisabled) {
41499
41724
  if (dmDisabled)
@@ -41501,7 +41726,7 @@ function startText(agentName3, dmDisabled) {
41501
41726
  return [
41502
41727
  `<b>Switchroom</b> \u2014 Telegram on your Claude Pro or Max subscription.`,
41503
41728
  ``,
41504
- `This bot is the <b>${escapeHtml5(agentName3)}</b> agent. Pair first, then send messages here and they reach the agent; replies and reactions come back.`,
41729
+ `This bot is the <b>${escapeHtml6(agentName3)}</b> agent. Pair first, then send messages here and they reach the agent; replies and reactions come back.`,
41505
41730
  ``,
41506
41731
  `<b>To pair:</b>`,
41507
41732
  `1. DM me anything \u2014 you'll get a 6-char code`,
@@ -41515,7 +41740,7 @@ function helpText(agentName3) {
41515
41740
  return [
41516
41741
  `<b>Switchroom</b> \u2014 your Pro/Max subscription, wired to Telegram.`,
41517
41742
  ``,
41518
- `This bot is the <b>${escapeHtml5(agentName3)}</b> agent. Text and photos route through to it; replies, reactions and progress cards come back.`,
41743
+ `This bot is the <b>${escapeHtml6(agentName3)}</b> agent. Text and photos route through to it; replies, reactions and progress cards come back.`,
41519
41744
  ``,
41520
41745
  `Tool approvals surface as inline buttons (\u2705 / \u274c) or via <code>/approve</code>, <code>/deny</code>, <code>/pending</code>. Start a fresh session with <code>/new</code> or <code>/reset</code>.`,
41521
41746
  ``,
@@ -41534,40 +41759,40 @@ var STATUS_DOT = {
41534
41759
  function statusPairedText(params) {
41535
41760
  const { user, meta } = params;
41536
41761
  const lines = [
41537
- `Paired as ${escapeHtml5(user)}.`,
41762
+ `Paired as ${escapeHtml6(user)}.`,
41538
41763
  ``,
41539
41764
  `Agent: ${formatAgentLine(meta)}`,
41540
41765
  `Auth: ${formatAuthLine(meta.auth)}`
41541
41766
  ];
41542
41767
  if (meta.status)
41543
- lines.push(`Status: <code>${escapeHtml5(meta.status)}</code>${meta.uptime ? ` \u00b7 up ${escapeHtml5(meta.uptime)}` : ""}`);
41768
+ lines.push(`Status: <code>${escapeHtml6(meta.status)}</code>${meta.uptime ? ` \u00b7 up ${escapeHtml6(meta.uptime)}` : ""}`);
41544
41769
  if (meta.live && meta.live.length > 0) {
41545
41770
  lines.push("");
41546
41771
  lines.push("<b>Health</b>");
41547
41772
  for (const row of meta.live) {
41548
41773
  const dot = STATUS_DOT[row.status] ?? STATUS_DOT.fail;
41549
- lines.push(`${dot} <b>${escapeHtml5(row.label)}</b> ${escapeHtml5(row.detail)}`);
41774
+ lines.push(`${dot} <b>${escapeHtml6(row.label)}</b> ${escapeHtml6(row.detail)}`);
41550
41775
  }
41551
41776
  }
41552
41777
  const audit = meta.audit;
41553
41778
  if (audit) {
41554
41779
  lines.push("");
41555
41780
  if (audit.version)
41556
- lines.push(`<b>Version</b> ${escapeHtml5(audit.version)}`);
41781
+ lines.push(`<b>Version</b> ${escapeHtml6(audit.version)}`);
41557
41782
  if (meta.extendsProfile)
41558
- lines.push(`<b>Profile</b> ${escapeHtml5(meta.extendsProfile)}`);
41783
+ lines.push(`<b>Profile</b> ${escapeHtml6(meta.extendsProfile)}`);
41559
41784
  if (audit.tools)
41560
- lines.push(`<b>Tools</b> ${escapeHtml5(audit.tools)}`);
41785
+ lines.push(`<b>Tools</b> ${escapeHtml6(audit.tools)}`);
41561
41786
  if (audit.toolsDeny)
41562
- lines.push(`<b>Deny</b> ${escapeHtml5(audit.toolsDeny)}`);
41787
+ lines.push(`<b>Deny</b> ${escapeHtml6(audit.toolsDeny)}`);
41563
41788
  if (audit.skills)
41564
- lines.push(`<b>Skills</b> ${escapeHtml5(audit.skills)}`);
41789
+ lines.push(`<b>Skills</b> ${escapeHtml6(audit.skills)}`);
41565
41790
  if (audit.limits)
41566
- lines.push(`<b>Limits</b> ${escapeHtml5(audit.limits)}`);
41791
+ lines.push(`<b>Limits</b> ${escapeHtml6(audit.limits)}`);
41567
41792
  if (audit.channel)
41568
- lines.push(`<b>Channel</b> ${escapeHtml5(audit.channel)}`);
41793
+ lines.push(`<b>Channel</b> ${escapeHtml6(audit.channel)}`);
41569
41794
  if (audit.memoryBank)
41570
- lines.push(`<b>Memory</b> ${escapeHtml5(audit.memoryBank)}`);
41795
+ lines.push(`<b>Memory</b> ${escapeHtml6(audit.memoryBank)}`);
41571
41796
  }
41572
41797
  return lines.join(`
41573
41798
  `);
@@ -41575,7 +41800,7 @@ function statusPairedText(params) {
41575
41800
  function statusPendingText(code) {
41576
41801
  return `Pending pairing \u2014 run in Claude Code:
41577
41802
 
41578
- <code>/telegram:access pair ${escapeHtml5(code)}</code>`;
41803
+ <code>/telegram:access pair ${escapeHtml6(code)}</code>`;
41579
41804
  }
41580
41805
  function statusUnpairedText() {
41581
41806
  return "Not paired. Send me a message to get a pairing code.";
@@ -41604,7 +41829,7 @@ var TELEGRAM_BASE_COMMANDS = TELEGRAM_MENU_COMMANDS.slice(0, 3);
41604
41829
  var TELEGRAM_SWITCHROOM_COMMANDS = TELEGRAM_MENU_COMMANDS.slice(3);
41605
41830
  function switchroomHelpText(agentName3) {
41606
41831
  return [
41607
- `<b>Switchroom bot</b> \u2014 commands for the <b>${escapeHtml5(agentName3)}</b> agent.`,
41832
+ `<b>Switchroom bot</b> \u2014 commands for the <b>${escapeHtml6(agentName3)}</b> agent.`,
41608
41833
  ``,
41609
41834
  `<b>Session &amp; approvals</b>`,
41610
41835
  `<code>/new</code> \u2014 fresh session (flush handoff, restart)`,
@@ -41652,15 +41877,15 @@ function switchroomHelpText(agentName3) {
41652
41877
  `);
41653
41878
  }
41654
41879
  function restartAckText(agentName3) {
41655
- return `\uD83D\uDD04 Restarting <b>${escapeHtml5(agentName3)}</b>\u2026`;
41880
+ return `\uD83D\uDD04 Restarting <b>${escapeHtml6(agentName3)}</b>\u2026`;
41656
41881
  }
41657
41882
  function newSessionAckText(agentName3, flushedHandoff) {
41658
41883
  const tail = flushedHandoff ? " \u00b7 flushed handoff" : "";
41659
- return `\uD83C\uDD95 Started fresh session for <b>${escapeHtml5(agentName3)}</b>${tail} \u00b7 restarting\u2026`;
41884
+ return `\uD83C\uDD95 Started fresh session for <b>${escapeHtml6(agentName3)}</b>${tail} \u00b7 restarting\u2026`;
41660
41885
  }
41661
41886
  function resetSessionAckText(agentName3, flushedHandoff) {
41662
41887
  const tail = flushedHandoff ? " \u00b7 flushed handoff" : "";
41663
- return `\uD83D\uDD04 Reset session for <b>${escapeHtml5(agentName3)}</b>${tail} \u00b7 restarting\u2026`;
41888
+ return `\uD83D\uDD04 Reset session for <b>${escapeHtml6(agentName3)}</b>${tail} \u00b7 restarting\u2026`;
41664
41889
  }
41665
41890
 
41666
41891
  // gateway/auth-status-adapter.ts
@@ -41868,7 +42093,7 @@ function shouldShowHandoffLine() {
41868
42093
  function formatHandoffLine(topic, format) {
41869
42094
  const prefix = "\u21a9\ufe0f Picked up where we left off, ";
41870
42095
  if (format === "html") {
41871
- return `<i>${prefix}${escapeHtml6(topic)}</i>
42096
+ return `<i>${prefix}${escapeHtml7(topic)}</i>
41872
42097
 
41873
42098
  `;
41874
42099
  }
@@ -41883,7 +42108,7 @@ function formatHandoffLine(topic, format) {
41883
42108
 
41884
42109
  `;
41885
42110
  }
41886
- function escapeHtml6(s) {
42111
+ function escapeHtml7(s) {
41887
42112
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
41888
42113
  }
41889
42114
  var MDV2_SPECIALS = /[_*\[\]()~`>#+\-=|{}.!\\]/g;
@@ -45909,13 +46134,13 @@ function buildMs365CardText(p) {
45909
46134
  const lines = [];
45910
46135
  lines.push(`\uD83D\uDCC4 Microsoft 365 write approval`);
45911
46136
  lines.push("");
45912
- lines.push(`Agent: ${truncate2(p.agentName, 64)}`);
45913
- lines.push(`Tool: ${truncate2(p.toolName.replace(/^mcp__/, ""), 96)}`);
45914
- lines.push(`Item: ${truncate2(p.itemDisplayName, 256)}`);
46137
+ lines.push(`Agent: ${truncate3(p.agentName, 64)}`);
46138
+ lines.push(`Tool: ${truncate3(p.toolName.replace(/^mcp__/, ""), 96)}`);
46139
+ lines.push(`Item: ${truncate3(p.itemDisplayName, 256)}`);
45915
46140
  if (p.itemId !== "(new)") {
45916
- lines.push(`ID: ${truncate2(p.itemId, 96)}`);
46141
+ lines.push(`ID: ${truncate3(p.itemId, 96)}`);
45917
46142
  }
45918
- lines.push(`Account: ${truncate2(p.accountEmail, 96)}`);
46143
+ lines.push(`Account: ${truncate3(p.accountEmail, 96)}`);
45919
46144
  if (typeof p.sizeBytesBefore === "number" || typeof p.sizeBytesAfter === "number") {
45920
46145
  const before = p.sizeBytesBefore ?? 0;
45921
46146
  const after = p.sizeBytesAfter ?? 0;
@@ -45924,18 +46149,18 @@ function buildMs365CardText(p) {
45924
46149
  lines.push(`Size: ${humanBytes(before)} \u2192 ${humanBytes(after)} (${sign}${humanBytes(delta)})`);
45925
46150
  }
45926
46151
  if (p.deepLink) {
45927
- lines.push(`Link: ${truncate2(p.deepLink, 256)}`);
46152
+ lines.push(`Link: ${truncate3(p.deepLink, 256)}`);
45928
46153
  }
45929
46154
  if (p.agentRationale) {
45930
46155
  lines.push("");
45931
- lines.push(`\uD83D\uDCAC ${truncate2(p.agentRationale, 512)}`);
46156
+ lines.push(`\uD83D\uDCAC ${truncate3(p.agentRationale, 512)}`);
45932
46157
  }
45933
46158
  lines.push("");
45934
46159
  lines.push("\u26a0\ufe0f Weak attestation (RFC \u00a78 v1): operator should click through to verify the actual change before approving. Structural diff coming v1.5.");
45935
46160
  return lines.join(`
45936
46161
  `);
45937
46162
  }
45938
- function truncate2(s, n) {
46163
+ function truncate3(s, n) {
45939
46164
  if (s.length <= n)
45940
46165
  return s;
45941
46166
  return s.slice(0, n - 1) + "\u2026";
@@ -46051,9 +46276,9 @@ function buildDiffPreviewCard(input) {
46051
46276
  }
46052
46277
  const preview = input.preview;
46053
46278
  const bodyLines = [];
46054
- bodyLines.push(`<b>${escapeHtml7(preview.title)}</b>`);
46279
+ bodyLines.push(`<b>${escapeHtml8(preview.title)}</b>`);
46055
46280
  for (const line of preview.lines) {
46056
- bodyLines.push(escapeHtml7(line.text));
46281
+ bodyLines.push(escapeHtml8(line.text));
46057
46282
  }
46058
46283
  const text = bodyLines.join(`
46059
46284
  `);
@@ -46106,7 +46331,7 @@ function buildDiffPreviewCard(input) {
46106
46331
  }
46107
46332
  return { text, reply_markup: kb };
46108
46333
  }
46109
- function escapeHtml7(s) {
46334
+ function escapeHtml8(s) {
46110
46335
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
46111
46336
  }
46112
46337
 
@@ -46959,14 +47184,14 @@ function buildVaultSaveDiscardedInbound(opts) {
46959
47184
  // gateway/subagent-handback-inbound-builder.ts
46960
47185
  var HANDBACK_RESULT_MAX = 3000;
46961
47186
  var HANDBACK_DESC_MAX = 200;
46962
- function truncate3(s, max) {
47187
+ function truncate4(s, max) {
46963
47188
  const t = s.trim();
46964
47189
  return t.length > max ? t.slice(0, max) + "\u2026" : t;
46965
47190
  }
46966
47191
  function buildSubagentHandbackInbound(opts) {
46967
47192
  const ts = opts.nowMs ?? Date.now();
46968
- const desc = truncate3(opts.ctx.taskDescription, HANDBACK_DESC_MAX) || "(no description)";
46969
- const result = truncate3(opts.ctx.resultText, HANDBACK_RESULT_MAX);
47193
+ const desc = truncate4(opts.ctx.taskDescription, HANDBACK_DESC_MAX) || "(no description)";
47194
+ const result = truncate4(opts.ctx.resultText, HANDBACK_RESULT_MAX);
46970
47195
  const text = opts.ctx.outcome === "failed" ? `\uD83E\uDD1D A background worker you dispatched has FAILED.
46971
47196
 
46972
47197
  ` + `Task: ${desc}
@@ -47030,7 +47255,7 @@ function decideSubagentHandback(input) {
47030
47255
  var PROGRESS_RESULT_MAX = 800;
47031
47256
  var PROGRESS_DESC_MAX = 200;
47032
47257
  var DEFAULT_PROGRESS_INTERVAL_MS = 5 * 60 * 1000;
47033
- function truncate4(s, max) {
47258
+ function truncate5(s, max) {
47034
47259
  const t = s.trim();
47035
47260
  return t.length > max ? t.slice(0, max) + "\u2026" : t;
47036
47261
  }
@@ -47044,8 +47269,8 @@ function formatElapsed(ms) {
47044
47269
  }
47045
47270
  function buildSubagentProgressInbound(opts) {
47046
47271
  const ts = opts.nowMs ?? Date.now();
47047
- const desc = truncate4(opts.ctx.taskDescription, PROGRESS_DESC_MAX) || "(no description)";
47048
- const summary = truncate4(opts.ctx.latestSummary, PROGRESS_RESULT_MAX);
47272
+ const desc = truncate5(opts.ctx.taskDescription, PROGRESS_DESC_MAX) || "(no description)";
47273
+ const summary = truncate5(opts.ctx.latestSummary, PROGRESS_RESULT_MAX);
47049
47274
  const elapsed = formatElapsed(opts.ctx.elapsedMs);
47050
47275
  const expiresAt = ts + 2 * opts.ctx.progressIntervalMs;
47051
47276
  const text = `\uD83D\uDD04 A background worker you dispatched is still running.
@@ -48852,7 +49077,9 @@ function readSubTail(entry, tail, now, onDescriptionUpdate, fs2, log, db2, paren
48852
49077
  prevBucketIdx: entry.lastProgressBucketIdx,
48853
49078
  setBucketIdx: (b) => {
48854
49079
  entry.lastProgressBucketIdx = b;
48855
- }
49080
+ },
49081
+ lastTool: entry.lastTool,
49082
+ toolCount: entry.toolCount
48856
49083
  });
48857
49084
  } catch (cbErr) {
48858
49085
  log?.(`subagent-watcher: onProgress callback error ${entry.agentId}: ${cbErr.message}`);
@@ -49088,7 +49315,7 @@ function startSubagentWatcher(config) {
49088
49315
  if (idleMs >= threshold) {
49089
49316
  entry.stallNotified = true;
49090
49317
  entry.stalledAt = n;
49091
- const desc = escapeHtml8(truncate5(entry.description, 80));
49318
+ const desc = escapeHtml(truncate(entry.description, 80));
49092
49319
  const idleSec = Math.floor(idleMs / 1000);
49093
49320
  log?.(`subagent-watcher: stall detected for ${entry.agentId} (idle ${idleSec}s): ${desc}`);
49094
49321
  if (db2 != null) {
@@ -49562,7 +49789,7 @@ function renderIssuesCard(opts) {
49562
49789
  const maxSeverity = sorted[0].severity;
49563
49790
  const headerEmoji = SEVERITY_EMOJI[maxSeverity];
49564
49791
  const count = sorted.length;
49565
- const header = `${headerEmoji} <b>${escapeHtml8(opts.agentName)}</b> \u00b7 ${count} ${count === 1 ? "issue" : "issues"}`;
49792
+ const header = `${headerEmoji} <b>${escapeHtml(opts.agentName)}</b> \u00b7 ${count} ${count === 1 ? "issue" : "issues"}`;
49566
49793
  const maxRows = opts.maxRows ?? DEFAULT_MAX_ROWS;
49567
49794
  const visible = sorted.slice(0, maxRows);
49568
49795
  const overflow = sorted.length - visible.length;
@@ -49571,10 +49798,10 @@ function renderIssuesCard(opts) {
49571
49798
  const emoji = SEVERITY_EMOJI[e.severity];
49572
49799
  const occ = e.occurrences > 1 ? ` <i>(\u00d7${e.occurrences})</i>` : "";
49573
49800
  const ago = relTime(now - e.last_seen);
49574
- const head = `${emoji} <code>${escapeHtml8(e.fingerprint)}</code> ${escapeHtml8(e.summary)}${occ} \u2014 <i>${ago}</i>`;
49801
+ const head = `${emoji} <code>${escapeHtml(e.fingerprint)}</code> ${escapeHtml(e.summary)}${occ} \u2014 <i>${ago}</i>`;
49575
49802
  const remediation = formatRemediation(e.detail);
49576
49803
  return remediation == null ? head : `${head}
49577
- \u2192 <i>${escapeHtml8(remediation)}</i>`;
49804
+ \u2192 <i>${escapeHtml(remediation)}</i>`;
49578
49805
  });
49579
49806
  const lines = [header, "", ...rows];
49580
49807
  if (overflow > 0) {
@@ -49612,7 +49839,7 @@ function relTime(deltaMs) {
49612
49839
  const d = Math.round(h / 24);
49613
49840
  return `${d}d ago`;
49614
49841
  }
49615
- function extractRetryAfterSecs(err) {
49842
+ function extractRetryAfterSecs2(err) {
49616
49843
  if (err == null || typeof err !== "object")
49617
49844
  return null;
49618
49845
  const e = err;
@@ -49623,7 +49850,7 @@ function extractRetryAfterSecs(err) {
49623
49850
  return ra;
49624
49851
  return null;
49625
49852
  }
49626
- var COOLDOWN_JITTER_MS = 500;
49853
+ var COOLDOWN_JITTER_MS2 = 500;
49627
49854
  function readPersistedMessageId(path, log) {
49628
49855
  try {
49629
49856
  const raw = readFileSync29(path, "utf8");
@@ -49655,10 +49882,10 @@ function createIssuesCardHandle(opts) {
49655
49882
  let cooldownUntil = 0;
49656
49883
  const nowFn = opts.now ?? Date.now;
49657
49884
  function noteRateLimited(err, label) {
49658
- const retryAfter = extractRetryAfterSecs(err);
49885
+ const retryAfter = extractRetryAfterSecs2(err);
49659
49886
  if (retryAfter == null)
49660
49887
  return;
49661
- cooldownUntil = nowFn() + retryAfter * 1000 + COOLDOWN_JITTER_MS;
49888
+ cooldownUntil = nowFn() + retryAfter * 1000 + COOLDOWN_JITTER_MS2;
49662
49889
  log(`issues-card: ${label} 429 \u2014 backing off ${retryAfter}s (cooldown until ${cooldownUntil})`);
49663
49890
  }
49664
49891
  function setMessageId(next) {
@@ -50913,10 +51140,10 @@ function sweepStaleTurnActiveMarker(stateDir, opts) {
50913
51140
  }
50914
51141
 
50915
51142
  // ../src/build-info.ts
50916
- var VERSION = "0.14.13";
50917
- var COMMIT_SHA = "240594e9";
50918
- var COMMIT_DATE = "2026-05-29T12:19:57Z";
50919
- var LATEST_PR = 1996;
51143
+ var VERSION = "0.14.15";
51144
+ var COMMIT_SHA = "e0f95f64";
51145
+ var COMMIT_DATE = "2026-05-30T04:03:13Z";
51146
+ var LATEST_PR = 2001;
50920
51147
  var COMMITS_AHEAD_OF_TAG = 0;
50921
51148
 
50922
51149
  // gateway/boot-version.ts
@@ -51827,6 +52054,11 @@ if (!STATIC)
51827
52054
  var chatThreadMap = new Map;
51828
52055
  var activeStatusReactions = new Map;
51829
52056
  var activeReactionMsgIds = new Map;
52057
+ var deferredDoneReactions = new DeferredDoneReactions({
52058
+ countRunningWorkers: () => countRunningWorkers(),
52059
+ getActive: (key) => activeStatusReactions.get(key),
52060
+ purge: (key) => purgeReactionTracking(key)
52061
+ });
51830
52062
  var outboundDedup = new OutboundDedupCache;
51831
52063
  var chatAvailableReactions = new Map;
51832
52064
  var chatProbesInFlight = new Set;
@@ -52084,9 +52316,23 @@ function finalizeStatusReaction(chatId, threadId, reason = "done") {
52084
52316
  const ctrl = activeStatusReactions.get(key);
52085
52317
  if (!ctrl)
52086
52318
  return;
52319
+ if (reason === "done" && deferredDoneReactions.tryDefer(key, ctrl))
52320
+ return;
52321
+ deferredDoneReactions.drop(key);
52087
52322
  ctrl.finalize(reason);
52088
52323
  purgeReactionTracking(key);
52089
52324
  }
52325
+ function countRunningWorkers() {
52326
+ const reg = subagentWatcher?.getRegistry();
52327
+ if (reg == null)
52328
+ return 0;
52329
+ let n = 0;
52330
+ for (const e of reg.values()) {
52331
+ if (e.state === "running" && !e.historical)
52332
+ n++;
52333
+ }
52334
+ return n;
52335
+ }
52090
52336
  function resolveThreadId(chat_id, explicit) {
52091
52337
  if (explicit != null)
52092
52338
  return Number(explicit);
@@ -61042,6 +61288,18 @@ var didOneTimeSetup = false;
61042
61288
  if (streamMode === "checklist") {
61043
61289
  const watcherAgentDir = resolveAgentDirFromEnv();
61044
61290
  if (watcherAgentDir != null) {
61291
+ const workerFeedEnabled = process.env.SWITCHROOM_WORKER_ACTIVITY_FEED === "1";
61292
+ const workerActivityFeed = createWorkerActivityFeed({
61293
+ bot: {
61294
+ sendMessage: async (cid, text, sendOpts) => {
61295
+ const sent = await robustApiCall(() => lockedBot.api.sendMessage(cid, text, sendOpts), { chat_id: cid, verb: "worker-feed" });
61296
+ return sent;
61297
+ },
61298
+ editMessageText: (cid, mid, text, editOpts) => robustApiCall(() => lockedBot.api.editMessageText(cid, mid, text, editOpts), { chat_id: cid, verb: "worker-feed" })
61299
+ },
61300
+ log: (msg) => process.stderr.write(`telegram gateway: ${msg}
61301
+ `)
61302
+ });
61045
61303
  subagentWatcher = startSubagentWatcher({
61046
61304
  agentDir: watcherAgentDir,
61047
61305
  agentCwd: watcherAgentDir,
@@ -61071,7 +61329,18 @@ var didOneTimeSetup = false;
61071
61329
  `);
61072
61330
  }
61073
61331
  },
61074
- onFinish: ({ agentId, outcome, description, resultText }) => {
61332
+ onFinish: ({ agentId, outcome, description, resultText, toolCount, durationMs }) => {
61333
+ deferredDoneReactions.promote();
61334
+ if (workerFeedEnabled) {
61335
+ workerActivityFeed.finish(agentId, {
61336
+ description,
61337
+ lastTool: null,
61338
+ toolCount,
61339
+ latestSummary: resultText,
61340
+ elapsedMs: durationMs,
61341
+ state: outcome === "failed" ? "failed" : "done"
61342
+ });
61343
+ }
61075
61344
  let fleetChatId = "";
61076
61345
  let isBackground = false;
61077
61346
  try {
@@ -61122,7 +61391,7 @@ var didOneTimeSetup = false;
61122
61391
  process.stderr.write(`telegram gateway: subagent-handback queued agent=${agentId} outcome=${outcome} chat=${decision.chatId} resultChars=${resultText.length}
61123
61392
  `);
61124
61393
  },
61125
- onProgress: ({ agentId, description, latestSummary, elapsedMs, prevBucketIdx, setBucketIdx }) => {
61394
+ onProgress: ({ agentId, description, latestSummary, elapsedMs, prevBucketIdx, setBucketIdx, lastTool, toolCount }) => {
61126
61395
  let fleetChatId = "";
61127
61396
  let isBackground = false;
61128
61397
  try {
@@ -61143,6 +61412,17 @@ var didOneTimeSetup = false;
61143
61412
  }
61144
61413
  if (!isBackground)
61145
61414
  return;
61415
+ if (workerFeedEnabled) {
61416
+ workerActivityFeed.update(agentId, fleetChatId || (loadAccess().allowFrom[0] ?? ""), {
61417
+ description,
61418
+ lastTool,
61419
+ toolCount,
61420
+ latestSummary,
61421
+ elapsedMs,
61422
+ state: "running"
61423
+ });
61424
+ return;
61425
+ }
61146
61426
  const decision = decideSubagentProgress({
61147
61427
  disableEnvValue: process.env.SWITCHROOM_DISABLE_SUBAGENT_PROGRESS,
61148
61428
  isBackground,