switchroom 0.14.7 → 0.14.8

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.
@@ -338,19 +338,18 @@ export function describeToolUse(
338
338
 
339
339
  // ─── Accumulating activity feed (draft-mirror Phase 2) ──────────────────────
340
340
  //
341
- // Phase 1 showed only the latest action; this accumulates the turn's actions
342
- // into a running feed like Claude Code's own UI streamed into the
343
- // ephemeral draft and cleared on reply. Chronological (oldest first, newest
344
- // last), consecutive exact-duplicates collapsed, capped to the most recent
345
- // MIRROR_MAX_LINES with a "+N earlier" header so a heavy turn stays readable
346
- // inside Telegram's compose-area draft.
341
+ // Accumulates the turn's actions into a running feed like Claude Code's
342
+ // own UI rendered into one Telegram message that edits in place and is
343
+ // cleared on reply. Chronological (oldest first, newest last), consecutive
344
+ // exact-duplicates collapsed, capped to the most recent MIRROR_MAX_LINES
345
+ // with a "+N earlier" header so a heavy turn stays readable.
347
346
 
348
347
  export const MIRROR_MAX_LINES = 6;
349
348
 
350
349
  /**
351
350
  * Append a tool_use's friendly line to the running feed (mutates `lines`)
352
- * and return the rendered draft body — or null when the tool is a surface
353
- * tool / produced no line (caller skips the draft update).
351
+ * and return the rendered feed (ready Telegram HTML) — or null when the
352
+ * tool is a surface tool / produced no line (caller skips the update).
354
353
  *
355
354
  * Dedups only consecutive identical lines (e.g. a burst of parallel Reads of
356
355
  * the same file) so distinct actions are all preserved.
@@ -368,19 +367,32 @@ export function appendActivityLine(
368
367
  return renderActivityFeed(lines);
369
368
  }
370
369
 
370
+ /** Minimal HTML escape for Telegram parse_mode=HTML (matches the gateway's). */
371
+ function escapeFeedHtml(s: string): string {
372
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
373
+ }
374
+
371
375
  /**
372
- * Render the accumulated feed as a plain-text block (one action per line).
373
- * The caller HTML-escapes + wraps it for Telegram. Returns null when empty.
374
- *
375
- * Newest-last chronological order; capped to the last MIRROR_MAX_LINES with a
376
- * dim "+N earlier" header when the turn ran longer.
376
+ * Render the accumulated feed as ready Telegram HTML one action per line,
377
+ * newest last. The current (newest) step is bold with a `→`; finished steps
378
+ * are italic with a `✓`. Capped to the last MIRROR_MAX_LINES with a dim
379
+ * `✓ +N earlier…` header when the turn ran longer. Returns null when empty.
380
+ * Callers send the result verbatim do NOT re-escape or re-wrap it.
377
381
  */
378
382
  export function renderActivityFeed(lines: string[]): string | null {
379
383
  if (lines.length === 0) return null;
380
384
  const shown = lines.slice(-MIRROR_MAX_LINES);
381
385
  const hidden = lines.length - shown.length;
382
- const body = shown.map((l) => `· ${l}`).join("\n");
383
- return hidden > 0 ? +${hidden} earlier…\n${body}` : body;
386
+ const out: string[] = [];
387
+ if (hidden > 0) out.push(`<i>✓ +${hidden} earlier…</i>`);
388
+ const lastIdx = shown.length - 1;
389
+ // Newest line = in-progress step (bold, →); earlier = done (italic, ✓).
390
+ // Returns ready Telegram HTML — callers must NOT re-escape or re-wrap it.
391
+ shown.forEach((l, i) => {
392
+ const esc = escapeFeedHtml(l);
393
+ out.push(i === lastIdx ? `<b>→ ${esc}</b>` : `<i>✓ ${esc}</i>`);
394
+ });
395
+ return out.join("\n");
384
396
  }
385
397
 
386
398
  /**