switchroom 0.18.15 → 0.18.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/dist/agent-scheduler/index.js +16 -0
  2. package/dist/auth-broker/index.js +445 -10
  3. package/dist/cli/notion-write-pretool.mjs +16 -0
  4. package/dist/cli/switchroom.js +654 -479
  5. package/dist/host-control/main.js +20 -1
  6. package/dist/vault/approvals/kernel-server.js +16 -0
  7. package/dist/vault/broker/server.js +16 -0
  8. package/package.json +1 -1
  9. package/profiles/_base/start.sh.hbs +81 -139
  10. package/telegram-plugin/bridge/bridge.ts +7 -1
  11. package/telegram-plugin/dist/bridge/bridge.js +26 -1
  12. package/telegram-plugin/dist/gateway/gateway.js +1758 -661
  13. package/telegram-plugin/dist/server.js +26 -1
  14. package/telegram-plugin/draft-stream.ts +78 -3
  15. package/telegram-plugin/fleet-fallback-resume.ts +26 -3
  16. package/telegram-plugin/gateway/approval-hold.ts +49 -0
  17. package/telegram-plugin/gateway/bridge-dead-watchdog.ts +64 -22
  18. package/telegram-plugin/gateway/effort-command.ts +9 -7
  19. package/telegram-plugin/gateway/gateway.ts +627 -291
  20. package/telegram-plugin/gateway/linear-activity.ts +20 -4
  21. package/telegram-plugin/gateway/litellm-local-notice-wiring.ts +200 -0
  22. package/telegram-plugin/gateway/model-command.ts +96 -18
  23. package/telegram-plugin/gateway/pending-session-command.ts +10 -8
  24. package/telegram-plugin/gateway/premium-recovery-wiring.ts +122 -0
  25. package/telegram-plugin/gateway/session-model-file.ts +141 -172
  26. package/telegram-plugin/gateway/tier-downgrade-wiring.ts +121 -0
  27. package/telegram-plugin/gateway/unhandled-rejection-policy.ts +14 -1
  28. package/telegram-plugin/litellm-local-notice.ts +189 -0
  29. package/telegram-plugin/llm-error-present.ts +436 -0
  30. package/telegram-plugin/operator-events.ts +7 -1
  31. package/telegram-plugin/permission-title.ts +172 -10
  32. package/telegram-plugin/premium-recovery.ts +101 -0
  33. package/telegram-plugin/quota-watch.ts +16 -4
  34. package/telegram-plugin/raw-error-scrub.ts +73 -0
  35. package/telegram-plugin/retry-api-call.ts +8 -2
  36. package/telegram-plugin/runtime-metrics.ts +16 -0
  37. package/telegram-plugin/send-gate-degraded.test.ts +161 -8
  38. package/telegram-plugin/send-gate-observability.test.ts +140 -0
  39. package/telegram-plugin/send-gate-observability.ts +65 -20
  40. package/telegram-plugin/send-gate.test.ts +143 -1
  41. package/telegram-plugin/send-gate.ts +246 -23
  42. package/telegram-plugin/session-tail.ts +16 -0
  43. package/telegram-plugin/shared/local-time.ts +69 -0
  44. package/telegram-plugin/stream-controller.ts +143 -20
  45. package/telegram-plugin/stream-reply-handler.ts +12 -2
  46. package/telegram-plugin/tests/approval-hold-harness.ts +6 -6
  47. package/telegram-plugin/tests/approval-hold-outcome.test.ts +10 -2
  48. package/telegram-plugin/tests/bot-api.harness.ts +7 -2
  49. package/telegram-plugin/tests/bridge-dead-watchdog.test.ts +61 -0
  50. package/telegram-plugin/tests/draft-stream.test.ts +110 -1
  51. package/telegram-plugin/tests/effort-command.test.ts +4 -4
  52. package/telegram-plugin/tests/fleet-fallback-resume.test.ts +39 -0
  53. package/telegram-plugin/tests/flood-windows-persistence.test.ts +5 -4
  54. package/telegram-plugin/tests/gateway-pending-command-wiring.test.ts +33 -19
  55. package/telegram-plugin/tests/gateway-session-model-relaunch.test.ts +47 -127
  56. package/telegram-plugin/tests/linear-create-issue.test.ts +30 -2
  57. package/telegram-plugin/tests/litellm-local-notice.test.ts +417 -0
  58. package/telegram-plugin/tests/llm-error-present.test.ts +380 -0
  59. package/telegram-plugin/tests/model-command.test.ts +84 -1
  60. package/telegram-plugin/tests/permission-title.test.ts +167 -4
  61. package/telegram-plugin/tests/premium-recovery-wiring.test.ts +150 -0
  62. package/telegram-plugin/tests/premium-recovery.test.ts +165 -0
  63. package/telegram-plugin/tests/quota-watch.test.ts +21 -0
  64. package/telegram-plugin/tests/reaction-gate-routing.test.ts +8 -3
  65. package/telegram-plugin/tests/retry-api-call.test.ts +21 -0
  66. package/telegram-plugin/tests/session-model-file.test.ts +7 -155
  67. package/telegram-plugin/tests/stream-controller-send-gate.test.ts +521 -0
  68. package/telegram-plugin/tests/stream-reply-handler.test.ts +44 -0
  69. package/telegram-plugin/tests/tier-downgrade-wiring.test.ts +165 -0
  70. package/telegram-plugin/tests/tier-downgrade.test.ts +141 -0
  71. package/telegram-plugin/tests/unhandled-rejection-policy.test.ts +27 -1
  72. package/telegram-plugin/tests/worker-activity-feed.test.ts +212 -2
  73. package/telegram-plugin/tests/worker-feed-coalesce.test.ts +492 -0
  74. package/telegram-plugin/tier-downgrade.ts +198 -0
  75. package/telegram-plugin/tool-activity-summary.ts +99 -0
  76. package/telegram-plugin/worker-activity-feed.ts +543 -368
@@ -557,6 +557,105 @@ export function renderActivityFeedWithNested(
557
557
  })
558
558
  }
559
559
 
560
+ // ─── Combined multi-worker feed (coalesced one-message-per-chat) ────────────
561
+ //
562
+ // When 2+ background workers are live in the SAME chat/thread, they render into
563
+ // ONE shared message rather than one message each — so their edits form a
564
+ // single per-message edit stream under the send gate's 1/sec per-chat ceiling
565
+ // instead of N streams that contend and shed (#3084 per-chat bucket). Each
566
+ // worker is a compact two-line row (header + live → step); the body is capped
567
+ // at `maxRows` with a `+M more working…` spill so it stays compact/legible and
568
+ // under the rich-message wire ceiling (STATUS_CARD_CHAR_BUDGET). A single-worker
569
+ // chat still renders through
570
+ // `renderStatusCard` (the 🛠 Worker card) — this primitive owns the N≥2 case.
571
+
572
+ /** Dispatch-time task description cap for a combined-feed worker row. */
573
+ const COMBINED_ROW_DESC_MAX = 72
574
+
575
+ /** One live worker's render-relevant snapshot for the combined feed. */
576
+ export interface CombinedWorkerRow {
577
+ /** Dispatch-time task description (raw, unescaped). */
578
+ description: string
579
+ /** Wall-clock since dispatch, ms — climbs on every heartbeat re-render. */
580
+ elapsedMs: number
581
+ /** Tool calls observed so far. */
582
+ toolCount: number
583
+ /** The worker's latest step line (raw prose or friendly tool label). */
584
+ currentStep: string
585
+ /** Live model id (raw, e.g. `claude-opus-4-8`); omitted when unknown. */
586
+ model?: string
587
+ }
588
+
589
+ export interface CombinedWorkerFeedOpts {
590
+ /** Max worker rows rendered before the `+M more working…` spill line.
591
+ * The overflow is ordered oldest-hidden-first (the newest/most-recently
592
+ * active workers stay visible). */
593
+ maxRows: number
594
+ }
595
+
596
+ /**
597
+ * Render N≥1 live workers into ONE combined feed body (ready Telegram
598
+ * markdown; callers send verbatim — do NOT re-escape). Layout:
599
+ *
600
+ * 🛠 **Workers** · _N running_
601
+ * **{desc1}** _· {elapsed} · {n} tools_
602
+ * → _{step1}_
603
+ * **{desc2}** _· {elapsed} · {n} tools_
604
+ * → _{step2}_
605
+ * _+M more working…_
606
+ *
607
+ * Pure. Rows are rendered in the order supplied (the manager passes them
608
+ * dispatch-order, oldest first). `maxRows` caps the visible rows; the hidden
609
+ * remainder collapses to a single `+M more working…` line. A total-budget
610
+ * backstop drops the OLDEST visible rows one at a time (growing the spill)
611
+ * until the body fits STATUS_CARD_CHAR_BUDGET, so a burst of long descriptions
612
+ * can never overflow the wire limit. Returns null only when `rows` is empty.
613
+ */
614
+ export function renderCombinedWorkerFeed(
615
+ rows: CombinedWorkerRow[],
616
+ opts: CombinedWorkerFeedOpts,
617
+ ): string | null {
618
+ if (rows.length === 0) return null
619
+ const maxRows = Math.max(1, Math.floor(opts.maxRows))
620
+
621
+ const rowLines = (r: CombinedWorkerRow): [string, string] => {
622
+ const desc = escapeMarkdown(
623
+ truncate(stripMarkdown(r.description).replace(/\s+/g, ' ').trim() || 'background task', COMBINED_ROW_DESC_MAX),
624
+ )
625
+ const toolWord = r.toolCount === 1 ? 'tool' : 'tools'
626
+ const modelLabel = formatModelLabel(r.model)
627
+ const modelPart = modelLabel != null ? ` · ${escapeMarkdown(modelLabel)}` : ''
628
+ const header = `**${desc}** _· ${formatFeedElapsed(r.elapsedMs)} · ${r.toolCount} ${toolWord}${modelPart}_`
629
+ const stepClean = stripMarkdown(r.currentStep).replace(/\s+/g, ' ').trim()
630
+ const step =
631
+ stepClean.length > 0
632
+ ? `→ _${escapeMarkdown(truncate(stepClean, STATUS_LINE_MAX))}_`
633
+ : `→ _starting…_`
634
+ return [header, step]
635
+ }
636
+
637
+ const compose = (visibleCount: number): string => {
638
+ const shown = rows.slice(0, visibleCount)
639
+ const hidden = rows.length - shown.length
640
+ const out: string[] = [`🛠 **Workers** · _${rows.length} running_`]
641
+ for (const r of shown) {
642
+ const [h, s] = rowLines(r)
643
+ out.push(h, s)
644
+ }
645
+ if (hidden > 0) out.push(`_+${hidden} more working…_`)
646
+ return stackCardLines(out)
647
+ }
648
+
649
+ // Cap to maxRows first, then shrink further only if the char budget demands.
650
+ let visible = Math.min(rows.length, maxRows)
651
+ let body = compose(visible)
652
+ while (body.length > STATUS_CARD_CHAR_BUDGET && visible > 1) {
653
+ visible -= 1
654
+ body = compose(visible)
655
+ }
656
+ return body
657
+ }
658
+
560
659
  /**
561
660
  * Like appendActivityLine, but for a pre-computed label (from the
562
661
  * real-time PreToolUse sidecar / `tool_label` event) — the hook already