switchroom 0.18.3 → 0.18.6

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 (101) hide show
  1. package/dist/agent-scheduler/index.js +2 -1
  2. package/dist/auth-broker/index.js +2 -1
  3. package/dist/cli/notion-write-pretool.mjs +2 -1
  4. package/dist/cli/switchroom.js +119 -74
  5. package/dist/host-control/main.js +3 -2
  6. package/dist/vault/approvals/kernel-server.js +2 -1
  7. package/dist/vault/broker/server.js +37 -8
  8. package/package.json +1 -1
  9. package/skills/switchroom-release/SKILL.md +78 -0
  10. package/telegram-plugin/auth-snapshot-format.ts +15 -1
  11. package/telegram-plugin/dist/gateway/gateway.js +922 -459
  12. package/telegram-plugin/gateway/gateway.ts +260 -21
  13. package/telegram-plugin/gateway/resolve-person.ts +304 -0
  14. package/telegram-plugin/gateway/unhandled-rejection-policy.ts +21 -1
  15. package/telegram-plugin/hooks/silent-end-scan.mjs +164 -40
  16. package/telegram-plugin/operator-events.ts +21 -0
  17. package/telegram-plugin/pending-work-progress.ts +42 -7
  18. package/telegram-plugin/quota-bar-format.ts +360 -0
  19. package/telegram-plugin/subagent-watcher.ts +27 -9
  20. package/telegram-plugin/tests/auth-snapshot-format.test.ts +21 -0
  21. package/telegram-plugin/tests/gateway-boot-marker-clear.test.ts +3 -3
  22. package/telegram-plugin/tests/operator-events.test.ts +16 -0
  23. package/telegram-plugin/tests/pending-work-progress.test.ts +116 -3
  24. package/telegram-plugin/tests/quota-bar-format.test.ts +444 -0
  25. package/telegram-plugin/tests/resolve-person.test.ts +290 -0
  26. package/telegram-plugin/tests/silent-end-interrupt-stop-integration.test.ts +53 -0
  27. package/telegram-plugin/tests/silent-end-interrupt-stop-scan.test.ts +138 -0
  28. package/telegram-plugin/tests/subagent-watcher.test.ts +61 -0
  29. package/telegram-plugin/tests/unhandled-rejection-policy.test.ts +19 -0
  30. package/telegram-plugin/tests/worker-activity-feed.test.ts +97 -0
  31. package/telegram-plugin/worker-activity-feed.ts +220 -15
  32. package/profiles/default/CLAUDE.md +0 -116
  33. package/telegram-plugin/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +0 -1
  34. package/vendor/hindsight-memory/scripts/__pycache__/directive_verify.cpython-313.pyc +0 -0
  35. package/vendor/hindsight-memory/scripts/__pycache__/drain_pending.cpython-313.pyc +0 -0
  36. package/vendor/hindsight-memory/scripts/__pycache__/recall.cpython-313.pyc +0 -0
  37. package/vendor/hindsight-memory/scripts/__pycache__/retain.cpython-313.pyc +0 -0
  38. package/vendor/hindsight-memory/scripts/__pycache__/session_end.cpython-313.pyc +0 -0
  39. package/vendor/hindsight-memory/scripts/lib/__pycache__/__init__.cpython-313.pyc +0 -0
  40. package/vendor/hindsight-memory/scripts/lib/__pycache__/bank.cpython-313.pyc +0 -0
  41. package/vendor/hindsight-memory/scripts/lib/__pycache__/client.cpython-313.pyc +0 -0
  42. package/vendor/hindsight-memory/scripts/lib/__pycache__/config.cpython-313.pyc +0 -0
  43. package/vendor/hindsight-memory/scripts/lib/__pycache__/content.cpython-313.pyc +0 -0
  44. package/vendor/hindsight-memory/scripts/lib/__pycache__/daemon.cpython-313.pyc +0 -0
  45. package/vendor/hindsight-memory/scripts/lib/__pycache__/directives.cpython-313.pyc +0 -0
  46. package/vendor/hindsight-memory/scripts/lib/__pycache__/gateway_ipc.cpython-313.pyc +0 -0
  47. package/vendor/hindsight-memory/scripts/lib/__pycache__/llm.cpython-313.pyc +0 -0
  48. package/vendor/hindsight-memory/scripts/lib/__pycache__/pending.cpython-313.pyc +0 -0
  49. package/vendor/hindsight-memory/scripts/lib/__pycache__/state.cpython-313.pyc +0 -0
  50. package/vendor/hindsight-memory/scripts/lib/__pycache__/switchroom_envelope.cpython-313.pyc +0 -0
  51. package/vendor/hindsight-memory/scripts/tests/__pycache__/__init__.cpython-313.pyc +0 -0
  52. package/vendor/hindsight-memory/scripts/tests/__pycache__/test_config_client_casts.cpython-313-pytest-9.1.1.pyc +0 -0
  53. package/vendor/hindsight-memory/scripts/tests/__pycache__/test_config_client_casts.cpython-313.pyc +0 -0
  54. package/vendor/hindsight-memory/scripts/tests/__pycache__/test_directive_capture_nudge.cpython-313-pytest-9.1.1.pyc +0 -0
  55. package/vendor/hindsight-memory/scripts/tests/__pycache__/test_directive_capture_nudge.cpython-313.pyc +0 -0
  56. package/vendor/hindsight-memory/scripts/tests/__pycache__/test_directive_verify.cpython-313-pytest-9.1.1.pyc +0 -0
  57. package/vendor/hindsight-memory/scripts/tests/__pycache__/test_directive_verify.cpython-313.pyc +0 -0
  58. package/vendor/hindsight-memory/scripts/tests/__pycache__/test_directives.cpython-313-pytest-9.1.1.pyc +0 -0
  59. package/vendor/hindsight-memory/scripts/tests/__pycache__/test_directives.cpython-313.pyc +0 -0
  60. package/vendor/hindsight-memory/scripts/tests/__pycache__/test_gateway_ipc.cpython-313-pytest-9.1.1.pyc +0 -0
  61. package/vendor/hindsight-memory/scripts/tests/__pycache__/test_gateway_ipc.cpython-313.pyc +0 -0
  62. package/vendor/hindsight-memory/scripts/tests/__pycache__/test_recall_context_slice.cpython-313-pytest-9.1.1.pyc +0 -0
  63. package/vendor/hindsight-memory/scripts/tests/__pycache__/test_recall_context_slice.cpython-313.pyc +0 -0
  64. package/vendor/hindsight-memory/scripts/tests/__pycache__/test_recall_integration.cpython-313-pytest-9.1.1.pyc +0 -0
  65. package/vendor/hindsight-memory/scripts/tests/__pycache__/test_recall_integration.cpython-313.pyc +0 -0
  66. package/vendor/hindsight-memory/scripts/tests/__pycache__/test_recall_tag_filters.cpython-313-pytest-9.1.1.pyc +0 -0
  67. package/vendor/hindsight-memory/scripts/tests/__pycache__/test_recall_tag_filters.cpython-313.pyc +0 -0
  68. package/vendor/hindsight-memory/scripts/tests/__pycache__/test_recall_topic_filter.cpython-313-pytest-9.1.1.pyc +0 -0
  69. package/vendor/hindsight-memory/scripts/tests/__pycache__/test_recall_topic_filter.cpython-313.pyc +0 -0
  70. package/vendor/hindsight-memory/scripts/tests/__pycache__/test_recall_trivial_skip.cpython-313-pytest-9.1.1.pyc +0 -0
  71. package/vendor/hindsight-memory/scripts/tests/__pycache__/test_recall_trivial_skip.cpython-313.pyc +0 -0
  72. package/vendor/hindsight-memory/scripts/tests/__pycache__/test_retain_window.cpython-313-pytest-9.1.1.pyc +0 -0
  73. package/vendor/hindsight-memory/scripts/tests/__pycache__/test_retain_window.cpython-313.pyc +0 -0
  74. package/vendor/hindsight-memory/scripts/tests/__pycache__/test_sender_routing.cpython-313-pytest-9.1.1.pyc +0 -0
  75. package/vendor/hindsight-memory/scripts/tests/__pycache__/test_sender_routing.cpython-313.pyc +0 -0
  76. package/vendor/hindsight-memory/scripts/tests/__pycache__/test_switchroom_envelope.cpython-313-pytest-9.1.1.pyc +0 -0
  77. package/vendor/hindsight-memory/tests/__pycache__/conftest.cpython-313-pytest-9.0.3.pyc +0 -0
  78. package/vendor/hindsight-memory/tests/__pycache__/conftest.cpython-313-pytest-9.1.1.pyc +0 -0
  79. package/vendor/hindsight-memory/tests/__pycache__/test_bank.cpython-313-pytest-9.1.1.pyc +0 -0
  80. package/vendor/hindsight-memory/tests/__pycache__/test_bank.cpython-313.pyc +0 -0
  81. package/vendor/hindsight-memory/tests/__pycache__/test_client.cpython-313-pytest-9.1.1.pyc +0 -0
  82. package/vendor/hindsight-memory/tests/__pycache__/test_client.cpython-313.pyc +0 -0
  83. package/vendor/hindsight-memory/tests/__pycache__/test_config.cpython-313-pytest-9.0.3.pyc +0 -0
  84. package/vendor/hindsight-memory/tests/__pycache__/test_config.cpython-313-pytest-9.1.1.pyc +0 -0
  85. package/vendor/hindsight-memory/tests/__pycache__/test_config.cpython-313.pyc +0 -0
  86. package/vendor/hindsight-memory/tests/__pycache__/test_content.cpython-313-pytest-9.1.1.pyc +0 -0
  87. package/vendor/hindsight-memory/tests/__pycache__/test_content.cpython-313.pyc +0 -0
  88. package/vendor/hindsight-memory/tests/__pycache__/test_drain_pending.cpython-313-pytest-9.1.1.pyc +0 -0
  89. package/vendor/hindsight-memory/tests/__pycache__/test_drain_pending.cpython-313.pyc +0 -0
  90. package/vendor/hindsight-memory/tests/__pycache__/test_hooks.cpython-313-pytest-9.1.1.pyc +0 -0
  91. package/vendor/hindsight-memory/tests/__pycache__/test_hooks.cpython-313.pyc +0 -0
  92. package/vendor/hindsight-memory/tests/__pycache__/test_manifest.cpython-313-pytest-9.1.1.pyc +0 -0
  93. package/vendor/hindsight-memory/tests/__pycache__/test_manifest.cpython-313.pyc +0 -0
  94. package/vendor/hindsight-memory/tests/__pycache__/test_pending.cpython-313-pytest-9.1.1.pyc +0 -0
  95. package/vendor/hindsight-memory/tests/__pycache__/test_pending.cpython-313.pyc +0 -0
  96. package/vendor/hindsight-memory/tests/__pycache__/test_recall_exit_codes.cpython-313-pytest-9.1.1.pyc +0 -0
  97. package/vendor/hindsight-memory/tests/__pycache__/test_recall_exit_codes.cpython-313.pyc +0 -0
  98. package/vendor/hindsight-memory/tests/__pycache__/test_session_end_pending.cpython-313-pytest-9.1.1.pyc +0 -0
  99. package/vendor/hindsight-memory/tests/__pycache__/test_session_end_pending.cpython-313.pyc +0 -0
  100. package/vendor/hindsight-memory/tests/__pycache__/test_state.cpython-313-pytest-9.1.1.pyc +0 -0
  101. package/vendor/hindsight-memory/tests/__pycache__/test_state.cpython-313.pyc +0 -0
@@ -12658,6 +12658,445 @@ var init_demo_mask = __esm(() => {
12658
12658
  vaultKeyCache = new Map;
12659
12659
  });
12660
12660
 
12661
+ // auth-snapshot-format.ts
12662
+ function classifyHealth(snap, now = new Date) {
12663
+ if (!snap.quota)
12664
+ return "unknown";
12665
+ const q = snap.quota;
12666
+ if (isProbeThin(q))
12667
+ return "unknown";
12668
+ const norm = refillNormalizedUtils(q, now);
12669
+ const max = Math.max(norm.fiveHourUtilizationPct, norm.sevenDayUtilizationPct);
12670
+ if (max >= 99.5)
12671
+ return "blocked";
12672
+ if (max >= THROTTLING_THRESHOLD_PCT)
12673
+ return "throttling";
12674
+ return "healthy";
12675
+ }
12676
+ function blockedReason(snap, now = new Date) {
12677
+ if (classifyHealth(snap, now) !== "blocked")
12678
+ return null;
12679
+ return "quota-exhausted";
12680
+ }
12681
+ function bindingWindow(q) {
12682
+ if (q.representativeClaim === "seven_day")
12683
+ return "7d";
12684
+ if (q.representativeClaim === "five_hour")
12685
+ return "5h";
12686
+ return q.sevenDayUtilizationPct >= q.fiveHourUtilizationPct ? "7d" : "5h";
12687
+ }
12688
+ function formatRelative(target, now = new Date) {
12689
+ if (!target)
12690
+ return "\u2014";
12691
+ const deltaMs = target.getTime() - now.getTime();
12692
+ if (deltaMs <= 0)
12693
+ return "now";
12694
+ const totalMin = Math.round(deltaMs / 60000);
12695
+ if (totalMin < 60)
12696
+ return `${totalMin}m`;
12697
+ const h = Math.floor(totalMin / 60);
12698
+ const m = totalMin % 60;
12699
+ if (h < 24)
12700
+ return m > 0 ? `${h}h ${m}m` : `${h}h`;
12701
+ const d = Math.floor(h / 24);
12702
+ const rh = h % 24;
12703
+ return rh > 0 ? `${d}d ${rh}h` : `${d}d`;
12704
+ }
12705
+ function formatAbsolute(target, tz = "UTC") {
12706
+ if (!target)
12707
+ return "\u2014";
12708
+ return target.toLocaleString("en-US", {
12709
+ timeZone: tz,
12710
+ weekday: "short",
12711
+ hour: "numeric",
12712
+ minute: "2-digit",
12713
+ hour12: true
12714
+ });
12715
+ }
12716
+ function formatStatusTime(target, now = new Date, tz = "UTC") {
12717
+ if (!target)
12718
+ return "\u2014";
12719
+ const dayFmt = new Intl.DateTimeFormat("en-CA", {
12720
+ timeZone: tz,
12721
+ year: "numeric",
12722
+ month: "2-digit",
12723
+ day: "2-digit"
12724
+ });
12725
+ const timeFmt = new Intl.DateTimeFormat("en-US", {
12726
+ timeZone: tz,
12727
+ hour: "numeric",
12728
+ minute: "2-digit",
12729
+ hour12: true
12730
+ });
12731
+ const timeStr = timeFmt.format(target);
12732
+ if (dayFmt.format(target) === dayFmt.format(now)) {
12733
+ return timeStr;
12734
+ }
12735
+ const withinWeek = target.getTime() - now.getTime() < 7 * 24 * 60 * 60 * 1000;
12736
+ const dateFmt = new Intl.DateTimeFormat("en-US", {
12737
+ timeZone: tz,
12738
+ weekday: "short",
12739
+ ...withinWeek ? {} : { day: "numeric", month: "short" }
12740
+ });
12741
+ return `${dateFmt.format(target)} ${timeStr}`;
12742
+ }
12743
+ function fmtPct(pct) {
12744
+ return `${Math.min(100, Math.round(pct))}%`;
12745
+ }
12746
+ function displayLabel(label, opts) {
12747
+ return opts.demo ? maskEmail(label) : label;
12748
+ }
12749
+ function renderAccountRow(snap, opts) {
12750
+ const now = opts.now ?? new Date;
12751
+ const tz = opts.tz ?? "UTC";
12752
+ const lines = [];
12753
+ const marker = snap.isActive ? "\u25cf " : "";
12754
+ const label = displayLabel(snap.label, opts);
12755
+ if (!snap.quota) {
12756
+ lines.push(`${marker}\`${codeSpanSafe(label)}\` _quota probe failed_`);
12757
+ if (snap.quotaError) {
12758
+ lines.push(` _${escapeMarkdown(snap.quotaError)}_`);
12759
+ }
12760
+ return lines;
12761
+ }
12762
+ const q = snap.quota;
12763
+ if (isProbeThin(q)) {
12764
+ lines.push(`${marker}\`${codeSpanSafe(label)}\` _quota unknown (thin probe)_`);
12765
+ return lines;
12766
+ }
12767
+ const norm = refillNormalizedUtils(q, now);
12768
+ const fiveStr = fmtPct(norm.fiveHourUtilizationPct);
12769
+ const sevenStr = fmtPct(norm.sevenDayUtilizationPct);
12770
+ lines.push(`${marker}\`${codeSpanSafe(label)}\` ${fiveStr} / ${sevenStr}`);
12771
+ const health = classifyHealth(snap, now);
12772
+ if (health === "blocked") {
12773
+ const win = bindingWindow(q);
12774
+ const reset2 = win === "5h" ? q.fiveHourResetAt : q.sevenDayResetAt;
12775
+ const winLabel = win === "5h" ? "5-hour" : "7-day";
12776
+ lines.push(reset2 ? ` _quota exhausted \u2014 back ${formatAbsolute(reset2, tz)} (\`in ${formatRelative(reset2, now)}\`, ${winLabel} cap)_` : ` _quota exhausted \u2014 ${winLabel} cap, reset time unknown_`);
12777
+ return lines;
12778
+ }
12779
+ const fiveResetIn = q.fiveHourResetAt ? q.fiveHourResetAt.getTime() - now.getTime() : Infinity;
12780
+ const sevenResetIn = q.sevenDayResetAt ? q.sevenDayResetAt.getTime() - now.getTime() : Infinity;
12781
+ const fiveFirst = fiveResetIn <= sevenResetIn;
12782
+ const fiveSeg = q.fiveHourResetAt ? `5h refills ${formatAbsolute(q.fiveHourResetAt, tz)} (\`in ${formatRelative(q.fiveHourResetAt, now)}\`)` : "5h refills \u2014";
12783
+ const sevenSeg = q.sevenDayResetAt ? `7d resets ${formatAbsolute(q.sevenDayResetAt, tz)} (\`in ${formatRelative(q.sevenDayResetAt, now)}\`)` : "7d resets \u2014";
12784
+ lines.push(` _${fiveFirst ? fiveSeg : sevenSeg}_`);
12785
+ lines.push(` _${fiveFirst ? sevenSeg : fiveSeg}_`);
12786
+ if (q.overageDisabledReason != null && OVERAGE_EXHAUSTED_REASONS.has(q.overageDisabledReason)) {
12787
+ lines.push(` _overage off (${escapeMarkdown(q.overageDisabledReason)}) \u2014 serving from quota_`);
12788
+ }
12789
+ return lines;
12790
+ }
12791
+ function formatAgeStamp(atMs, now = new Date) {
12792
+ const ageSec = Math.max(0, Math.round((now.getTime() - atMs) / 1000));
12793
+ return ageSec < 60 ? `${ageSec}s ago` : `${Math.round(ageSec / 60)}m ago`;
12794
+ }
12795
+ function tableCell(s) {
12796
+ return s.replace(/\\/g, "\\\\").replace(/\|/g, "\\|").replace(/\r?\n/g, " ");
12797
+ }
12798
+ function pctCells(snap, now) {
12799
+ if (!snap.quota)
12800
+ return { five: "\u2014", seven: "\u2014" };
12801
+ if (isProbeThin(snap.quota))
12802
+ return { five: "?", seven: "?" };
12803
+ const norm = refillNormalizedUtils(snap.quota, now);
12804
+ return { five: fmtPct(norm.fiveHourUtilizationPct), seven: fmtPct(norm.sevenDayUtilizationPct) };
12805
+ }
12806
+ function windowResetCell(snap, now, tz, win) {
12807
+ if (!snap.quota) {
12808
+ return snap.quotaError ? `probe failed (${snap.quotaError})` : "\u2014";
12809
+ }
12810
+ if (isProbeThin(snap.quota))
12811
+ return "quota unknown";
12812
+ const reset2 = win === "5h" ? snap.quota.fiveHourResetAt : snap.quota.sevenDayResetAt;
12813
+ if (!reset2)
12814
+ return "\u2014";
12815
+ return `${formatStatusTime(reset2, now, tz)} (in ${formatRelative(reset2, now)})`;
12816
+ }
12817
+ function renderAuthSnapshotFormat2(snapshots, opts = {}) {
12818
+ const now = opts.now ?? new Date;
12819
+ const tz = opts.tz ?? "UTC";
12820
+ const lines = [];
12821
+ lines.push("\uD83D\uDD0B **Auth \u2014 fleet status**");
12822
+ const ordered = [...snapshots].sort((a, b) => {
12823
+ if (a.isActive !== b.isActive)
12824
+ return a.isActive ? -1 : 1;
12825
+ const r = TABLE_HEALTH_RANK[classifyHealth(a, now)] - TABLE_HEALTH_RANK[classifyHealth(b, now)];
12826
+ if (r !== 0)
12827
+ return r;
12828
+ return a.label.localeCompare(b.label);
12829
+ });
12830
+ if (ordered.length > 0) {
12831
+ lines.push("");
12832
+ lines.push("| State | Account | 5h | 5h resets | 7d | 7d resets |");
12833
+ lines.push("| --- | --- | --- | --- | --- | --- |");
12834
+ for (const s of ordered) {
12835
+ const emoji = HEALTH_EMOJI[classifyHealth(s, now)];
12836
+ const label = displayLabel(s.label, opts);
12837
+ const accountCell = `\`${codeSpanSafe(s.isActive ? `${label} (active)` : label)}\``;
12838
+ const { five, seven } = pctCells(s, now);
12839
+ const fiveReset = windowResetCell(s, now, tz, "5h");
12840
+ const sevenReset = windowResetCell(s, now, tz, "7d");
12841
+ lines.push(`| ${emoji} | ${tableCell(accountCell)} | ${five} | ${tableCell(fiveReset)} | ${seven} | ${tableCell(sevenReset)} |`);
12842
+ }
12843
+ }
12844
+ lines.push("");
12845
+ lines.push(`_${recommendation(snapshots, now, opts.demo ?? false)}_`);
12846
+ if (opts.staleCachedAtMs != null) {
12847
+ lines.push(`_\u26a0 cached ${formatAgeStamp(opts.staleCachedAtMs, now)}_`);
12848
+ } else if (opts.liveProbedAtMs != null) {
12849
+ lines.push(`_Live \u00b7 refreshed ${formatAgeStamp(opts.liveProbedAtMs, now)}_`);
12850
+ } else if (opts.probeFailed) {
12851
+ lines.push("_\u26a0 probe failed \u2014 no live data_");
12852
+ } else {
12853
+ lines.push("_Live_");
12854
+ }
12855
+ return lines.join(`
12856
+ `);
12857
+ }
12858
+ function recommendation(snapshots, now = new Date, demo = false) {
12859
+ const active = snapshots.find((s) => s.isActive);
12860
+ if (!active)
12861
+ return "No active account set.";
12862
+ const activeHealth = classifyHealth(active, now);
12863
+ const others = snapshots.filter((s) => !s.isActive);
12864
+ const healthyAlt = others.find((s) => classifyHealth(s, now) === "healthy");
12865
+ const lbl = (s) => demo ? maskEmail(s.label) : s.label;
12866
+ const activeLabel = lbl(active);
12867
+ if (activeHealth === "healthy") {
12868
+ return `Recommendation: stay on ${activeLabel}.`;
12869
+ }
12870
+ if (activeHealth === "throttling") {
12871
+ if (healthyAlt) {
12872
+ return `Recommendation: active ${activeLabel} is throttling. Switch to ${lbl(healthyAlt)} for headroom.`;
12873
+ }
12874
+ return `Recommendation: active ${activeLabel} is throttling; no healthy alternative \u2014 wait for refill.`;
12875
+ }
12876
+ if (activeHealth === "blocked") {
12877
+ if (healthyAlt) {
12878
+ return `Recommendation: active ${activeLabel} is BLOCKED \u2014 switch to ${lbl(healthyAlt)} now.`;
12879
+ }
12880
+ return summarizeNoHealthyAlt(snapshots, now, demo);
12881
+ }
12882
+ return `Active ${activeLabel}: quota probe failed; broker last_seen unknown.`;
12883
+ }
12884
+ function summarizeNoHealthyAlt(snapshots, now, demo = false) {
12885
+ const mask = (label) => demo ? maskEmail(label) : label;
12886
+ let throttlingLabel = null;
12887
+ let allTrulyBlocked = true;
12888
+ for (const s of snapshots) {
12889
+ const h = classifyHealth(s, now);
12890
+ if (h === "throttling") {
12891
+ if (!throttlingLabel)
12892
+ throttlingLabel = s.label;
12893
+ allTrulyBlocked = false;
12894
+ } else if (h === "healthy" || h === "unknown") {
12895
+ allTrulyBlocked = false;
12896
+ } else if (h === "blocked" && blockedReason(s, now) === "quota-exhausted") {
12897
+ if (s.quota) {
12898
+ const win = bindingWindow(s.quota);
12899
+ const at = win === "5h" ? s.quota.fiveHourResetAt : s.quota.sevenDayResetAt;
12900
+ if (at && at.getTime() > now.getTime())
12901
+ allTrulyBlocked = false;
12902
+ }
12903
+ }
12904
+ }
12905
+ const earliestRecovery = pickEarliestRecovery(snapshots, now);
12906
+ if (throttlingLabel) {
12907
+ const eta = earliestRecovery ? ` Soonest full refill: ${mask(earliestRecovery.label)} in ${formatRelative(earliestRecovery.at, now)}.` : "";
12908
+ return `No fully-healthy account; ${mask(throttlingLabel)} is throttling but still usable.${eta}`;
12909
+ }
12910
+ if (!allTrulyBlocked) {
12911
+ if (earliestRecovery) {
12912
+ return `All accounts at capacity; soonest refill: ${mask(earliestRecovery.label)} in ${formatRelative(earliestRecovery.at, now)}.`;
12913
+ }
12914
+ return `All accounts at capacity \u2014 waiting on a window refill.`;
12915
+ }
12916
+ if (earliestRecovery) {
12917
+ return `All accounts blocked. Earliest recovery: ${mask(earliestRecovery.label)} in ${formatRelative(earliestRecovery.at, now)}.`;
12918
+ }
12919
+ return `All accounts blocked. Run /auth add to attach another subscription.`;
12920
+ }
12921
+ function pickEarliestRecovery(snapshots, now) {
12922
+ let best = null;
12923
+ for (const s of snapshots) {
12924
+ if (!s.quota)
12925
+ continue;
12926
+ if (isProbeThin(s.quota))
12927
+ continue;
12928
+ const win = bindingWindow(s.quota);
12929
+ const at = win === "5h" ? s.quota.fiveHourResetAt : s.quota.sevenDayResetAt;
12930
+ if (!at || at.getTime() <= now.getTime())
12931
+ continue;
12932
+ if (!best || at.getTime() < best.at.getTime()) {
12933
+ best = { label: s.label, at };
12934
+ }
12935
+ }
12936
+ return best;
12937
+ }
12938
+ function renderFallbackAnnouncement(input) {
12939
+ const now = input.now ?? new Date;
12940
+ const tz = input.tz ?? "UTC";
12941
+ const lines = [];
12942
+ const limitWord = input.oldQuota ? limitWordFor(input.oldQuota) : "quota";
12943
+ const headerLimit = limitWord === "quota" ? "quota cap" : `${limitWord} limit`;
12944
+ if (!input.newLabel) {
12945
+ lines.push(`\uD83D\uDD34 **All accounts blocked \u00b7 ${headerLimit} on ${escapeMarkdown(input.oldLabel)}**`);
12946
+ lines.push("");
12947
+ lines.push(`Triggered by: agent **${escapeMarkdown(input.triggerAgent)}**`);
12948
+ const fleet = input.fleetSnapshots ?? [];
12949
+ if (fleet.length > 0) {
12950
+ lines.push("");
12951
+ const rowOpts = { now, tz };
12952
+ const healthOrder = ["blocked", "throttling", "healthy", "unknown"];
12953
+ const rank = (s) => healthOrder.indexOf(classifyHealth(s, now));
12954
+ const ordered = [...fleet].sort((a, b) => rank(a) - rank(b) || Number(b.isActive) - Number(a.isActive));
12955
+ for (const snap of ordered) {
12956
+ for (const ln of renderAccountRow(snap, rowOpts))
12957
+ lines.push(ln);
12958
+ }
12959
+ const earliest = pickEarliestRecovery(fleet, now);
12960
+ if (earliest) {
12961
+ lines.push("");
12962
+ lines.push(`Earliest recovery: \`${codeSpanSafe(earliest.label)}\` ` + `${formatAbsolute(earliest.at, tz)} (in ${formatRelative(earliest.at, now)})`);
12963
+ }
12964
+ } else if (input.oldQuota) {
12965
+ const recovery = recoveryAtFor(input.oldQuota);
12966
+ if (recovery) {
12967
+ lines.push(`${escapeMarkdown(input.oldLabel)} recovers ${formatAbsolute(recovery, tz)} ` + `(in ${formatRelative(recovery, now)})`);
12968
+ }
12969
+ }
12970
+ lines.push("");
12971
+ lines.push(`Run \`/auth add <label>\` to attach another subscription, ` + `or \`/auth refresh\` to re-probe.`);
12972
+ return lines.join(`
12973
+ `);
12974
+ }
12975
+ lines.push(`\u2713 **Switched fleet \u00b7 ${headerLimit} on ${escapeMarkdown(input.oldLabel)}**`);
12976
+ lines.push("");
12977
+ lines.push(`\`${codeSpanSafe(input.oldLabel)}\` \u2192 \`${codeSpanSafe(input.newLabel)}\``);
12978
+ lines.push(`Triggered by: agent **${escapeMarkdown(input.triggerAgent)}**`);
12979
+ lines.push("");
12980
+ if (input.oldQuota) {
12981
+ const recovery = recoveryAtFor(input.oldQuota);
12982
+ if (recovery) {
12983
+ lines.push(`\`${codeSpanSafe(input.oldLabel)}\` recovers ` + `${formatAbsolute(recovery, tz)} (in ${formatRelative(recovery, now)})`);
12984
+ }
12985
+ }
12986
+ if (input.newQuota) {
12987
+ const fiveStr = fmtPct(input.newQuota.fiveHourUtilizationPct);
12988
+ const sevenStr = fmtPct(input.newQuota.sevenDayUtilizationPct);
12989
+ const hasHeadroom = input.newQuota.fiveHourUtilizationPct < THROTTLING_THRESHOLD_PCT && input.newQuota.sevenDayUtilizationPct < THROTTLING_THRESHOLD_PCT;
12990
+ const headroomStr = hasHeadroom ? "_(plenty of headroom)_" : "_(near limit \u2014 watch this)_";
12991
+ lines.push(`\`${codeSpanSafe(input.newLabel)}\` now: ${fiveStr} of 5h \u00b7 ${sevenStr} of 7d ${headroomStr}`);
12992
+ } else {
12993
+ lines.push(`_(quota probe for new account is pending \u2014 will reflect on next /auth)_`);
12994
+ }
12995
+ return lines.join(`
12996
+ `);
12997
+ }
12998
+ function limitWordFor(q) {
12999
+ if (q.representativeClaim === "seven_day" && q.sevenDayUtilizationPct >= 99)
13000
+ return "7-day";
13001
+ if (q.representativeClaim === "five_hour" && q.fiveHourUtilizationPct >= 99)
13002
+ return "5-hour";
13003
+ if (q.sevenDayUtilizationPct >= 99)
13004
+ return "7-day";
13005
+ if (q.fiveHourUtilizationPct >= 99)
13006
+ return "5-hour";
13007
+ return q.sevenDayUtilizationPct >= q.fiveHourUtilizationPct ? "7-day" : "5-hour";
13008
+ }
13009
+ function recoveryAtFor(q) {
13010
+ const word = limitWordFor(q);
13011
+ if (word === "7-day")
13012
+ return q.sevenDayResetAt;
13013
+ if (word === "5-hour")
13014
+ return q.fiveHourResetAt;
13015
+ if (!q.fiveHourResetAt)
13016
+ return q.sevenDayResetAt;
13017
+ if (!q.sevenDayResetAt)
13018
+ return q.fiveHourResetAt;
13019
+ return q.fiveHourResetAt.getTime() < q.sevenDayResetAt.getTime() ? q.fiveHourResetAt : q.sevenDayResetAt;
13020
+ }
13021
+ function buildSnapshotKeyboard(snapshots, opts = {}) {
13022
+ const max = opts.maxSwitchButtons ?? 3;
13023
+ const now = opts.now ?? new Date;
13024
+ const rows = [];
13025
+ const switchTargets = snapshots.filter((s) => !s.isActive).sort((a, b) => switchPriority(a, now) - switchPriority(b, now)).filter((s) => classifyHealth(s, now) !== "blocked" && classifyHealth(s, now) !== "unknown").slice(0, max);
13026
+ for (const t of switchTargets) {
13027
+ rows.push([
13028
+ {
13029
+ text: `Switch fleet \u2192 ${opts.demo ? maskEmail(t.label) : t.label}`,
13030
+ callbackData: `auth:use:${t.label}`
13031
+ }
13032
+ ]);
13033
+ }
13034
+ rows.push([
13035
+ { text: "\u21bb Refresh", callbackData: opts.demo ? "auth:refresh:demo" : "auth:refresh" },
13036
+ { text: "/usage", insertText: "/usage" },
13037
+ { text: "+ Add", insertText: "/auth add " }
13038
+ ]);
13039
+ return rows;
13040
+ }
13041
+ function switchPriority(s, now = new Date) {
13042
+ const h = classifyHealth(s, now);
13043
+ if (h === "healthy")
13044
+ return 0;
13045
+ if (h === "throttling")
13046
+ return 1;
13047
+ if (h === "unknown")
13048
+ return 2;
13049
+ return 3;
13050
+ }
13051
+ function buildSnapshotsFromState(state3, quotas) {
13052
+ const out = [];
13053
+ for (let i = 0;i < state3.accounts.length; i++) {
13054
+ const acc = state3.accounts[i];
13055
+ const q = quotas[i];
13056
+ out.push({
13057
+ label: acc.label,
13058
+ isActive: acc.label === state3.active,
13059
+ quota: q && q.ok ? q.data : null,
13060
+ quotaError: q && !q.ok ? q.reason : undefined,
13061
+ expiresAtMs: acc.expiresAt
13062
+ });
13063
+ }
13064
+ return out;
13065
+ }
13066
+ function reviveLastQuota(snap) {
13067
+ if (!snap)
13068
+ return null;
13069
+ return {
13070
+ fiveHourUtilizationPct: snap.fiveHourUtilizationPct,
13071
+ sevenDayUtilizationPct: snap.sevenDayUtilizationPct,
13072
+ fiveHourResetAt: snap.fiveHourResetAt ? new Date(snap.fiveHourResetAt) : null,
13073
+ sevenDayResetAt: snap.sevenDayResetAt ? new Date(snap.sevenDayResetAt) : null,
13074
+ representativeClaim: snap.representativeClaim,
13075
+ overageStatus: snap.overageStatus,
13076
+ overageDisabledReason: snap.overageDisabledReason,
13077
+ fiveHourUtilPresent: snap.fiveHourUtilPresent,
13078
+ sevenDayUtilPresent: snap.sevenDayUtilPresent
13079
+ };
13080
+ }
13081
+ var THROTTLING_THRESHOLD_PCT = 80, OVERAGE_EXHAUSTED_REASONS, HEALTH_EMOJI, TABLE_HEALTH_RANK;
13082
+ var init_auth_snapshot_format = __esm(() => {
13083
+ init_demo_mask();
13084
+ init_card_format();
13085
+ OVERAGE_EXHAUSTED_REASONS = new Set(["out_of_credits"]);
13086
+ HEALTH_EMOJI = {
13087
+ healthy: "\uD83D\uDFE2",
13088
+ throttling: "\uD83D\uDFE1",
13089
+ blocked: "\uD83D\uDD34",
13090
+ unknown: "\u26aa"
13091
+ };
13092
+ TABLE_HEALTH_RANK = {
13093
+ blocked: 0,
13094
+ throttling: 1,
13095
+ unknown: 2,
13096
+ healthy: 3
13097
+ };
13098
+ });
13099
+
12661
13100
  // ../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/helpers/util.js
12662
13101
  var util, objectUtil, ZodParsedType, getParsedType = (data) => {
12663
13102
  const t = typeof data;
@@ -24950,7 +25389,8 @@ var init_schema = __esm(() => {
24950
25389
  UserSchema = exports_external.object({
24951
25390
  name: exports_external.string().optional().describe("Display name for the user."),
24952
25391
  telegram_ids: exports_external.array(exports_external.string()).min(1).describe("Telegram username(s) and/or numeric user id(s) identifying this user " + "(a leading @ is optional). Matched against the message sender for " + "per-speaker memory routing."),
24953
- profile_bank: exports_external.string().describe("Hindsight bank holding this user's memory profile (author via " + "`switchroom memory profile add <bank> ...`).")
25392
+ profile_bank: exports_external.string().describe("Hindsight bank holding this user's memory profile (author via " + "`switchroom memory profile add <bank> ...`)."),
25393
+ person_id: exports_external.string().optional().describe("Free-text display name projected into the inbound `<channel>` " + 'tag\'s `user` attribute (e.g. "Lisa") so an agent can greet by ' + "name instead of only seeing the raw Telegram id/username. NOT a " + "stable identity system \u2014 just a label. Resolution is boot-time-only " + "(no hot-reload; a config change needs an agent restart) and " + "chat-scoped (a resolved name is only shown in a chat/group the " + "person is actually a member of, per that chat's access.json " + "membership \u2014 never broadcast into every chat the agent operates " + "in). Keep this broadly safe to show, the same discipline as " + "picking a `profile_bank` name: there is no automated enforcement " + "that a `person_id` stays safe if a group's membership changes " + "later \u2014 see docs/configuration.md.")
24954
25394
  });
24955
25395
  SwitchroomConfigSchema = exports_external.object({
24956
25396
  switchroom: exports_external.object({
@@ -36687,7 +37127,7 @@ var init_boot_card = __esm(() => {
36687
37127
  var exports_auth_snapshot_format = {};
36688
37128
  __export(exports_auth_snapshot_format, {
36689
37129
  zipProbeResults: () => zipProbeResults,
36690
- reviveLastQuota: () => reviveLastQuota,
37130
+ reviveLastQuota: () => reviveLastQuota2,
36691
37131
  renderFallbackAnnouncement: () => renderFallbackAnnouncement2,
36692
37132
  renderAuthSnapshotFormat2: () => renderAuthSnapshotFormat22,
36693
37133
  recommendation: () => recommendation2,
@@ -36891,6 +37331,8 @@ function renderAuthSnapshotFormat22(snapshots, opts = {}) {
36891
37331
  lines.push(`_\u26a0 cached ${formatAgeStamp2(opts.staleCachedAtMs, now)}_`);
36892
37332
  } else if (opts.liveProbedAtMs != null) {
36893
37333
  lines.push(`_Live \u00b7 refreshed ${formatAgeStamp2(opts.liveProbedAtMs, now)}_`);
37334
+ } else if (opts.probeFailed) {
37335
+ lines.push("_\u26a0 probe failed \u2014 no live data_");
36894
37336
  } else {
36895
37337
  lines.push("_Live_");
36896
37338
  }
@@ -37118,7 +37560,7 @@ function buildSnapshotsFromState2(state4, quotas) {
37118
37560
  }
37119
37561
  return out;
37120
37562
  }
37121
- function reviveLastQuota(snap) {
37563
+ function reviveLastQuota2(snap) {
37122
37564
  if (!snap)
37123
37565
  return null;
37124
37566
  return {
@@ -37139,7 +37581,7 @@ function buildSnapshotsFromCachedState(state4) {
37139
37581
  return {
37140
37582
  label: acc.label,
37141
37583
  isActive: acc.label === state4.active,
37142
- quota: reviveLastQuota(lq),
37584
+ quota: reviveLastQuota2(lq),
37143
37585
  quotaError: lq ? undefined : "no cached quota (no probe since broker start)",
37144
37586
  expiresAtMs: acc.expiresAt,
37145
37587
  capturedAtMs: lq?.capturedAt
@@ -37147,7 +37589,7 @@ function buildSnapshotsFromCachedState(state4) {
37147
37589
  });
37148
37590
  }
37149
37591
  var THROTTLING_THRESHOLD_PCT2 = 80, OVERAGE_EXHAUSTED_REASONS2, HEALTH_EMOJI2, TABLE_HEALTH_RANK2;
37150
- var init_auth_snapshot_format = __esm(() => {
37592
+ var init_auth_snapshot_format2 = __esm(() => {
37151
37593
  init_demo_mask();
37152
37594
  init_card_format();
37153
37595
  OVERAGE_EXHAUSTED_REASONS2 = new Set(["out_of_credits"]);
@@ -37799,6 +38241,148 @@ var init_approvals_commands = __esm(() => {
37799
38241
  init_client3();
37800
38242
  });
37801
38243
 
38244
+ // quota-bar-format.ts
38245
+ var exports_quota_bar_format = {};
38246
+ __export(exports_quota_bar_format, {
38247
+ renderUsageCard: () => renderUsageCard,
38248
+ renderQuotaBarBlockFromListState: () => renderQuotaBarBlockFromListState,
38249
+ renderQuotaBarBlock: () => renderQuotaBarBlock,
38250
+ renderQuotaBarAccount: () => renderQuotaBarAccount,
38251
+ pickDot: () => pickDot,
38252
+ formatWindowRow: () => formatWindowRow,
38253
+ formatTimeLeft: () => formatTimeLeft,
38254
+ elapsedFraction: () => elapsedFraction,
38255
+ buildBar: () => buildBar,
38256
+ accountStatus: () => accountStatus
38257
+ });
38258
+ function pickDot(pct) {
38259
+ const clamped = Math.max(0, Math.min(100, pct));
38260
+ if (clamped >= 90)
38261
+ return "\uD83D\uDD34";
38262
+ if (clamped >= 50)
38263
+ return "\uD83D\uDFE1";
38264
+ return "\uD83D\uDFE2";
38265
+ }
38266
+ function formatTimeLeft(target, now = new Date) {
38267
+ if (!target)
38268
+ return "resets now";
38269
+ const deltaMs = target.getTime() - now.getTime();
38270
+ if (!Number.isFinite(deltaMs) || deltaMs <= 0)
38271
+ return "resets now";
38272
+ const totalMin = Math.round(deltaMs / 60000);
38273
+ if (totalMin < 60)
38274
+ return `${totalMin}m left`;
38275
+ const totalHours = Math.floor(totalMin / 60);
38276
+ const m = totalMin % 60;
38277
+ if (totalHours < 24)
38278
+ return `${totalHours}h${m}m left`;
38279
+ const d = Math.floor(totalHours / 24);
38280
+ const h = totalHours % 24;
38281
+ return h > 0 ? `${d}d${h}h left` : `${d}d left`;
38282
+ }
38283
+ function elapsedFraction(target, windowDurationMs, now = new Date) {
38284
+ if (!target)
38285
+ return 0;
38286
+ const timeLeftMs = target.getTime() - now.getTime();
38287
+ if (!Number.isFinite(timeLeftMs) || timeLeftMs <= 0)
38288
+ return 0;
38289
+ return Math.max(0, Math.min(1, 1 - timeLeftMs / windowDurationMs));
38290
+ }
38291
+ function buildBar(pct, elapsedFrac) {
38292
+ const clampedPct = Math.max(0, Math.min(100, pct));
38293
+ const fillCount = Math.round(clampedPct / 100 * BAR_WIDTH);
38294
+ const cells = new Array(BAR_WIDTH).fill("\u2591");
38295
+ for (let i = 0;i < fillCount; i++)
38296
+ cells[i] = "\u2588";
38297
+ const tickIndex = Math.max(0, Math.min(BAR_WIDTH - 1, Math.round(elapsedFrac * (BAR_WIDTH - 1))));
38298
+ cells[tickIndex] = "\u2503";
38299
+ return cells.join("");
38300
+ }
38301
+ function accountStatus(isActive, exhausted) {
38302
+ if (isActive)
38303
+ return "active";
38304
+ if (exhausted)
38305
+ return "exhausted";
38306
+ return "idle";
38307
+ }
38308
+ function formatWindowRow(window2, pct, resetAt, now = new Date) {
38309
+ const windowMs = window2 === "5h" ? FIVE_HOUR_MS : SEVEN_DAY_MS;
38310
+ const dot = pickDot(pct);
38311
+ const bar = buildBar(pct, elapsedFraction(resetAt, windowMs, now));
38312
+ const pctStr = `${Math.round(Math.max(0, Math.min(100, pct)))}%`;
38313
+ const timeLeft = formatTimeLeft(resetAt, now);
38314
+ return `- ${dot} ${window2} \`[${bar}] ${pctStr} / ${timeLeft}\``;
38315
+ }
38316
+ function renderQuotaBarAccount(label, isActive, exhausted, quota, now = new Date, demo = false) {
38317
+ const status = accountStatus(isActive, exhausted);
38318
+ const displayLabel3 = demo ? maskEmail(label) : label;
38319
+ const lines = [`- **${escapeMarkdown(displayLabel3)}** (${status})`];
38320
+ if (!quota || isProbeThin(quota)) {
38321
+ const reason = !quota ? "no data \u2014 probe failed" : "no data \u2014 thin probe";
38322
+ lines.push(`- \u26a0\ufe0f 5h \`${reason}\``);
38323
+ lines.push(`- \u26a0\ufe0f 7d \`${reason}\``);
38324
+ return lines;
38325
+ }
38326
+ const norm = refillNormalizedUtils(quota, now);
38327
+ lines.push(formatWindowRow("5h", norm.fiveHourUtilizationPct, quota.fiveHourResetAt, now));
38328
+ lines.push(formatWindowRow("7d", norm.sevenDayUtilizationPct, quota.sevenDayResetAt, now));
38329
+ return lines;
38330
+ }
38331
+ function formatAgeStamp3(atMs, now = new Date) {
38332
+ const ageSec = Math.max(0, Math.round((now.getTime() - atMs) / 1000));
38333
+ return ageSec < 60 ? `${ageSec}s ago` : `${Math.round(ageSec / 60)}m ago`;
38334
+ }
38335
+ function renderQuotaBarBlock(snapshots, exhaustedByLabel, opts = {}) {
38336
+ const now = opts.now ?? new Date;
38337
+ const demo = opts.demo ?? false;
38338
+ const lines = [];
38339
+ for (const snap of snapshots) {
38340
+ const exhausted = exhaustedByLabel.get(snap.label) ?? false;
38341
+ lines.push(...renderQuotaBarAccount(snap.label, snap.isActive, exhausted, snap.quota, now, demo));
38342
+ }
38343
+ return lines.join(`
38344
+ `);
38345
+ }
38346
+ function renderQuotaBarBlockFromListState(state4, opts = {}) {
38347
+ const now = opts.now ?? new Date;
38348
+ const exhaustedByLabel = new Map(state4.accounts.map((a) => [a.label, a.exhausted]));
38349
+ const snapshots = state4.accounts.map((acc) => ({
38350
+ label: acc.label,
38351
+ isActive: acc.label === state4.active,
38352
+ quota: reviveLastQuota(acc.last_quota ?? null),
38353
+ quotaError: acc.last_quota ? undefined : "no cached quota (no probe since broker start)",
38354
+ expiresAtMs: acc.expiresAt,
38355
+ capturedAtMs: acc.last_quota?.capturedAt
38356
+ }));
38357
+ return renderQuotaBarBlock(snapshots, exhaustedByLabel, { now });
38358
+ }
38359
+ function renderUsageCard(snapshots, exhaustedByLabel, opts = {}) {
38360
+ const now = opts.now ?? new Date;
38361
+ const demo = opts.demo ?? false;
38362
+ const bar = renderQuotaBarBlock(snapshots, exhaustedByLabel, { now, demo });
38363
+ const lines = [bar];
38364
+ lines.push(`_${recommendation(snapshots, now, demo)}_`);
38365
+ if (opts.staleCachedAtMs != null) {
38366
+ lines.push(`_\u26a0 cached ${formatAgeStamp3(opts.staleCachedAtMs, now)}_`);
38367
+ } else if (opts.liveProbedAtMs != null) {
38368
+ lines.push(`_Live \u00b7 refreshed ${formatAgeStamp3(opts.liveProbedAtMs, now)}_`);
38369
+ } else if (opts.probeFailed) {
38370
+ lines.push("_\u26a0 probe failed \u2014 no live data_");
38371
+ } else {
38372
+ lines.push("_Live_");
38373
+ }
38374
+ return lines.join(`
38375
+ `);
38376
+ }
38377
+ var FIVE_HOUR_MS, SEVEN_DAY_MS, BAR_WIDTH = 10;
38378
+ var init_quota_bar_format = __esm(() => {
38379
+ init_auth_snapshot_format();
38380
+ init_card_format();
38381
+ init_demo_mask();
38382
+ FIVE_HOUR_MS = 5 * 60 * 60 * 1000;
38383
+ SEVEN_DAY_MS = 7 * 24 * 60 * 60 * 1000;
38384
+ });
38385
+
37802
38386
  // gateway/approval-callback.ts
37803
38387
  var exports_approval_callback = {};
37804
38388
  __export(exports_approval_callback, {
@@ -40681,6 +41265,19 @@ function extractRetryAfterSecs(err) {
40681
41265
  return ra;
40682
41266
  return null;
40683
41267
  }
41268
+ function classifyEditError(err) {
41269
+ const retryAfter = extractRetryAfterSecs(err);
41270
+ if (retryAfter != null)
41271
+ return "rate_limited";
41272
+ const desc = err instanceof Error ? err.message : err != null && typeof err === "object" && ("description" in err) ? String(err.description) : String(err);
41273
+ const low = desc.toLowerCase();
41274
+ if (low.includes("not modified"))
41275
+ return "not_modified";
41276
+ if (low.includes("not found") || low.includes("can't be edited") || low.includes("cannot be edited") || low.includes("not enough rights")) {
41277
+ return "gone";
41278
+ }
41279
+ return "transient";
41280
+ }
40684
41281
  function createWorkerActivityFeed(opts) {
40685
41282
  const log = opts.log ?? (() => {});
40686
41283
  const nowFn = opts.now ?? Date.now;
@@ -40694,6 +41291,18 @@ function createWorkerActivityFeed(opts) {
40694
41291
  });
40695
41292
  const clearIntervalFn = opts.clearInterval ?? ((handle) => clearInterval(handle));
40696
41293
  const handles = new Map;
41294
+ const finalized = new Set;
41295
+ const FINALIZED_CAP = 256;
41296
+ function markFinalized(agentId) {
41297
+ if (finalized.has(agentId))
41298
+ return;
41299
+ finalized.add(agentId);
41300
+ if (finalized.size > FINALIZED_CAP) {
41301
+ const oldest = finalized.values().next().value;
41302
+ if (oldest != null)
41303
+ finalized.delete(oldest);
41304
+ }
41305
+ }
40697
41306
  let heartbeatTimer = null;
40698
41307
  function sendOptsFor(h) {
40699
41308
  return {
@@ -40755,29 +41364,65 @@ function createWorkerActivityFeed(opts) {
40755
41364
  h.lastEditAt = nowFn();
40756
41365
  log(`worker-feed: edit agent=${h.agentId} chat=${h.chatId} ` + `thread=${h.threadId ?? "-"} msgId=${h.messageId} bytes=${body.length}`);
40757
41366
  } catch (err) {
40758
- noteRateLimited(h, err, "edit");
40759
- log(`worker-feed: edit failed, will re-post: ${err.message}`);
40760
- h.messageId = null;
40761
- h.lastBody = null;
41367
+ const outcome = classifyEditError(err);
41368
+ if (outcome === "rate_limited") {
41369
+ noteRateLimited(h, err, "edit");
41370
+ return;
41371
+ }
41372
+ if (outcome === "not_modified") {
41373
+ h.lastBody = body;
41374
+ h.lastEditAt = nowFn();
41375
+ return;
41376
+ }
41377
+ if (outcome === "gone") {
41378
+ h.messageId = null;
41379
+ h.lastBody = null;
41380
+ return;
41381
+ }
41382
+ log(`worker-feed: edit transient error agent=${h.agentId}: ${err.message}`);
40762
41383
  }
40763
41384
  }
40764
41385
  async function doFinish(h, view) {
40765
- if (h.messageId == null)
41386
+ h.finished = true;
41387
+ markFinalized(h.agentId);
41388
+ if (h.messageId == null) {
41389
+ h.pendingFinish = null;
40766
41390
  return;
41391
+ }
40767
41392
  if (nowFn() < h.cooldownUntil) {
41393
+ h.pendingFinish = view;
40768
41394
  return;
40769
41395
  }
40770
41396
  const body = renderWorkerActivity({ ...view, narrativeLines: h.narrative });
40771
- if (body === h.lastBody)
41397
+ if (body === h.lastBody) {
41398
+ h.pendingFinish = null;
40772
41399
  return;
41400
+ }
40773
41401
  try {
40774
41402
  await opts.bot.editMessageText(h.chatId, h.messageId, body, sendOptsFor(h));
40775
41403
  h.lastBody = body;
40776
41404
  h.lastEditAt = nowFn();
41405
+ h.pendingFinish = null;
40777
41406
  log(`worker-feed: finish agent=${h.agentId} chat=${h.chatId} ` + `thread=${h.threadId ?? "-"} msgId=${h.messageId} state=${view.state} bytes=${body.length}`);
40778
41407
  } catch (err) {
40779
- noteRateLimited(h, err, "finish");
40780
- log(`worker-feed: finish edit failed: ${err.message}`);
41408
+ const outcome = classifyEditError(err);
41409
+ if (outcome === "rate_limited") {
41410
+ noteRateLimited(h, err, "finish");
41411
+ h.pendingFinish = view;
41412
+ return;
41413
+ }
41414
+ if (outcome === "not_modified") {
41415
+ h.lastBody = body;
41416
+ h.lastEditAt = nowFn();
41417
+ h.pendingFinish = null;
41418
+ return;
41419
+ }
41420
+ if (outcome === "gone") {
41421
+ h.pendingFinish = null;
41422
+ return;
41423
+ }
41424
+ h.pendingFinish = view;
41425
+ log(`worker-feed: finish transient error agent=${h.agentId}: ${err.message}`);
40781
41426
  }
40782
41427
  }
40783
41428
  function heartbeatTick() {
@@ -40785,6 +41430,17 @@ function createWorkerActivityFeed(opts) {
40785
41430
  for (const h of handles.values()) {
40786
41431
  if (!handles.has(h.agentId))
40787
41432
  continue;
41433
+ if (h.pendingFinish != null && now >= h.cooldownUntil) {
41434
+ const view2 = h.pendingFinish;
41435
+ h.chain = h.chain.then(() => doFinish(h, view2)).catch((err) => {
41436
+ log(`worker-feed: heartbeat finalize re-drive error ${h.agentId}: ${err.message}`);
41437
+ }).finally(() => {
41438
+ if (handles.get(h.agentId)?.pendingFinish == null) {
41439
+ handles.delete(h.agentId);
41440
+ }
41441
+ });
41442
+ continue;
41443
+ }
40788
41444
  if (h.lastView == null)
40789
41445
  continue;
40790
41446
  if (h.lastView.state !== "running")
@@ -40828,7 +41484,12 @@ function createWorkerActivityFeed(opts) {
40828
41484
  update(agentId, chatId, view, threadId) {
40829
41485
  if (chatId.length === 0)
40830
41486
  return Promise.resolve();
40831
- let h = handles.get(agentId);
41487
+ if (finalized.has(agentId))
41488
+ return Promise.resolve();
41489
+ const existing = handles.get(agentId);
41490
+ if (existing?.finished === true)
41491
+ return Promise.resolve();
41492
+ let h = existing;
40832
41493
  if (h == null) {
40833
41494
  h = {
40834
41495
  agentId,
@@ -40842,7 +41503,9 @@ function createWorkerActivityFeed(opts) {
40842
41503
  chain: Promise.resolve(),
40843
41504
  lastView: null,
40844
41505
  dispatchAtMs: null,
40845
- stepStartedAtMs: null
41506
+ stepStartedAtMs: null,
41507
+ finished: false,
41508
+ pendingFinish: null
40846
41509
  };
40847
41510
  handles.set(agentId, h);
40848
41511
  }
@@ -40859,11 +41522,14 @@ function createWorkerActivityFeed(opts) {
40859
41522
  h.chain = h.chain.then(() => doFinish(h, view)).catch((err) => {
40860
41523
  log(`worker-feed: finish chain error ${agentId}: ${err.message}`);
40861
41524
  }).finally(() => {
40862
- handles.delete(agentId);
41525
+ if (handles.get(agentId)?.pendingFinish == null) {
41526
+ handles.delete(agentId);
41527
+ }
40863
41528
  });
40864
41529
  return h.chain;
40865
41530
  },
40866
41531
  drop(agentId) {
41532
+ markFinalized(agentId);
40867
41533
  handles.delete(agentId);
40868
41534
  },
40869
41535
  heartbeatTick,
@@ -57237,8 +57903,20 @@ _still working (${minutes}m) \u00b7 message me anytime, I'll keep you posted_`;
57237
57903
  elapsedMs: elapsed
57238
57904
  });
57239
57905
  }).catch((err) => {
57240
- process.stderr.write(`pending-work-progress: edit failed key=${key} ` + `msg=${editCtx.messageId}: ${err.message}
57241
- `);
57906
+ const desc = err instanceof Error ? err.message : err != null && typeof err === "object" && ("description" in err) ? String(err.description) : String(err);
57907
+ const low = desc.toLowerCase();
57908
+ if (low.includes("not modified")) {
57909
+ activeDeps2.emitMetric?.({
57910
+ kind: "pending_progress_edited",
57911
+ chatKey: key,
57912
+ elapsedMs: elapsed
57913
+ });
57914
+ return;
57915
+ }
57916
+ if (low.includes("not found") || low.includes("can't be edited") || low.includes("cannot be edited") || low.includes("not enough rights")) {
57917
+ clearPending(key, "stale_turn");
57918
+ return;
57919
+ }
57242
57920
  });
57243
57921
  }
57244
57922
  }
@@ -57790,427 +58468,121 @@ function writeQuarantineMarker(telegramStateDir, reason, detail, nowFn = Date.no
57790
58468
  writeFileSync8(join11(telegramStateDir, QUARANTINE_FILENAME), JSON.stringify(marker) + `
57791
58469
  `, "utf-8");
57792
58470
  }
57793
- // auth-snapshot-format.ts
57794
- init_demo_mask();
57795
- init_card_format();
57796
- var THROTTLING_THRESHOLD_PCT = 80;
57797
- var OVERAGE_EXHAUSTED_REASONS = new Set(["out_of_credits"]);
57798
- function classifyHealth(snap, now = new Date) {
57799
- if (!snap.quota)
57800
- return "unknown";
57801
- const q = snap.quota;
57802
- if (isProbeThin(q))
57803
- return "unknown";
57804
- const norm = refillNormalizedUtils(q, now);
57805
- const max = Math.max(norm.fiveHourUtilizationPct, norm.sevenDayUtilizationPct);
57806
- if (max >= 99.5)
57807
- return "blocked";
57808
- if (max >= THROTTLING_THRESHOLD_PCT)
57809
- return "throttling";
57810
- return "healthy";
57811
- }
57812
- function blockedReason(snap, now = new Date) {
57813
- if (classifyHealth(snap, now) !== "blocked")
57814
- return null;
57815
- return "quota-exhausted";
57816
- }
57817
- function bindingWindow(q) {
57818
- if (q.representativeClaim === "seven_day")
57819
- return "7d";
57820
- if (q.representativeClaim === "five_hour")
57821
- return "5h";
57822
- return q.sevenDayUtilizationPct >= q.fiveHourUtilizationPct ? "7d" : "5h";
57823
- }
57824
- function formatRelative(target, now = new Date) {
57825
- if (!target)
57826
- return "\u2014";
57827
- const deltaMs = target.getTime() - now.getTime();
57828
- if (deltaMs <= 0)
57829
- return "now";
57830
- const totalMin = Math.round(deltaMs / 60000);
57831
- if (totalMin < 60)
57832
- return `${totalMin}m`;
57833
- const h = Math.floor(totalMin / 60);
57834
- const m = totalMin % 60;
57835
- if (h < 24)
57836
- return m > 0 ? `${h}h ${m}m` : `${h}h`;
57837
- const d = Math.floor(h / 24);
57838
- const rh = h % 24;
57839
- return rh > 0 ? `${d}d ${rh}h` : `${d}d`;
57840
- }
57841
- function formatAbsolute(target, tz = "UTC") {
57842
- if (!target)
57843
- return "\u2014";
57844
- return target.toLocaleString("en-US", {
57845
- timeZone: tz,
57846
- weekday: "short",
57847
- hour: "numeric",
57848
- minute: "2-digit",
57849
- hour12: true
57850
- });
57851
- }
57852
- function formatStatusTime(target, now = new Date, tz = "UTC") {
57853
- if (!target)
57854
- return "\u2014";
57855
- const dayFmt = new Intl.DateTimeFormat("en-CA", {
57856
- timeZone: tz,
57857
- year: "numeric",
57858
- month: "2-digit",
57859
- day: "2-digit"
57860
- });
57861
- const timeFmt = new Intl.DateTimeFormat("en-US", {
57862
- timeZone: tz,
57863
- hour: "numeric",
57864
- minute: "2-digit",
57865
- hour12: true
57866
- });
57867
- const timeStr = timeFmt.format(target);
57868
- if (dayFmt.format(target) === dayFmt.format(now)) {
57869
- return timeStr;
57870
- }
57871
- const withinWeek = target.getTime() - now.getTime() < 7 * 24 * 60 * 60 * 1000;
57872
- const dateFmt = new Intl.DateTimeFormat("en-US", {
57873
- timeZone: tz,
57874
- weekday: "short",
57875
- ...withinWeek ? {} : { day: "numeric", month: "short" }
57876
- });
57877
- return `${dateFmt.format(target)} ${timeStr}`;
57878
- }
57879
- function fmtPct(pct) {
57880
- return `${Math.min(100, Math.round(pct))}%`;
57881
- }
57882
- function displayLabel(label, opts) {
57883
- return opts.demo ? maskEmail(label) : label;
57884
- }
57885
- var HEALTH_EMOJI = {
57886
- healthy: "\uD83D\uDFE2",
57887
- throttling: "\uD83D\uDFE1",
57888
- blocked: "\uD83D\uDD34",
57889
- unknown: "\u26aa"
57890
- };
57891
- function renderAccountRow(snap, opts) {
57892
- const now = opts.now ?? new Date;
57893
- const tz = opts.tz ?? "UTC";
57894
- const lines = [];
57895
- const marker = snap.isActive ? "\u25cf " : "";
57896
- const label = displayLabel(snap.label, opts);
57897
- if (!snap.quota) {
57898
- lines.push(`${marker}\`${codeSpanSafe(label)}\` _quota probe failed_`);
57899
- if (snap.quotaError) {
57900
- lines.push(` _${escapeMarkdown(snap.quotaError)}_`);
57901
- }
57902
- return lines;
57903
- }
57904
- const q = snap.quota;
57905
- if (isProbeThin(q)) {
57906
- lines.push(`${marker}\`${codeSpanSafe(label)}\` _quota unknown (thin probe)_`);
57907
- return lines;
57908
- }
57909
- const norm = refillNormalizedUtils(q, now);
57910
- const fiveStr = fmtPct(norm.fiveHourUtilizationPct);
57911
- const sevenStr = fmtPct(norm.sevenDayUtilizationPct);
57912
- lines.push(`${marker}\`${codeSpanSafe(label)}\` ${fiveStr} / ${sevenStr}`);
57913
- const health = classifyHealth(snap, now);
57914
- if (health === "blocked") {
57915
- const win = bindingWindow(q);
57916
- const reset2 = win === "5h" ? q.fiveHourResetAt : q.sevenDayResetAt;
57917
- const winLabel = win === "5h" ? "5-hour" : "7-day";
57918
- lines.push(reset2 ? ` _quota exhausted \u2014 back ${formatAbsolute(reset2, tz)} (\`in ${formatRelative(reset2, now)}\`, ${winLabel} cap)_` : ` _quota exhausted \u2014 ${winLabel} cap, reset time unknown_`);
57919
- return lines;
57920
- }
57921
- const fiveResetIn = q.fiveHourResetAt ? q.fiveHourResetAt.getTime() - now.getTime() : Infinity;
57922
- const sevenResetIn = q.sevenDayResetAt ? q.sevenDayResetAt.getTime() - now.getTime() : Infinity;
57923
- const fiveFirst = fiveResetIn <= sevenResetIn;
57924
- const fiveSeg = q.fiveHourResetAt ? `5h refills ${formatAbsolute(q.fiveHourResetAt, tz)} (\`in ${formatRelative(q.fiveHourResetAt, now)}\`)` : "5h refills \u2014";
57925
- const sevenSeg = q.sevenDayResetAt ? `7d resets ${formatAbsolute(q.sevenDayResetAt, tz)} (\`in ${formatRelative(q.sevenDayResetAt, now)}\`)` : "7d resets \u2014";
57926
- lines.push(` _${fiveFirst ? fiveSeg : sevenSeg}_`);
57927
- lines.push(` _${fiveFirst ? sevenSeg : fiveSeg}_`);
57928
- if (q.overageDisabledReason != null && OVERAGE_EXHAUSTED_REASONS.has(q.overageDisabledReason)) {
57929
- lines.push(` _overage off (${escapeMarkdown(q.overageDisabledReason)}) \u2014 serving from quota_`);
57930
- }
57931
- return lines;
57932
- }
57933
- function formatAgeStamp(atMs, now = new Date) {
57934
- const ageSec = Math.max(0, Math.round((now.getTime() - atMs) / 1000));
57935
- return ageSec < 60 ? `${ageSec}s ago` : `${Math.round(ageSec / 60)}m ago`;
57936
- }
57937
- var TABLE_HEALTH_RANK = {
57938
- blocked: 0,
57939
- throttling: 1,
57940
- unknown: 2,
57941
- healthy: 3
57942
- };
57943
- function tableCell(s) {
57944
- return s.replace(/\\/g, "\\\\").replace(/\|/g, "\\|").replace(/\r?\n/g, " ");
57945
- }
57946
- function pctCells(snap, now) {
57947
- if (!snap.quota)
57948
- return { five: "\u2014", seven: "\u2014" };
57949
- if (isProbeThin(snap.quota))
57950
- return { five: "?", seven: "?" };
57951
- const norm = refillNormalizedUtils(snap.quota, now);
57952
- return { five: fmtPct(norm.fiveHourUtilizationPct), seven: fmtPct(norm.sevenDayUtilizationPct) };
57953
- }
57954
- function windowResetCell(snap, now, tz, win) {
57955
- if (!snap.quota) {
57956
- return snap.quotaError ? `probe failed (${snap.quotaError})` : "\u2014";
57957
- }
57958
- if (isProbeThin(snap.quota))
57959
- return "quota unknown";
57960
- const reset2 = win === "5h" ? snap.quota.fiveHourResetAt : snap.quota.sevenDayResetAt;
57961
- if (!reset2)
57962
- return "\u2014";
57963
- return `${formatStatusTime(reset2, now, tz)} (in ${formatRelative(reset2, now)})`;
57964
- }
57965
- function renderAuthSnapshotFormat2(snapshots, opts = {}) {
57966
- const now = opts.now ?? new Date;
57967
- const tz = opts.tz ?? "UTC";
57968
- const lines = [];
57969
- lines.push("\uD83D\uDD0B **Auth \u2014 fleet status**");
57970
- const ordered = [...snapshots].sort((a, b) => {
57971
- if (a.isActive !== b.isActive)
57972
- return a.isActive ? -1 : 1;
57973
- const r = TABLE_HEALTH_RANK[classifyHealth(a, now)] - TABLE_HEALTH_RANK[classifyHealth(b, now)];
57974
- if (r !== 0)
57975
- return r;
57976
- return a.label.localeCompare(b.label);
57977
- });
57978
- if (ordered.length > 0) {
57979
- lines.push("");
57980
- lines.push("| State | Account | 5h | 5h resets | 7d | 7d resets |");
57981
- lines.push("| --- | --- | --- | --- | --- | --- |");
57982
- for (const s of ordered) {
57983
- const emoji = HEALTH_EMOJI[classifyHealth(s, now)];
57984
- const label = displayLabel(s.label, opts);
57985
- const accountCell = `\`${codeSpanSafe(s.isActive ? `${label} (active)` : label)}\``;
57986
- const { five, seven } = pctCells(s, now);
57987
- const fiveReset = windowResetCell(s, now, tz, "5h");
57988
- const sevenReset = windowResetCell(s, now, tz, "7d");
57989
- lines.push(`| ${emoji} | ${tableCell(accountCell)} | ${five} | ${tableCell(fiveReset)} | ${seven} | ${tableCell(sevenReset)} |`);
57990
- }
57991
- }
57992
- lines.push("");
57993
- lines.push(`_${recommendation(snapshots, now, opts.demo ?? false)}_`);
57994
- if (opts.staleCachedAtMs != null) {
57995
- lines.push(`_\u26a0 cached ${formatAgeStamp(opts.staleCachedAtMs, now)}_`);
57996
- } else if (opts.liveProbedAtMs != null) {
57997
- lines.push(`_Live \u00b7 refreshed ${formatAgeStamp(opts.liveProbedAtMs, now)}_`);
57998
- } else {
57999
- lines.push("_Live_");
58000
- }
58001
- return lines.join(`
58002
- `);
58003
- }
58004
- function recommendation(snapshots, now = new Date, demo = false) {
58005
- const active = snapshots.find((s) => s.isActive);
58006
- if (!active)
58007
- return "No active account set.";
58008
- const activeHealth = classifyHealth(active, now);
58009
- const others = snapshots.filter((s) => !s.isActive);
58010
- const healthyAlt = others.find((s) => classifyHealth(s, now) === "healthy");
58011
- const lbl = (s) => demo ? maskEmail(s.label) : s.label;
58012
- const activeLabel = lbl(active);
58013
- if (activeHealth === "healthy") {
58014
- return `Recommendation: stay on ${activeLabel}.`;
58015
- }
58016
- if (activeHealth === "throttling") {
58017
- if (healthyAlt) {
58018
- return `Recommendation: active ${activeLabel} is throttling. Switch to ${lbl(healthyAlt)} for headroom.`;
58019
- }
58020
- return `Recommendation: active ${activeLabel} is throttling; no healthy alternative \u2014 wait for refill.`;
58021
- }
58022
- if (activeHealth === "blocked") {
58023
- if (healthyAlt) {
58024
- return `Recommendation: active ${activeLabel} is BLOCKED \u2014 switch to ${lbl(healthyAlt)} now.`;
58025
- }
58026
- return summarizeNoHealthyAlt(snapshots, now, demo);
58027
- }
58028
- return `Active ${activeLabel}: quota probe failed; broker last_seen unknown.`;
58029
- }
58030
- function summarizeNoHealthyAlt(snapshots, now, demo = false) {
58031
- const mask = (label) => demo ? maskEmail(label) : label;
58032
- let throttlingLabel = null;
58033
- let allTrulyBlocked = true;
58034
- for (const s of snapshots) {
58035
- const h = classifyHealth(s, now);
58036
- if (h === "throttling") {
58037
- if (!throttlingLabel)
58038
- throttlingLabel = s.label;
58039
- allTrulyBlocked = false;
58040
- } else if (h === "healthy" || h === "unknown") {
58041
- allTrulyBlocked = false;
58042
- } else if (h === "blocked" && blockedReason(s, now) === "quota-exhausted") {
58043
- if (s.quota) {
58044
- const win = bindingWindow(s.quota);
58045
- const at = win === "5h" ? s.quota.fiveHourResetAt : s.quota.sevenDayResetAt;
58046
- if (at && at.getTime() > now.getTime())
58047
- allTrulyBlocked = false;
58048
- }
58471
+
58472
+ // gateway/resolve-person.ts
58473
+ function normalizeTelegramKey(raw) {
58474
+ return raw.trim().replace(/^@/, "").toLowerCase();
58475
+ }
58476
+ function buildPersonDirectory(entries) {
58477
+ const byTelegramKey = {};
58478
+ const dropped = [];
58479
+ const personIdOwner = new Map;
58480
+ const telegramKeyOwner = new Map;
58481
+ for (const raw of entries) {
58482
+ const key = typeof raw.key === "string" ? raw.key.trim() : "";
58483
+ const personId = typeof raw.person_id === "string" ? raw.person_id.trim() : "";
58484
+ if (key.length === 0) {
58485
+ dropped.push({ key: raw.key || "(unknown)", reason: "missing users: map key", reasonClass: "missing users: map key" });
58486
+ continue;
58049
58487
  }
58050
- }
58051
- const earliestRecovery = pickEarliestRecovery(snapshots, now);
58052
- if (throttlingLabel) {
58053
- const eta = earliestRecovery ? ` Soonest full refill: ${mask(earliestRecovery.label)} in ${formatRelative(earliestRecovery.at, now)}.` : "";
58054
- return `No fully-healthy account; ${mask(throttlingLabel)} is throttling but still usable.${eta}`;
58055
- }
58056
- if (!allTrulyBlocked) {
58057
- if (earliestRecovery) {
58058
- return `All accounts at capacity; soonest refill: ${mask(earliestRecovery.label)} in ${formatRelative(earliestRecovery.at, now)}.`;
58488
+ if (personId.length === 0) {
58489
+ dropped.push({ key, reason: "empty or missing person_id", reasonClass: "empty or missing person_id" });
58490
+ continue;
58059
58491
  }
58060
- return `All accounts at capacity \u2014 waiting on a window refill.`;
58061
- }
58062
- if (earliestRecovery) {
58063
- return `All accounts blocked. Earliest recovery: ${mask(earliestRecovery.label)} in ${formatRelative(earliestRecovery.at, now)}.`;
58064
- }
58065
- return `All accounts blocked. Run /auth add to attach another subscription.`;
58066
- }
58067
- function pickEarliestRecovery(snapshots, now) {
58068
- let best = null;
58069
- for (const s of snapshots) {
58070
- if (!s.quota)
58492
+ if (!Array.isArray(raw.telegram_ids) || raw.telegram_ids.length === 0) {
58493
+ dropped.push({ key, reason: "no telegram_ids to resolve against", reasonClass: "no telegram_ids to resolve against" });
58071
58494
  continue;
58072
- if (isProbeThin(s.quota))
58495
+ }
58496
+ const personIdLower = personId.toLowerCase();
58497
+ const existingOwner = personIdOwner.get(personIdLower);
58498
+ if (existingOwner != null && existingOwner !== key) {
58499
+ dropped.push({
58500
+ key,
58501
+ reason: `duplicate person_id "${personId}" already claimed by users.${existingOwner}`,
58502
+ reasonClass: `duplicate person_id already claimed by users.${existingOwner}`
58503
+ });
58073
58504
  continue;
58074
- const win = bindingWindow(s.quota);
58075
- const at = win === "5h" ? s.quota.fiveHourResetAt : s.quota.sevenDayResetAt;
58076
- if (!at || at.getTime() <= now.getTime())
58505
+ }
58506
+ personIdOwner.set(personIdLower, key);
58507
+ const telegramKeys = [...new Set(raw.telegram_ids.map(normalizeTelegramKey).filter((k) => k.length > 0))];
58508
+ if (telegramKeys.length === 0) {
58509
+ dropped.push({ key, reason: "telegram_ids contained no usable id/username", reasonClass: "telegram_ids contained no usable id/username" });
58510
+ personIdOwner.delete(personIdLower);
58077
58511
  continue;
58078
- if (!best || at.getTime() < best.at.getTime()) {
58079
- best = { label: s.label, at };
58080
58512
  }
58081
- }
58082
- return best;
58083
- }
58084
- function renderFallbackAnnouncement(input) {
58085
- const now = input.now ?? new Date;
58086
- const tz = input.tz ?? "UTC";
58087
- const lines = [];
58088
- const limitWord = input.oldQuota ? limitWordFor(input.oldQuota) : "quota";
58089
- const headerLimit = limitWord === "quota" ? "quota cap" : `${limitWord} limit`;
58090
- if (!input.newLabel) {
58091
- lines.push(`\uD83D\uDD34 **All accounts blocked \u00b7 ${headerLimit} on ${escapeMarkdown(input.oldLabel)}**`);
58092
- lines.push("");
58093
- lines.push(`Triggered by: agent **${escapeMarkdown(input.triggerAgent)}**`);
58094
- const fleet = input.fleetSnapshots ?? [];
58095
- if (fleet.length > 0) {
58096
- lines.push("");
58097
- const rowOpts = { now, tz };
58098
- const healthOrder = ["blocked", "throttling", "healthy", "unknown"];
58099
- const rank = (s) => healthOrder.indexOf(classifyHealth(s, now));
58100
- const ordered = [...fleet].sort((a, b) => rank(a) - rank(b) || Number(b.isActive) - Number(a.isActive));
58101
- for (const snap of ordered) {
58102
- for (const ln of renderAccountRow(snap, rowOpts))
58103
- lines.push(ln);
58104
- }
58105
- const earliest = pickEarliestRecovery(fleet, now);
58106
- if (earliest) {
58107
- lines.push("");
58108
- lines.push(`Earliest recovery: \`${codeSpanSafe(earliest.label)}\` ` + `${formatAbsolute(earliest.at, tz)} (in ${formatRelative(earliest.at, now)})`);
58109
- }
58110
- } else if (input.oldQuota) {
58111
- const recovery = recoveryAtFor(input.oldQuota);
58112
- if (recovery) {
58113
- lines.push(`${escapeMarkdown(input.oldLabel)} recovers ${formatAbsolute(recovery, tz)} ` + `(in ${formatRelative(recovery, now)})`);
58114
- }
58513
+ const collidingTelegramKey = telegramKeys.find((tk) => {
58514
+ const existingTkOwner = telegramKeyOwner.get(tk);
58515
+ return existingTkOwner != null && existingTkOwner !== key;
58516
+ });
58517
+ if (collidingTelegramKey != null) {
58518
+ const existingTkOwner = telegramKeyOwner.get(collidingTelegramKey);
58519
+ dropped.push({
58520
+ key,
58521
+ reason: `duplicate telegram_id "${collidingTelegramKey}" already claimed by users.${existingTkOwner}`,
58522
+ reasonClass: `duplicate telegram_id already claimed by users.${existingTkOwner}`
58523
+ });
58524
+ personIdOwner.delete(personIdLower);
58525
+ continue;
58115
58526
  }
58116
- lines.push("");
58117
- lines.push(`Run \`/auth add <label>\` to attach another subscription, ` + `or \`/auth refresh\` to re-probe.`);
58118
- return lines.join(`
58119
- `);
58120
- }
58121
- lines.push(`\u2713 **Switched fleet \u00b7 ${headerLimit} on ${escapeMarkdown(input.oldLabel)}**`);
58122
- lines.push("");
58123
- lines.push(`\`${codeSpanSafe(input.oldLabel)}\` \u2192 \`${codeSpanSafe(input.newLabel)}\``);
58124
- lines.push(`Triggered by: agent **${escapeMarkdown(input.triggerAgent)}**`);
58125
- lines.push("");
58126
- if (input.oldQuota) {
58127
- const recovery = recoveryAtFor(input.oldQuota);
58128
- if (recovery) {
58129
- lines.push(`\`${codeSpanSafe(input.oldLabel)}\` recovers ` + `${formatAbsolute(recovery, tz)} (in ${formatRelative(recovery, now)})`);
58527
+ const entry = { key, personId, telegramKeys };
58528
+ for (const tk of telegramKeys) {
58529
+ telegramKeyOwner.set(tk, key);
58530
+ byTelegramKey[tk] = entry;
58130
58531
  }
58131
58532
  }
58132
- if (input.newQuota) {
58133
- const fiveStr = fmtPct(input.newQuota.fiveHourUtilizationPct);
58134
- const sevenStr = fmtPct(input.newQuota.sevenDayUtilizationPct);
58135
- const hasHeadroom = input.newQuota.fiveHourUtilizationPct < THROTTLING_THRESHOLD_PCT && input.newQuota.sevenDayUtilizationPct < THROTTLING_THRESHOLD_PCT;
58136
- const headroomStr = hasHeadroom ? "_(plenty of headroom)_" : "_(near limit \u2014 watch this)_";
58137
- lines.push(`\`${codeSpanSafe(input.newLabel)}\` now: ${fiveStr} of 5h \u00b7 ${sevenStr} of 7d ${headroomStr}`);
58138
- } else {
58139
- lines.push(`_(quota probe for new account is pending \u2014 will reflect on next /auth)_`);
58140
- }
58141
- return lines.join(`
58142
- `);
58533
+ return { directory: { byTelegramKey }, dropped };
58143
58534
  }
58144
- function limitWordFor(q) {
58145
- if (q.representativeClaim === "seven_day" && q.sevenDayUtilizationPct >= 99)
58146
- return "7-day";
58147
- if (q.representativeClaim === "five_hour" && q.fiveHourUtilizationPct >= 99)
58148
- return "5-hour";
58149
- if (q.sevenDayUtilizationPct >= 99)
58150
- return "7-day";
58151
- if (q.fiveHourUtilizationPct >= 99)
58152
- return "5-hour";
58153
- return q.sevenDayUtilizationPct >= q.fiveHourUtilizationPct ? "7-day" : "5-hour";
58154
- }
58155
- function recoveryAtFor(q) {
58156
- const word = limitWordFor(q);
58157
- if (word === "7-day")
58158
- return q.sevenDayResetAt;
58159
- if (word === "5-hour")
58160
- return q.fiveHourResetAt;
58161
- if (!q.fiveHourResetAt)
58162
- return q.sevenDayResetAt;
58163
- if (!q.sevenDayResetAt)
58164
- return q.fiveHourResetAt;
58165
- return q.fiveHourResetAt.getTime() < q.sevenDayResetAt.getTime() ? q.fiveHourResetAt : q.sevenDayResetAt;
58166
- }
58167
- function buildSnapshotKeyboard(snapshots, opts = {}) {
58168
- const max = opts.maxSwitchButtons ?? 3;
58169
- const now = opts.now ?? new Date;
58170
- const rows = [];
58171
- const switchTargets = snapshots.filter((s) => !s.isActive).sort((a, b) => switchPriority(a, now) - switchPriority(b, now)).filter((s) => classifyHealth(s, now) !== "blocked" && classifyHealth(s, now) !== "unknown").slice(0, max);
58172
- for (const t of switchTargets) {
58173
- rows.push([
58174
- {
58175
- text: `Switch fleet \u2192 ${opts.demo ? maskEmail(t.label) : t.label}`,
58176
- callbackData: `auth:use:${t.label}`
58177
- }
58178
- ]);
58535
+ function runPersonDirectoryBootCheck(readEntries) {
58536
+ try {
58537
+ const rawEntries = readEntries();
58538
+ const { directory, dropped } = buildPersonDirectory(rawEntries);
58539
+ if (dropped.length > 0) {
58540
+ const alertSummary = dropped.map((d) => `${d.key} (${d.reasonClass})`).join("; ");
58541
+ const logSummary = dropped.map((d) => `${d.key} (${d.reason})`).join("; ");
58542
+ const plural = dropped.length === 1 ? "y" : "ies";
58543
+ return {
58544
+ directory,
58545
+ alertDetail: `person_id: dropped ${dropped.length} malformed users: entr${plural} at boot \u2014 ${alertSummary}`,
58546
+ logLine: `telegram gateway: person_id boot validation dropped ${dropped.length} entr${plural}: ${logSummary}`
58547
+ };
58548
+ }
58549
+ return {
58550
+ directory,
58551
+ alertDetail: null,
58552
+ logLine: rawEntries.length > 0 ? `telegram gateway: person_id boot validation ok \u2014 ${Object.keys(directory.byTelegramKey).length} telegram id/username(s) resolved` : `telegram gateway: person_id boot validation ok \u2014 no person_id entries configured`
58553
+ };
58554
+ } catch (err) {
58555
+ const msg = err instanceof Error ? err.message : String(err);
58556
+ return {
58557
+ directory: { byTelegramKey: {} },
58558
+ alertDetail: `person_id boot validation crashed \u2014 name resolution disabled this boot (fail-open, raw ids/usernames will show); see gateway stderr for detail`,
58559
+ logLine: `telegram gateway: person_id boot validation CRASHED (name resolution disabled this boot, raw ids will show \u2014 fail-open): ${msg}`
58560
+ };
58179
58561
  }
58180
- rows.push([
58181
- { text: "\u21bb Refresh", callbackData: opts.demo ? "auth:refresh:demo" : "auth:refresh" },
58182
- { text: "/usage", insertText: "/usage" },
58183
- { text: "+ Add", insertText: "/auth add " }
58184
- ]);
58185
- return rows;
58186
- }
58187
- function switchPriority(s, now = new Date) {
58188
- const h = classifyHealth(s, now);
58189
- if (h === "healthy")
58190
- return 0;
58191
- if (h === "throttling")
58192
- return 1;
58193
- if (h === "unknown")
58194
- return 2;
58195
- return 3;
58196
58562
  }
58197
- function buildSnapshotsFromState(state3, quotas) {
58198
- const out = [];
58199
- for (let i = 0;i < state3.accounts.length; i++) {
58200
- const acc = state3.accounts[i];
58201
- const q = quotas[i];
58202
- out.push({
58203
- label: acc.label,
58204
- isActive: acc.label === state3.active,
58205
- quota: q && q.ok ? q.data : null,
58206
- quotaError: q && !q.ok ? q.reason : undefined,
58207
- expiresAtMs: acc.expiresAt
58208
- });
58563
+ function resolvePersonName(directory, opts) {
58564
+ const idKey = normalizeTelegramKey(opts.telegramId);
58565
+ const usernameKey = opts.username ? normalizeTelegramKey(opts.username) : undefined;
58566
+ const entry = directory.byTelegramKey[idKey] ?? (usernameKey ? directory.byTelegramKey[usernameKey] : undefined);
58567
+ if (!entry)
58568
+ return;
58569
+ if (opts.isDm)
58570
+ return entry.personId;
58571
+ const allowFrom = opts.groupAllowFrom ?? [];
58572
+ const allowFromNormalized = allowFrom.map(normalizeTelegramKey);
58573
+ const memberConfirmed = allowFromNormalized.includes(idKey) || usernameKey != null && allowFromNormalized.includes(usernameKey);
58574
+ return memberConfirmed ? entry.personId : undefined;
58575
+ }
58576
+ function safeResolvePersonName(directory, opts, rawFallback) {
58577
+ try {
58578
+ return resolvePersonName(directory, opts) ?? rawFallback;
58579
+ } catch {
58580
+ return rawFallback;
58209
58581
  }
58210
- return out;
58211
58582
  }
58212
58583
 
58213
58584
  // gateway/auth-command.ts
58585
+ init_auth_snapshot_format();
58214
58586
  init_demo_mask();
58215
58587
  var AUTH_RM_CONFIRM_TTL_MS = 60000;
58216
58588
  var pendingAuthRmFlows = new Map;
@@ -60148,6 +60520,20 @@ function renderOperatorEvent(ev) {
60148
60520
  ]
60149
60521
  }
60150
60522
  };
60523
+ case "config-warning":
60524
+ return {
60525
+ text: [
60526
+ `\u2139\ufe0f **Config warning** for **${agent}**.`,
60527
+ detail ? `_${detail}_` : "",
60528
+ `Non-urgent \u2014 config will keep working with today's fallback behavior.`
60529
+ ].filter(Boolean).join(`
60530
+ `),
60531
+ keyboard: {
60532
+ inline_keyboard: [
60533
+ [{ text: "\u274c Dismiss", callback_data: `op:dismiss:${encodeURIComponent(ev.agent)}` }]
60534
+ ]
60535
+ }
60536
+ };
60151
60537
  }
60152
60538
  }
60153
60539
  var DEFAULT_OPERATOR_EVENT_COOLDOWN_MS = 5 * 60000;
@@ -60402,6 +60788,7 @@ function resolveModelUnavailableFromOperatorEvent(ev) {
60402
60788
 
60403
60789
  // auto-fallback-fleet.ts
60404
60790
  init_card_format();
60791
+ init_auth_snapshot_format();
60405
60792
  function renderFallbackFailureNotice(triggerAgent, reason) {
60406
60793
  return `\u26a0\ufe0f **Auto-failover could not run** (trigger: **${escapeMarkdown(triggerAgent)}**)
60407
60794
  ` + `${escapeMarkdown(reason)}
@@ -72126,7 +72513,7 @@ function readSubTail(entry, tail, now, onDescriptionUpdate, fs2, log, db2, paren
72126
72513
  const events = projectSubagentLine(line, entry.agentId, startState);
72127
72514
  const fireNarrativeProgress = () => {
72128
72515
  if (onProgress == null || entry.state !== "running" || entry.historical)
72129
- return;
72516
+ return false;
72130
72517
  try {
72131
72518
  onProgress({
72132
72519
  agentId: entry.agentId,
@@ -72140,24 +72527,26 @@ function readSubTail(entry, tail, now, onDescriptionUpdate, fs2, log, db2, paren
72140
72527
  lastTool: entry.lastTool,
72141
72528
  toolCount: entry.toolCount
72142
72529
  });
72530
+ return true;
72143
72531
  } catch (cbErr) {
72144
72532
  log?.(`subagent-watcher: onProgress callback error ${entry.agentId}: ${cbErr.message}`);
72533
+ return false;
72145
72534
  }
72146
72535
  };
72147
72536
  const resolvePendingSubNarrative = (toolName, toolInput) => {
72148
72537
  if (entry.pendingNarrative == null)
72149
- return;
72538
+ return false;
72150
72539
  const pending = entry.pendingNarrative;
72151
72540
  entry.pendingNarrative = null;
72152
72541
  if (toolName != null && REPLY_TOOLS2.has(toolName)) {
72153
72542
  const replyText = typeof toolInput?.text === "string" ? toolInput.text : "";
72154
72543
  if (isDraftOfReply2(pending.text, replyText))
72155
- return;
72544
+ return false;
72156
72545
  } else if (toolName == null && entry.lastReplyText != null && entry.lastReplyText.length > 0) {
72157
72546
  if (isDraftOfReply2(pending.text, entry.lastReplyText))
72158
- return;
72547
+ return false;
72159
72548
  }
72160
- fireNarrativeProgress();
72549
+ return fireNarrativeProgress();
72161
72550
  };
72162
72551
  for (const ev of events) {
72163
72552
  const idleSecBeforeBump = Math.round((now - entry.lastActivityAt) / 1000);
@@ -72185,7 +72574,7 @@ function readSubTail(entry, tail, now, onDescriptionUpdate, fs2, log, db2, paren
72185
72574
  log?.(`subagent-watcher: stall cleared for ${entry.agentId} (activity resumed after ${idleSecBeforeBump}s \u2014 re-arming detection)`);
72186
72575
  }
72187
72576
  if (ev.kind === "sub_agent_tool_use") {
72188
- resolvePendingSubNarrative(ev.toolName, ev.input);
72577
+ const narrativeJustFired = resolvePendingSubNarrative(ev.toolName, ev.input);
72189
72578
  if (REPLY_TOOLS2.has(ev.toolName) && typeof ev.input?.text === "string") {
72190
72579
  entry.lastReplyText = ev.input.text;
72191
72580
  }
@@ -72194,7 +72583,7 @@ function readSubTail(entry, tail, now, onDescriptionUpdate, fs2, log, db2, paren
72194
72583
  name: ev.toolName,
72195
72584
  sanitisedArg: sanitiseToolArg(ev.toolName, ev.input ?? {})
72196
72585
  };
72197
- if (onProgress != null && entry.state === "running" && !entry.historical) {
72586
+ if (onProgress != null && entry.state === "running" && !entry.historical && !narrativeJustFired) {
72198
72587
  const toolLine = describeToolUse(ev.toolName, ev.input ?? {});
72199
72588
  if (toolLine != null && toolLine.length > 0) {
72200
72589
  try {
@@ -74769,9 +75158,10 @@ function saveCreditState(stateDir, state4) {
74769
75158
  }
74770
75159
 
74771
75160
  // quota-watch.ts
75161
+ init_auth_snapshot_format();
75162
+ init_card_format();
74772
75163
  import { readFileSync as readFileSync42, writeFileSync as writeFileSync32, existsSync as existsSync41, mkdirSync as mkdirSync31 } from "fs";
74773
75164
  import { join as join43 } from "path";
74774
- init_card_format();
74775
75165
  var STATE_FILE2 = "quota-watch.json";
74776
75166
  function emptyQuotaWatchState() {
74777
75167
  return {};
@@ -74995,7 +75385,7 @@ function patchQuotaWatchState(current, accountLabel, accountState) {
74995
75385
  }
74996
75386
 
74997
75387
  // gateway/gateway.ts
74998
- init_auth_snapshot_format();
75388
+ init_auth_snapshot_format2();
74999
75389
 
75000
75390
  // demo-mask.ts
75001
75391
  function stableAssign2(input, cache, pool, overflow) {
@@ -75096,10 +75486,10 @@ function readTurnActiveMarkerAgeMs(stateDir, now) {
75096
75486
  }
75097
75487
 
75098
75488
  // ../src/build-info.ts
75099
- var VERSION = "0.18.3";
75100
- var COMMIT_SHA = "dbd8b112";
75101
- var COMMIT_DATE = "2026-07-09T13:17:41+10:00";
75102
- var LATEST_PR = 2952;
75489
+ var VERSION = "0.18.6";
75490
+ var COMMIT_SHA = "b62d558a";
75491
+ var COMMIT_DATE = "2026-07-10T10:22:09+10:00";
75492
+ var LATEST_PR = 2970;
75103
75493
  var COMMITS_AHEAD_OF_TAG = 0;
75104
75494
 
75105
75495
  // gateway/boot-version.ts
@@ -75161,7 +75551,7 @@ function classifyRejection(err, opts = {}) {
75161
75551
  if (e.error_code !== 400)
75162
75552
  return "shutdown";
75163
75553
  const desc = (e.description ?? "").toLowerCase();
75164
- if (desc.includes("message is not modified") || desc.includes("message to edit not found") || desc.includes("message to delete not found") || desc.includes("can't parse entities") || desc.includes("unsupported start tag") || desc.includes("chat not found") || desc.includes("not enough rights")) {
75554
+ if (desc.includes("message is not modified") || desc.includes("message to edit not found") || desc.includes("message to delete not found") || desc.includes("can't parse entities") || desc.includes("unsupported start tag") || desc.includes("chat not found") || desc.includes("not enough rights") || desc.includes("group chat was upgraded to a supergroup chat") || desc.includes("chat_id is empty") || desc.includes("group chat was deactivated")) {
75165
75555
  return "log_only";
75166
75556
  }
75167
75557
  return "shutdown";
@@ -76550,6 +76940,7 @@ var ACCESS_FILE = join47(STATE_DIR, "access.json");
76550
76940
  var APPROVED_DIR = join47(STATE_DIR, "approved");
76551
76941
  var ENV_FILE = join47(STATE_DIR, ".env");
76552
76942
  var INBOX_DIR = join47(STATE_DIR, "inbox");
76943
+ var PEOPLE_FILE = join47(STATE_DIR, "people.json");
76553
76944
  function triggerSelfRestart(targetAgent, reason, delayMs = 300) {
76554
76945
  const isDocker = process.env.SWITCHROOM_RUNTIME === "docker";
76555
76946
  const selfAgent = process.env.SWITCHROOM_AGENT_NAME;
@@ -76823,6 +77214,18 @@ var BOOT_ACCESS = STATIC ? (() => {
76823
77214
  function loadAccess() {
76824
77215
  return BOOT_ACCESS ?? readAccessFile();
76825
77216
  }
77217
+ function readPeopleFile() {
77218
+ try {
77219
+ const raw = readFileSync45(PEOPLE_FILE, "utf8");
77220
+ const parsed = JSON.parse(raw);
77221
+ if (!Array.isArray(parsed.entries))
77222
+ return [];
77223
+ return parsed.entries;
77224
+ } catch {
77225
+ return [];
77226
+ }
77227
+ }
77228
+ var PERSON_DIRECTORY = { byTelegramKey: {} };
76826
77229
  function assertAllowedChat(chat_id) {
76827
77230
  const id = String(chat_id);
76828
77231
  const access = loadAccess();
@@ -76999,6 +77402,29 @@ function resolveSubagentOriginChat(agentId) {
76999
77402
  return null;
77000
77403
  }
77001
77404
  }
77405
+ var WORKER_FEED_FALLBACK_LOG_CAP = 256;
77406
+ var workerFeedOwnerDmFallbackLogged = new Set;
77407
+ function resolveWorkerFeedChat(agentId, fleetChatId) {
77408
+ const origin = resolveSubagentOriginChat(agentId);
77409
+ if (origin != null && origin.chatId.length > 0)
77410
+ return origin;
77411
+ if (fleetChatId.length > 0)
77412
+ return { chatId: fleetChatId };
77413
+ const ownerDm = loadAccess().allowFrom[0] ?? "";
77414
+ if (origin == null && fleetChatId.length === 0 && ownerDm.length > 0) {
77415
+ if (!workerFeedOwnerDmFallbackLogged.has(agentId)) {
77416
+ workerFeedOwnerDmFallbackLogged.add(agentId);
77417
+ if (workerFeedOwnerDmFallbackLogged.size > WORKER_FEED_FALLBACK_LOG_CAP) {
77418
+ const oldest = workerFeedOwnerDmFallbackLogged.values().next().value;
77419
+ if (oldest != null)
77420
+ workerFeedOwnerDmFallbackLogged.delete(oldest);
77421
+ }
77422
+ process.stderr.write(`telegram gateway: worker-feed origin unresolved agent=${agentId} \u2014 routing card to owner DM
77423
+ `);
77424
+ }
77425
+ }
77426
+ return { chatId: ownerDm, threadId: origin?.threadId };
77427
+ }
77002
77428
  var REGISTRY_REAPER_INTERVAL_MS = 21600000;
77003
77429
  function runHistoryReaperNow(reason) {
77004
77430
  const retentionDays = resolveRetentionDays(HISTORY_ACCESS.historyRetentionDays);
@@ -77861,7 +78287,12 @@ async function resolveCompactCard(kind, occNow) {
77861
78287
  try {
77862
78288
  await swallowingApiCall(() => bot.api.editMessageText(card.chatId, card.messageId, richMessage2(text5), {}), { chat_id: card.chatId, verb: `proactiveCompact.${kind}` });
77863
78289
  } catch (err) {
77864
- process.stderr.write(`telegram gateway: proactive-compact ${kind} card edit failed: ${err instanceof Error ? err.message : String(err)}
78290
+ const desc = err instanceof Error ? err.message : String(err);
78291
+ const low = desc.toLowerCase();
78292
+ if (low.includes("not modified") || low.includes("not found") || low.includes("can't be edited") || low.includes("cannot be edited") || low.includes("not enough rights") || low.includes("429") || low.includes("retry after")) {
78293
+ return;
78294
+ }
78295
+ process.stderr.write(`telegram gateway: proactive-compact ${kind} card edit failed: ${desc}
77865
78296
  `);
77866
78297
  }
77867
78298
  }
@@ -82387,7 +82818,9 @@ async function drainActivitySummary(turn, producer = "tool", openFlags) {
82387
82818
  turn.activityLastSentRender = target;
82388
82819
  } catch (err) {
82389
82820
  const msg = err instanceof Error ? err.message : String(err);
82390
- if (!msg.includes("message is not modified")) {
82821
+ const low = msg.toLowerCase();
82822
+ const isTransport = low.includes("not modified") || low.includes("not found") || low.includes("can't be edited") || low.includes("cannot be edited") || low.includes("not enough rights") || low.includes("429") || low.includes("retry after");
82823
+ if (!isTransport) {
82391
82824
  turn.activityDrainFailures += 1;
82392
82825
  process.stderr.write(`telegram gateway: activity-summary drain failed: ${msg} (chat=${chat} thread=${thread ?? "-"} replyAnchor=${turn.sourceMessageId ?? "none"} everOpened=${turn.activityEverOpened} failures=${turn.activityDrainFailures})
82393
82826
  `);
@@ -82558,7 +82991,12 @@ function clearActivitySummary(turn, finalHtmlOverride) {
82558
82991
  try {
82559
82992
  await robustApiCall(() => bot.api.deleteMessage(chat, id), { chat_id: chat, ...thread != null ? { threadId: thread } : {}, verb: "activity-summary.delete" });
82560
82993
  } catch (err) {
82561
- process.stderr.write(`telegram gateway: activity-summary delete failed: ${err}
82994
+ const msg = err instanceof Error ? err.message : String(err);
82995
+ const low = msg.toLowerCase();
82996
+ if (low.includes("not modified") || low.includes("not found") || low.includes("not enough rights") || low.includes("429") || low.includes("retry after")) {
82997
+ return;
82998
+ }
82999
+ process.stderr.write(`telegram gateway: activity-summary delete failed: ${msg}
82562
83000
  `);
82563
83001
  }
82564
83002
  return;
@@ -82580,10 +83018,12 @@ function clearActivitySummary(turn, finalHtmlOverride) {
82580
83018
  await robustApiCall(() => bot.api.editMessageText(chat, id, richMessage2(finalHtml), {}), { chat_id: chat, ...thread != null ? { threadId: thread } : {}, verb: "activity-summary.finalize" });
82581
83019
  } catch (err) {
82582
83020
  const msg = err instanceof Error ? err.message : String(err);
82583
- if (!msg.includes("message is not modified")) {
82584
- process.stderr.write(`telegram gateway: activity-summary finalize failed: ${msg}
82585
- `);
83021
+ const low = msg.toLowerCase();
83022
+ if (low.includes("not modified") || low.includes("not found") || low.includes("can't be edited") || low.includes("cannot be edited") || low.includes("not enough rights") || low.includes("429") || low.includes("retry after")) {
83023
+ return;
82586
83024
  }
83025
+ process.stderr.write(`telegram gateway: activity-summary finalize failed: ${msg}
83026
+ `);
82587
83027
  }
82588
83028
  });
82589
83029
  }
@@ -84192,12 +84632,19 @@ ${preBlock(write.output)}`;
84192
84632
  }
84193
84633
  const originTurnId = deriveTurnId(chat_id, messageThreadId ?? null, msgId);
84194
84634
  const topicScope = TOPIC_FRAMING_ENABLED && messageThreadId != null ? "This message belongs to the current topic only \u2014 answer ONLY this question, in this topic. Do not also answer a pending message from another topic." : undefined;
84635
+ const rawUser = from.username ?? String(from.id);
84636
+ const displayUser = safeResolvePersonName(PERSON_DIRECTORY, {
84637
+ telegramId: String(from.id),
84638
+ username: from.username,
84639
+ isDm: isDmChatId(chat_id),
84640
+ groupAllowFrom: access.groups[chat_id]?.allowFrom
84641
+ }, rawUser);
84195
84642
  const inboundMsg = {
84196
84643
  type: "inbound",
84197
84644
  chatId: chat_id,
84198
84645
  ...messageThreadId != null ? { threadId: messageThreadId } : {},
84199
84646
  messageId: msgId ?? 0,
84200
- user: from.username ?? String(from.id),
84647
+ user: displayUser,
84201
84648
  userId: from.id,
84202
84649
  ts: ctx.message?.date ?? Math.floor(Date.now() / 1000),
84203
84650
  text: effectiveText,
@@ -84212,7 +84659,7 @@ ${preBlock(write.output)}`;
84212
84659
  meta: {
84213
84660
  chat_id,
84214
84661
  ...msgId != null ? { message_id: String(msgId) } : {},
84215
- user: from.username ?? String(from.id),
84662
+ user: displayUser,
84216
84663
  user_id: String(from.id),
84217
84664
  ts: new Date((ctx.message?.date ?? 0) * 1000).toISOString(),
84218
84665
  ...messageThreadId != null ? { message_thread_id: String(messageThreadId) } : {},
@@ -87729,14 +88176,14 @@ _Tap /auth to see updated quota for the new active account._`), {}), { chat_id:
87729
88176
  const probeResp = state4.accounts.length > 0 ? await client3.probeQuota(state4.accounts.map((a) => a.label), undefined, true).catch(() => ({ results: [] })) : { results: [] };
87730
88177
  const { quotas, staleCachedAtMs } = zipProbeResults(state4.accounts.map((a) => a.label), probeResp.results);
87731
88178
  const tz = process.env.SWITCHROOM_TIMEZONE ?? process.env.TZ ?? "UTC";
87732
- const { renderAuthSnapshotFormat2: renderAuthSnapshotFormat23, buildSnapshotsFromState: buildSnapshotsFromState4, buildSnapshotKeyboard: buildSnapshotKeyboard3 } = await Promise.resolve().then(() => (init_auth_snapshot_format(), exports_auth_snapshot_format));
88179
+ const { renderAuthSnapshotFormat2: renderAuthSnapshotFormat23, buildSnapshotsFromState: buildSnapshotsFromState4, buildSnapshotKeyboard: buildSnapshotKeyboard3 } = await Promise.resolve().then(() => (init_auth_snapshot_format2(), exports_auth_snapshot_format));
87733
88180
  const snapshots = buildSnapshotsFromState4(state4, quotas);
87734
88181
  const renderNow = new Date;
87735
88182
  const text5 = renderAuthSnapshotFormat23(snapshots, {
87736
88183
  tz,
87737
88184
  now: renderNow,
87738
88185
  demo: refreshDemo,
87739
- ...staleCachedAtMs != null ? { staleCachedAtMs } : { liveProbedAtMs: renderNow.getTime() }
88186
+ ...staleCachedAtMs != null ? { staleCachedAtMs } : probeResp.results.length > 0 ? { liveProbedAtMs: renderNow.getTime() } : { probeFailed: true }
87740
88187
  });
87741
88188
  const kbRows = buildSnapshotKeyboard3(snapshots, { now: renderNow, demo: refreshDemo });
87742
88189
  const inline_keyboard = kbRows.map((row) => row.map((b) => {
@@ -88118,16 +88565,17 @@ bot.command("usage", async (ctx) => {
88118
88565
  if (client3) {
88119
88566
  const state4 = await client3.listState();
88120
88567
  if (state4.accounts.length > 0) {
88568
+ const renderNow = new Date;
88121
88569
  const probeResp = await client3.probeQuota(state4.accounts.map((a) => a.label)).catch(() => ({ results: [] }));
88122
88570
  const { quotas, staleCachedAtMs } = zipProbeResults(state4.accounts.map((a) => a.label), probeResp.results);
88123
- const { renderAuthSnapshotFormat2: renderAuthSnapshotFormat23, buildSnapshotsFromState: buildSnapshotsFromState4, buildSnapshotKeyboard: buildSnapshotKeyboard3 } = await Promise.resolve().then(() => (init_auth_snapshot_format(), exports_auth_snapshot_format));
88124
- const tz = process.env.SWITCHROOM_TIMEZONE ?? process.env.TZ ?? "UTC";
88571
+ const { buildSnapshotsFromState: buildSnapshotsFromState4, buildSnapshotKeyboard: buildSnapshotKeyboard3 } = await Promise.resolve().then(() => (init_auth_snapshot_format2(), exports_auth_snapshot_format));
88572
+ const { renderUsageCard: renderUsageCard2 } = await Promise.resolve().then(() => (init_quota_bar_format(), exports_quota_bar_format));
88125
88573
  const snapshots = buildSnapshotsFromState4(state4, quotas);
88126
- const text5 = renderAuthSnapshotFormat23(snapshots, {
88127
- tz,
88128
- now: new Date,
88574
+ const exhaustedByLabel = new Map(state4.accounts.map((a) => [a.label, a.exhausted]));
88575
+ const text5 = renderUsageCard2(snapshots, exhaustedByLabel, {
88576
+ now: renderNow,
88129
88577
  demo,
88130
- ...staleCachedAtMs != null ? { staleCachedAtMs } : { liveProbedAtMs: Date.now() }
88578
+ ...staleCachedAtMs != null ? { staleCachedAtMs } : probeResp.results.length > 0 ? { liveProbedAtMs: renderNow.getTime() } : { probeFailed: true }
88131
88579
  });
88132
88580
  const kbRows = buildSnapshotKeyboard3(snapshots, { now: new Date, demo });
88133
88581
  const keyboard = new import_grammy12.InlineKeyboard;
@@ -89970,6 +90418,21 @@ var didOneTimeSetup = false;
89970
90418
  `);
89971
90419
  if (!didOneTimeSetup) {
89972
90420
  didOneTimeSetup = true;
90421
+ {
90422
+ const bootCheck = runPersonDirectoryBootCheck(readPeopleFile);
90423
+ PERSON_DIRECTORY = bootCheck.directory;
90424
+ process.stderr.write(bootCheck.logLine + `
90425
+ `);
90426
+ if (bootCheck.alertDetail != null) {
90427
+ emitGatewayOperatorEvent({
90428
+ kind: "config-warning",
90429
+ agent: process.env.SWITCHROOM_AGENT_NAME ?? "-",
90430
+ detail: bootCheck.alertDetail,
90431
+ suggestedActions: [],
90432
+ firstSeenAt: new Date
90433
+ });
90434
+ }
90435
+ }
89973
90436
  registerSwitchroomBotCommands().catch(() => {});
89974
90437
  try {
89975
90438
  const accessAtBoot = loadAccess();
@@ -90459,8 +90922,8 @@ var didOneTimeSetup = false;
90459
90922
  orphanStatusEnabled
90460
90923
  });
90461
90924
  if (surface === "worker-feed") {
90462
- const origin = resolveSubagentOriginChat(agentId);
90463
- const wkChat = origin?.chatId || fleetChatId || (loadAccess().allowFrom[0] ?? "");
90925
+ const wk = resolveWorkerFeedChat(agentId, fleetChatId);
90926
+ const wkChat = wk.chatId;
90464
90927
  workerActivityFeed?.update(agentId, wkChat, {
90465
90928
  description: dispatch.feedDescription,
90466
90929
  lastTool,
@@ -90468,7 +90931,7 @@ var didOneTimeSetup = false;
90468
90931
  latestSummary: stepLine,
90469
90932
  elapsedMs,
90470
90933
  state: "running"
90471
- }, origin?.threadId)?.then(() => reconcileWorkerPin(agentId, wkChat, true));
90934
+ }, wk.threadId)?.then(() => reconcileWorkerPin(agentId, wkChat, true));
90472
90935
  return;
90473
90936
  }
90474
90937
  if (surface !== "nest")
@@ -90517,8 +90980,8 @@ var didOneTimeSetup = false;
90517
90980
  stampTurn.subagentActivityAt = Date.now();
90518
90981
  }
90519
90982
  if (workerFeedEnabled) {
90520
- const origin = resolveSubagentOriginChat(agentId);
90521
- const wkChat = origin?.chatId || fleetChatId || (loadAccess().allowFrom[0] ?? "");
90983
+ const wk = resolveWorkerFeedChat(agentId, fleetChatId);
90984
+ const wkChat = wk.chatId;
90522
90985
  workerActivityFeed?.update(agentId, wkChat, {
90523
90986
  description: dispatch.feedDescription,
90524
90987
  lastTool,
@@ -90526,7 +90989,7 @@ var didOneTimeSetup = false;
90526
90989
  latestSummary: stepLine,
90527
90990
  elapsedMs,
90528
90991
  state: "running"
90529
- }, origin?.threadId)?.then(() => reconcileWorkerPin(agentId, wkChat, true));
90992
+ }, wk.threadId)?.then(() => reconcileWorkerPin(agentId, wkChat, true));
90530
90993
  return;
90531
90994
  }
90532
90995
  const progressOrigin = resolveSubagentOriginChat(agentId);