switchroom 0.19.1 → 0.19.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/dist/agent-scheduler/index.js +31 -1
  2. package/dist/auth-broker/index.js +565 -48
  3. package/dist/cli/autoaccept-poll.js +31 -1
  4. package/dist/cli/drive-write-pretool.mjs +32 -2
  5. package/dist/cli/ms-365-write-pretool.mjs +32 -2
  6. package/dist/cli/switchroom.js +1148 -274
  7. package/dist/host-control/main.js +3 -3
  8. package/dist/vault/approvals/kernel-server.js +2 -2
  9. package/dist/vault/broker/server.js +2 -2
  10. package/package.json +3 -2
  11. package/profiles/_base/start.sh.hbs +1 -0
  12. package/profiles/default/CLAUDE.md.hbs +8 -0
  13. package/skills/mental-model-curator/SKILL.md +68 -2
  14. package/skills/switchroom-cli/SKILL.md +25 -0
  15. package/telegram-plugin/auth-snapshot-format.ts +143 -12
  16. package/telegram-plugin/dist/bridge/bridge.js +8 -2
  17. package/telegram-plugin/dist/gateway/gateway.js +1427 -689
  18. package/telegram-plugin/dist/server.js +8 -2
  19. package/telegram-plugin/external-spend.ts +135 -0
  20. package/telegram-plugin/flushed-turn-supersede.ts +117 -13
  21. package/telegram-plugin/gateway/auth-add-flow.ts +215 -6
  22. package/telegram-plugin/gateway/auth-command.ts +138 -5
  23. package/telegram-plugin/gateway/gateway.ts +141 -158
  24. package/telegram-plugin/gateway/inbound-interceptors.ts +13 -3
  25. package/telegram-plugin/gateway/model-command.ts +309 -1
  26. package/telegram-plugin/gateway/narrative-lane.ts +23 -9
  27. package/telegram-plugin/gateway/outbound-send-path.ts +68 -15
  28. package/telegram-plugin/gateway/session-model-source.ts +90 -10
  29. package/telegram-plugin/gateway/status-pin-store.ts +64 -4
  30. package/telegram-plugin/gateway/stream-render.ts +22 -5
  31. package/telegram-plugin/gateway/usage-mask.ts +29 -0
  32. package/telegram-plugin/hooks/subagent-tracker-pretool.mjs +19 -2
  33. package/telegram-plugin/quota-bar-format.ts +78 -12
  34. package/telegram-plugin/quota-check.ts +17 -2
  35. package/telegram-plugin/reply-owner-resolve.ts +76 -11
  36. package/telegram-plugin/session-tail.ts +27 -3
  37. package/telegram-plugin/tests/activity-card-wiring.test.ts +47 -0
  38. package/telegram-plugin/tests/auth-add-flow.test.ts +367 -5
  39. package/telegram-plugin/tests/auth-snapshot-format.test.ts +41 -0
  40. package/telegram-plugin/tests/external-spend.test.ts +168 -0
  41. package/telegram-plugin/tests/flushed-turn-supersede.test.ts +117 -0
  42. package/telegram-plugin/tests/gateway-session-model-relaunch.test.ts +219 -29
  43. package/telegram-plugin/tests/model-command.test.ts +220 -0
  44. package/telegram-plugin/tests/quota-bar-format.test.ts +43 -0
  45. package/telegram-plugin/tests/quota-check.test.ts +57 -0
  46. package/telegram-plugin/tests/reply-owner-resolve.test.ts +257 -13
  47. package/telegram-plugin/tests/send-reply-golden.test.ts +154 -0
  48. package/telegram-plugin/tests/session-model-source.test.ts +142 -0
  49. package/telegram-plugin/tests/session-tail-first-attach.test.ts +115 -2
  50. package/telegram-plugin/tests/status-pin-store.test.ts +198 -0
  51. package/telegram-plugin/tests/subagent-tracker-hooks.test.ts +50 -0
  52. package/telegram-plugin/tests/usage-footer-freshness.test.ts +141 -0
  53. package/telegram-plugin/tests/usage-mask.test.ts +35 -0
  54. package/telegram-plugin/tests/worker-feed-dispatch.test.ts +27 -0
  55. package/telegram-plugin/tests/worker-feed-pin-persistence.test.ts +131 -1
  56. package/vendor/hindsight-memory/CHANGELOG.md +102 -0
  57. package/vendor/hindsight-memory/README.md +2 -1
  58. package/vendor/hindsight-memory/hooks/hooks.json +12 -0
  59. package/vendor/hindsight-memory/scripts/directive_verify.py +100 -3
  60. package/vendor/hindsight-memory/scripts/lib/config.py +150 -1
  61. package/vendor/hindsight-memory/scripts/lib/content.py +55 -5
  62. package/vendor/hindsight-memory/scripts/lib/directives.py +152 -15
  63. package/vendor/hindsight-memory/scripts/lib/parallel_recall.py +142 -0
  64. package/vendor/hindsight-memory/scripts/lib/state.py +31 -0
  65. package/vendor/hindsight-memory/scripts/recall.py +789 -143
  66. package/vendor/hindsight-memory/scripts/reconcile_tail.py +22 -1
  67. package/vendor/hindsight-memory/scripts/retain.py +71 -2
  68. package/vendor/hindsight-memory/scripts/subagent_retain.py +501 -0
  69. package/vendor/hindsight-memory/scripts/tests/test_directive_verify.py +169 -0
  70. package/vendor/hindsight-memory/scripts/tests/test_directives.py +177 -0
  71. package/vendor/hindsight-memory/scripts/tests/test_lesson_tagging.py +200 -0
  72. package/vendor/hindsight-memory/scripts/tests/test_recall_context_turns_default.py +200 -0
  73. package/vendor/hindsight-memory/scripts/tests/test_recall_envelope_strip_telemetry.py +477 -0
  74. package/vendor/hindsight-memory/scripts/tests/test_recall_integration.py +51 -0
  75. package/vendor/hindsight-memory/scripts/tests/test_recall_parallel_deadline.py +409 -0
  76. package/vendor/hindsight-memory/scripts/tests/test_recall_tag_weights.py +96 -0
  77. package/vendor/hindsight-memory/scripts/tests/test_recall_transcript_fallback.py +413 -0
  78. package/vendor/hindsight-memory/scripts/tests/test_reconcile_durability.py +49 -0
  79. package/vendor/hindsight-memory/scripts/tests/test_subagent_retain.py +439 -0
  80. package/vendor/hindsight-memory/settings.json +3 -1
@@ -7196,6 +7196,67 @@ var init_format = __esm(() => {
7196
7196
  ];
7197
7197
  });
7198
7198
 
7199
+ // ../src/auth/quota.ts
7200
+ function isProbeThin(q) {
7201
+ return q.fiveHourUtilPresent === false && q.sevenDayUtilPresent === false;
7202
+ }
7203
+ function refillNormalizedUtils(q, now) {
7204
+ const nowMs = now.getTime();
7205
+ const fiveHourRefilled = q.fiveHourResetAt != null && q.fiveHourResetAt.getTime() <= nowMs;
7206
+ const sevenDayRefilled = q.sevenDayResetAt != null && q.sevenDayResetAt.getTime() <= nowMs;
7207
+ return {
7208
+ fiveHourUtilizationPct: fiveHourRefilled ? 0 : q.fiveHourUtilizationPct,
7209
+ sevenDayUtilizationPct: sevenDayRefilled ? 0 : q.sevenDayUtilizationPct,
7210
+ fiveHourRefilled,
7211
+ sevenDayRefilled
7212
+ };
7213
+ }
7214
+
7215
+ // demo-mask.ts
7216
+ function stableAssign(input, cache, pool, overflow) {
7217
+ const existing = cache.get(input);
7218
+ if (existing !== undefined)
7219
+ return existing;
7220
+ const index = cache.size;
7221
+ const value = index < pool.length ? pool[index] : overflow(index);
7222
+ cache.set(input, value);
7223
+ return value;
7224
+ }
7225
+ function maskEmail(label) {
7226
+ return stableAssign(label, emailCache, EMAIL_POOL, (i) => `demo${i + 1}@example.com`);
7227
+ }
7228
+ function maskUsername(tag) {
7229
+ return stableAssign(tag, usernameCache, USERNAME_POOL, (i) => `@demo_user${i + 1}`);
7230
+ }
7231
+ function maskVaultKey(name) {
7232
+ return stableAssign(name, vaultKeyCache, [], (i) => `demo/secret-${i + 1}`);
7233
+ }
7234
+ var EMAIL_POOL, emailCache, USERNAME_POOL, usernameCache, vaultKeyCache;
7235
+ var init_demo_mask = __esm(() => {
7236
+ EMAIL_POOL = [
7237
+ "ada@example.com",
7238
+ "grace@example.com",
7239
+ "linus@example.com",
7240
+ "hopper@example.com",
7241
+ "turing@example.com",
7242
+ "lovelace@example.com",
7243
+ "dijkstra@example.com",
7244
+ "knuth@example.com",
7245
+ "ritchie@example.com",
7246
+ "torvalds@example.com"
7247
+ ];
7248
+ emailCache = new Map;
7249
+ USERNAME_POOL = [
7250
+ "@demo_user",
7251
+ "@demo_user2",
7252
+ "@demo_user3",
7253
+ "@demo_user4",
7254
+ "@demo_user5"
7255
+ ];
7256
+ usernameCache = new Map;
7257
+ vaultKeyCache = new Map;
7258
+ });
7259
+
7199
7260
  // text-voice-scrub.ts
7200
7261
  function enabled() {
7201
7262
  const v = process.env.SWITCHROOM_DISABLE_VOICE_SCRUB;
@@ -7383,6 +7444,533 @@ var init_card_format = __esm(() => {
7383
7444
  init_text_voice_scrub();
7384
7445
  });
7385
7446
 
7447
+ // auth-snapshot-format.ts
7448
+ var exports_auth_snapshot_format = {};
7449
+ __export(exports_auth_snapshot_format, {
7450
+ zipProbeResults: () => zipProbeResults,
7451
+ reviveLastQuota: () => reviveLastQuota,
7452
+ renderFallbackAnnouncement: () => renderFallbackAnnouncement,
7453
+ renderAuthSnapshotFormat2: () => renderAuthSnapshotFormat2,
7454
+ recommendation: () => recommendation,
7455
+ formatStatusTime: () => formatStatusTime,
7456
+ formatRelative: () => formatRelative,
7457
+ formatAbsolute: () => formatAbsolute,
7458
+ fmtPct: () => fmtPct,
7459
+ deriveUsageFooterFreshness: () => deriveUsageFooterFreshness,
7460
+ classifyHealth: () => classifyHealth,
7461
+ buildSnapshotsFromState: () => buildSnapshotsFromState,
7462
+ buildSnapshotsFromCachedState: () => buildSnapshotsFromCachedState,
7463
+ buildSnapshotKeyboard: () => buildSnapshotKeyboard,
7464
+ blockedReason: () => blockedReason,
7465
+ bindingWindow: () => bindingWindow,
7466
+ THROTTLING_THRESHOLD_PCT: () => THROTTLING_THRESHOLD_PCT
7467
+ });
7468
+ function classifyHealth(snap, now = new Date) {
7469
+ if (!snap.isActive) {
7470
+ if (snap.entitlementBlocked === true)
7471
+ return "org-blocked";
7472
+ if (snap.inService === false)
7473
+ return "retired";
7474
+ }
7475
+ if (!snap.quota)
7476
+ return "unknown";
7477
+ const q = snap.quota;
7478
+ if (isProbeThin(q))
7479
+ return "unknown";
7480
+ const norm = refillNormalizedUtils(q, now);
7481
+ const max = Math.max(norm.fiveHourUtilizationPct, norm.sevenDayUtilizationPct);
7482
+ if (max >= 99.5)
7483
+ return "blocked";
7484
+ if (max >= THROTTLING_THRESHOLD_PCT)
7485
+ return "throttling";
7486
+ return "healthy";
7487
+ }
7488
+ function blockedReason(snap, now = new Date) {
7489
+ if (classifyHealth(snap, now) !== "blocked")
7490
+ return null;
7491
+ return "quota-exhausted";
7492
+ }
7493
+ function bindingWindow(q) {
7494
+ if (q.representativeClaim === "seven_day")
7495
+ return "7d";
7496
+ if (q.representativeClaim === "five_hour")
7497
+ return "5h";
7498
+ return q.sevenDayUtilizationPct >= q.fiveHourUtilizationPct ? "7d" : "5h";
7499
+ }
7500
+ function formatRelative(target, now = new Date) {
7501
+ if (!target)
7502
+ return "\u2014";
7503
+ const deltaMs = target.getTime() - now.getTime();
7504
+ if (deltaMs <= 0)
7505
+ return "now";
7506
+ const totalMin = Math.round(deltaMs / 60000);
7507
+ if (totalMin < 60)
7508
+ return `${totalMin}m`;
7509
+ const h = Math.floor(totalMin / 60);
7510
+ const m = totalMin % 60;
7511
+ if (h < 24)
7512
+ return m > 0 ? `${h}h ${m}m` : `${h}h`;
7513
+ const d = Math.floor(h / 24);
7514
+ const rh = h % 24;
7515
+ return rh > 0 ? `${d}d ${rh}h` : `${d}d`;
7516
+ }
7517
+ function formatAbsolute(target, tz = "UTC") {
7518
+ if (!target)
7519
+ return "\u2014";
7520
+ return target.toLocaleString("en-US", {
7521
+ timeZone: tz,
7522
+ weekday: "short",
7523
+ hour: "numeric",
7524
+ minute: "2-digit",
7525
+ hour12: true
7526
+ });
7527
+ }
7528
+ function formatStatusTime(target, now = new Date, tz = "UTC") {
7529
+ if (!target)
7530
+ return "\u2014";
7531
+ const dayFmt = new Intl.DateTimeFormat("en-CA", {
7532
+ timeZone: tz,
7533
+ year: "numeric",
7534
+ month: "2-digit",
7535
+ day: "2-digit"
7536
+ });
7537
+ const timeFmt = new Intl.DateTimeFormat("en-US", {
7538
+ timeZone: tz,
7539
+ hour: "numeric",
7540
+ minute: "2-digit",
7541
+ hour12: true
7542
+ });
7543
+ const timeStr = timeFmt.format(target);
7544
+ if (dayFmt.format(target) === dayFmt.format(now)) {
7545
+ return timeStr;
7546
+ }
7547
+ const withinWeek = target.getTime() - now.getTime() < 7 * 24 * 60 * 60 * 1000;
7548
+ const dateFmt = new Intl.DateTimeFormat("en-US", {
7549
+ timeZone: tz,
7550
+ weekday: "short",
7551
+ ...withinWeek ? {} : { day: "numeric", month: "short" }
7552
+ });
7553
+ return `${dateFmt.format(target)} ${timeStr}`;
7554
+ }
7555
+ function fmtPct(pct) {
7556
+ return `${Math.min(100, Math.round(pct))}%`;
7557
+ }
7558
+ function displayLabel(label, opts) {
7559
+ return opts.demo ? maskEmail(label) : label;
7560
+ }
7561
+ function renderAccountRow(snap, opts) {
7562
+ const now = opts.now ?? new Date;
7563
+ const tz = opts.tz ?? "UTC";
7564
+ const lines = [];
7565
+ const marker = snap.isActive ? "\u25cf " : "";
7566
+ const label = displayLabel(snap.label, opts);
7567
+ const health = classifyHealth(snap, now);
7568
+ if (health === "org-blocked" || health === "retired") {
7569
+ const note = health === "org-blocked" ? "DISABLED (org) \u2014 no fleet routing" : "retired \u2014 removed from fleet rotation";
7570
+ lines.push(`${marker}\`${codeSpanSafe(label)}\` _${note}_`);
7571
+ return lines;
7572
+ }
7573
+ if (!snap.quota) {
7574
+ lines.push(`${marker}\`${codeSpanSafe(label)}\` _quota probe failed_`);
7575
+ if (snap.quotaError) {
7576
+ lines.push(` _${escapeMarkdown(snap.quotaError)}_`);
7577
+ }
7578
+ return lines;
7579
+ }
7580
+ const q = snap.quota;
7581
+ if (isProbeThin(q)) {
7582
+ lines.push(`${marker}\`${codeSpanSafe(label)}\` _quota unknown (thin probe)_`);
7583
+ return lines;
7584
+ }
7585
+ const norm = refillNormalizedUtils(q, now);
7586
+ const fiveStr = fmtPct(norm.fiveHourUtilizationPct);
7587
+ const sevenStr = fmtPct(norm.sevenDayUtilizationPct);
7588
+ lines.push(`${marker}\`${codeSpanSafe(label)}\` ${fiveStr} / ${sevenStr}`);
7589
+ if (health === "blocked") {
7590
+ const win = bindingWindow(q);
7591
+ const reset = win === "5h" ? q.fiveHourResetAt : q.sevenDayResetAt;
7592
+ const winLabel = win === "5h" ? "5-hour" : "7-day";
7593
+ lines.push(reset ? ` _quota exhausted \u2014 back ${formatAbsolute(reset, tz)} (\`in ${formatRelative(reset, now)}\`, ${winLabel} cap)_` : ` _quota exhausted \u2014 ${winLabel} cap, reset time unknown_`);
7594
+ return lines;
7595
+ }
7596
+ const fiveResetIn = q.fiveHourResetAt ? q.fiveHourResetAt.getTime() - now.getTime() : Infinity;
7597
+ const sevenResetIn = q.sevenDayResetAt ? q.sevenDayResetAt.getTime() - now.getTime() : Infinity;
7598
+ const fiveFirst = fiveResetIn <= sevenResetIn;
7599
+ const fiveSeg = q.fiveHourResetAt ? `5h refills ${formatAbsolute(q.fiveHourResetAt, tz)} (\`in ${formatRelative(q.fiveHourResetAt, now)}\`)` : "5h refills \u2014";
7600
+ const sevenSeg = q.sevenDayResetAt ? `7d resets ${formatAbsolute(q.sevenDayResetAt, tz)} (\`in ${formatRelative(q.sevenDayResetAt, now)}\`)` : "7d resets \u2014";
7601
+ lines.push(` _${fiveFirst ? fiveSeg : sevenSeg}_`);
7602
+ lines.push(` _${fiveFirst ? sevenSeg : fiveSeg}_`);
7603
+ if (q.overageDisabledReason != null && OVERAGE_EXHAUSTED_REASONS.has(q.overageDisabledReason)) {
7604
+ lines.push(` _overage off (${escapeMarkdown(q.overageDisabledReason)}) \u2014 serving from quota_`);
7605
+ }
7606
+ return lines;
7607
+ }
7608
+ function formatAgeStamp(atMs, now = new Date) {
7609
+ const ageSec = Math.max(0, Math.round((now.getTime() - atMs) / 1000));
7610
+ return ageSec < 60 ? `${ageSec}s ago` : `${Math.round(ageSec / 60)}m ago`;
7611
+ }
7612
+ function tableCell(s) {
7613
+ return s.replace(/\\/g, "\\\\").replace(/\|/g, "\\|").replace(/\r?\n/g, " ");
7614
+ }
7615
+ function pctCells(snap, now) {
7616
+ if (!snap.quota)
7617
+ return { five: "\u2014", seven: "\u2014" };
7618
+ if (isProbeThin(snap.quota))
7619
+ return { five: "?", seven: "?" };
7620
+ const norm = refillNormalizedUtils(snap.quota, now);
7621
+ return { five: fmtPct(norm.fiveHourUtilizationPct), seven: fmtPct(norm.sevenDayUtilizationPct) };
7622
+ }
7623
+ function windowResetCell(snap, now, tz, win) {
7624
+ if (!snap.quota) {
7625
+ return snap.quotaError ? `probe failed (${snap.quotaError})` : "\u2014";
7626
+ }
7627
+ if (isProbeThin(snap.quota))
7628
+ return "quota unknown";
7629
+ const reset = win === "5h" ? snap.quota.fiveHourResetAt : snap.quota.sevenDayResetAt;
7630
+ if (!reset)
7631
+ return "\u2014";
7632
+ return `${formatStatusTime(reset, now, tz)} (in ${formatRelative(reset, now)})`;
7633
+ }
7634
+ function renderAuthSnapshotFormat2(snapshots, opts = {}) {
7635
+ const now = opts.now ?? new Date;
7636
+ const tz = opts.tz ?? "UTC";
7637
+ const lines = [];
7638
+ lines.push("\uD83D\uDD0B **Auth \u2014 fleet status**");
7639
+ const ordered = [...snapshots].sort((a, b) => {
7640
+ if (a.isActive !== b.isActive)
7641
+ return a.isActive ? -1 : 1;
7642
+ const r = TABLE_HEALTH_RANK[classifyHealth(a, now)] - TABLE_HEALTH_RANK[classifyHealth(b, now)];
7643
+ if (r !== 0)
7644
+ return r;
7645
+ return a.label.localeCompare(b.label);
7646
+ });
7647
+ if (ordered.length > 0) {
7648
+ lines.push("");
7649
+ lines.push("| State | Account | 5h | 5h resets | 7d | 7d resets |");
7650
+ lines.push("| --- | --- | --- | --- | --- | --- |");
7651
+ for (const s of ordered) {
7652
+ const emoji = HEALTH_EMOJI[classifyHealth(s, now)];
7653
+ const label = displayLabel(s.label, opts);
7654
+ const accountCell = `\`${codeSpanSafe(s.isActive ? `${label} (active)` : label)}\``;
7655
+ const { five, seven } = pctCells(s, now);
7656
+ const fiveReset = windowResetCell(s, now, tz, "5h");
7657
+ const sevenReset = windowResetCell(s, now, tz, "7d");
7658
+ lines.push(`| ${emoji} | ${tableCell(accountCell)} | ${five} | ${tableCell(fiveReset)} | ${seven} | ${tableCell(sevenReset)} |`);
7659
+ }
7660
+ }
7661
+ lines.push("");
7662
+ lines.push(`_${recommendation(snapshots, now, opts.demo ?? false)}_`);
7663
+ if (opts.staleCachedAtMs != null) {
7664
+ lines.push(`_\u26a0 cached ${formatAgeStamp(opts.staleCachedAtMs, now)}_`);
7665
+ } else if (opts.liveProbedAtMs != null) {
7666
+ lines.push(`_Live \u00b7 refreshed ${formatAgeStamp(opts.liveProbedAtMs, now)}_`);
7667
+ } else if (opts.probeFailed) {
7668
+ lines.push("_\u26a0 probe failed \u2014 no live data_");
7669
+ } else {
7670
+ lines.push("_Live_");
7671
+ }
7672
+ return lines.join(`
7673
+ `);
7674
+ }
7675
+ function recommendation(snapshots, now = new Date, demo = false) {
7676
+ const active = snapshots.find((s) => s.isActive);
7677
+ if (!active)
7678
+ return "No active account set.";
7679
+ const activeHealth = classifyHealth(active, now);
7680
+ const inServiceFleet = snapshots.filter((s) => {
7681
+ const h = classifyHealth(s, now);
7682
+ return h !== "retired" && h !== "org-blocked";
7683
+ });
7684
+ const others = inServiceFleet.filter((s) => !s.isActive);
7685
+ const healthyAlt = others.find((s) => classifyHealth(s, now) === "healthy");
7686
+ const lbl = (s) => demo ? maskEmail(s.label) : s.label;
7687
+ const activeLabel = lbl(active);
7688
+ if (activeHealth === "healthy") {
7689
+ return `Recommendation: stay on ${activeLabel}.`;
7690
+ }
7691
+ if (activeHealth === "throttling") {
7692
+ if (healthyAlt) {
7693
+ return `Recommendation: active ${activeLabel} is throttling. Switch to ${lbl(healthyAlt)} for headroom.`;
7694
+ }
7695
+ return `Recommendation: active ${activeLabel} is throttling; no healthy alternative \u2014 wait for refill.`;
7696
+ }
7697
+ if (activeHealth === "blocked") {
7698
+ if (healthyAlt) {
7699
+ return `Recommendation: active ${activeLabel} is BLOCKED \u2014 switch to ${lbl(healthyAlt)} now.`;
7700
+ }
7701
+ return summarizeNoHealthyAlt(inServiceFleet, now, demo);
7702
+ }
7703
+ return `Active ${activeLabel}: quota probe failed; broker last_seen unknown.`;
7704
+ }
7705
+ function summarizeNoHealthyAlt(snapshots, now, demo = false) {
7706
+ const mask = (label) => demo ? maskEmail(label) : label;
7707
+ let throttlingLabel = null;
7708
+ let allTrulyBlocked = true;
7709
+ for (const s of snapshots) {
7710
+ const h = classifyHealth(s, now);
7711
+ if (h === "throttling") {
7712
+ if (!throttlingLabel)
7713
+ throttlingLabel = s.label;
7714
+ allTrulyBlocked = false;
7715
+ } else if (h === "healthy" || h === "unknown") {
7716
+ allTrulyBlocked = false;
7717
+ } else if (h === "blocked" && blockedReason(s, now) === "quota-exhausted") {
7718
+ if (s.quota) {
7719
+ const win = bindingWindow(s.quota);
7720
+ const at = win === "5h" ? s.quota.fiveHourResetAt : s.quota.sevenDayResetAt;
7721
+ if (at && at.getTime() > now.getTime())
7722
+ allTrulyBlocked = false;
7723
+ }
7724
+ }
7725
+ }
7726
+ const earliestRecovery = pickEarliestRecovery(snapshots, now);
7727
+ if (throttlingLabel) {
7728
+ const eta = earliestRecovery ? ` Soonest full refill: ${mask(earliestRecovery.label)} in ${formatRelative(earliestRecovery.at, now)}.` : "";
7729
+ return `No fully-healthy account; ${mask(throttlingLabel)} is throttling but still usable.${eta}`;
7730
+ }
7731
+ if (!allTrulyBlocked) {
7732
+ if (earliestRecovery) {
7733
+ return `All accounts at capacity; soonest refill: ${mask(earliestRecovery.label)} in ${formatRelative(earliestRecovery.at, now)}.`;
7734
+ }
7735
+ return `All accounts at capacity \u2014 waiting on a window refill.`;
7736
+ }
7737
+ if (earliestRecovery) {
7738
+ return `All accounts blocked. Earliest recovery: ${mask(earliestRecovery.label)} in ${formatRelative(earliestRecovery.at, now)}.`;
7739
+ }
7740
+ return `All accounts blocked. Run /auth add to attach another subscription.`;
7741
+ }
7742
+ function pickEarliestRecovery(snapshots, now) {
7743
+ let best = null;
7744
+ for (const s of snapshots) {
7745
+ if (!s.quota)
7746
+ continue;
7747
+ if (isProbeThin(s.quota))
7748
+ continue;
7749
+ const win = bindingWindow(s.quota);
7750
+ const at = win === "5h" ? s.quota.fiveHourResetAt : s.quota.sevenDayResetAt;
7751
+ if (!at || at.getTime() <= now.getTime())
7752
+ continue;
7753
+ if (!best || at.getTime() < best.at.getTime()) {
7754
+ best = { label: s.label, at };
7755
+ }
7756
+ }
7757
+ return best;
7758
+ }
7759
+ function renderFallbackAnnouncement(input) {
7760
+ const now = input.now ?? new Date;
7761
+ const tz = input.tz ?? "UTC";
7762
+ const lines = [];
7763
+ const limitWord = input.oldQuota ? limitWordFor(input.oldQuota) : "quota";
7764
+ const headerLimit = input.cause === "rate-limit" ? "rate limit" : limitWord === "quota" ? "quota cap" : `${limitWord} limit`;
7765
+ if (!input.newLabel) {
7766
+ lines.push(`\uD83D\uDD34 **All accounts blocked \u00b7 ${headerLimit} on ${escapeMarkdown(input.oldLabel)}**`);
7767
+ lines.push("");
7768
+ lines.push(`Triggered by: agent **${escapeMarkdown(input.triggerAgent)}**`);
7769
+ const fleet = input.fleetSnapshots ?? [];
7770
+ if (fleet.length > 0) {
7771
+ lines.push("");
7772
+ const rowOpts = { now, tz };
7773
+ const healthOrder = [
7774
+ "org-blocked",
7775
+ "blocked",
7776
+ "throttling",
7777
+ "healthy",
7778
+ "unknown",
7779
+ "retired"
7780
+ ];
7781
+ const rank = (s) => healthOrder.indexOf(classifyHealth(s, now));
7782
+ const ordered = [...fleet].sort((a, b) => rank(a) - rank(b) || Number(b.isActive) - Number(a.isActive));
7783
+ for (const snap of ordered) {
7784
+ for (const ln of renderAccountRow(snap, rowOpts))
7785
+ lines.push(ln);
7786
+ }
7787
+ const earliest = pickEarliestRecovery(fleet, now);
7788
+ if (earliest) {
7789
+ lines.push("");
7790
+ lines.push(`Earliest recovery: \`${codeSpanSafe(earliest.label)}\` ` + `${formatAbsolute(earliest.at, tz)} (in ${formatRelative(earliest.at, now)})`);
7791
+ }
7792
+ } else {
7793
+ const recovery = (input.oldQuota ? recoveryAtFor(input.oldQuota) : null) ?? input.parsedResetAt ?? null;
7794
+ if (recovery) {
7795
+ lines.push(`${escapeMarkdown(input.oldLabel)} recovers ${formatAbsolute(recovery, tz)} ` + `(in ${formatRelative(recovery, now)})`);
7796
+ }
7797
+ }
7798
+ lines.push("");
7799
+ lines.push(`Run \`/auth add <label>\` to attach another subscription, ` + `or \`/auth refresh\` to re-probe.`);
7800
+ return lines.join(`
7801
+ `);
7802
+ }
7803
+ lines.push(`\u2713 **Switched fleet \u00b7 ${headerLimit} on ${escapeMarkdown(input.oldLabel)}**`);
7804
+ lines.push("");
7805
+ lines.push(`\`${codeSpanSafe(input.oldLabel)}\` \u2192 \`${codeSpanSafe(input.newLabel)}\``);
7806
+ lines.push(`Triggered by: agent **${escapeMarkdown(input.triggerAgent)}**`);
7807
+ lines.push("");
7808
+ {
7809
+ const recovery = (input.oldQuota ? recoveryAtFor(input.oldQuota) : null) ?? input.parsedResetAt ?? null;
7810
+ if (recovery) {
7811
+ lines.push(`\`${codeSpanSafe(input.oldLabel)}\` recovers ` + `${formatAbsolute(recovery, tz)} (in ${formatRelative(recovery, now)})`);
7812
+ }
7813
+ }
7814
+ if (input.newQuota) {
7815
+ const fiveStr = fmtPct(input.newQuota.fiveHourUtilizationPct);
7816
+ const sevenStr = fmtPct(input.newQuota.sevenDayUtilizationPct);
7817
+ const hasHeadroom = input.newQuota.fiveHourUtilizationPct < THROTTLING_THRESHOLD_PCT && input.newQuota.sevenDayUtilizationPct < THROTTLING_THRESHOLD_PCT;
7818
+ const headroomStr = hasHeadroom ? "_(plenty of headroom)_" : "_(near limit \u2014 watch this)_";
7819
+ lines.push(`\`${codeSpanSafe(input.newLabel)}\` now: ${fiveStr} of 5h \u00b7 ${sevenStr} of 7d ${headroomStr}`);
7820
+ } else {
7821
+ lines.push(`_(quota probe for new account is pending \u2014 will reflect on next /auth)_`);
7822
+ }
7823
+ return lines.join(`
7824
+ `);
7825
+ }
7826
+ function limitWordFor(q) {
7827
+ if (q.representativeClaim === "seven_day" && q.sevenDayUtilizationPct >= 99)
7828
+ return "7-day";
7829
+ if (q.representativeClaim === "five_hour" && q.fiveHourUtilizationPct >= 99)
7830
+ return "5-hour";
7831
+ if (q.sevenDayUtilizationPct >= 99)
7832
+ return "7-day";
7833
+ if (q.fiveHourUtilizationPct >= 99)
7834
+ return "5-hour";
7835
+ return q.sevenDayUtilizationPct >= q.fiveHourUtilizationPct ? "7-day" : "5-hour";
7836
+ }
7837
+ function recoveryAtFor(q) {
7838
+ const word = limitWordFor(q);
7839
+ if (word === "7-day")
7840
+ return q.sevenDayResetAt;
7841
+ if (word === "5-hour")
7842
+ return q.fiveHourResetAt;
7843
+ if (!q.fiveHourResetAt)
7844
+ return q.sevenDayResetAt;
7845
+ if (!q.sevenDayResetAt)
7846
+ return q.fiveHourResetAt;
7847
+ return q.fiveHourResetAt.getTime() < q.sevenDayResetAt.getTime() ? q.fiveHourResetAt : q.sevenDayResetAt;
7848
+ }
7849
+ function buildSnapshotKeyboard(snapshots, opts = {}) {
7850
+ const max = opts.maxSwitchButtons ?? 3;
7851
+ const now = opts.now ?? new Date;
7852
+ const rows = [];
7853
+ const switchTargets = snapshots.filter((s) => !s.isActive).sort((a, b) => switchPriority(a, now) - switchPriority(b, now)).filter((s) => {
7854
+ const h = classifyHealth(s, now);
7855
+ return h !== "blocked" && h !== "unknown" && h !== "retired" && h !== "org-blocked";
7856
+ }).slice(0, max);
7857
+ for (const t of switchTargets) {
7858
+ rows.push([
7859
+ {
7860
+ text: `Switch fleet \u2192 ${opts.demo ? maskEmail(t.label) : t.label}`,
7861
+ callbackData: `auth:use:${t.label}`
7862
+ }
7863
+ ]);
7864
+ }
7865
+ rows.push([
7866
+ { text: "\u21bb Refresh", callbackData: opts.demo ? "auth:refresh:demo" : "auth:refresh" },
7867
+ { text: "/usage", insertText: "/usage" },
7868
+ { text: "+ Add", insertText: "/auth add " }
7869
+ ]);
7870
+ return rows;
7871
+ }
7872
+ function switchPriority(s, now = new Date) {
7873
+ const h = classifyHealth(s, now);
7874
+ if (h === "healthy")
7875
+ return 0;
7876
+ if (h === "throttling")
7877
+ return 1;
7878
+ if (h === "unknown")
7879
+ return 2;
7880
+ if (h === "blocked")
7881
+ return 3;
7882
+ return 4;
7883
+ }
7884
+ function zipProbeResults(labels, results) {
7885
+ let staleCachedAtMs;
7886
+ const quotas = labels.map((label) => {
7887
+ const hit = results.find((r) => r.label === label);
7888
+ if (!hit)
7889
+ return { ok: false, reason: "broker returned no result for account" };
7890
+ if (hit.served === "cache" && hit.capturedAt != null) {
7891
+ staleCachedAtMs = staleCachedAtMs == null ? hit.capturedAt : Math.min(staleCachedAtMs, hit.capturedAt);
7892
+ }
7893
+ return hit.result;
7894
+ });
7895
+ return staleCachedAtMs != null ? { quotas, staleCachedAtMs } : { quotas };
7896
+ }
7897
+ function deriveUsageFooterFreshness(results, staleCachedAtMs, liveProbedAtMs) {
7898
+ if (staleCachedAtMs != null)
7899
+ return { staleCachedAtMs };
7900
+ if (results.some((r) => r.result.ok))
7901
+ return { liveProbedAtMs };
7902
+ return { probeFailed: true };
7903
+ }
7904
+ function buildSnapshotsFromState(state, quotas) {
7905
+ const out = [];
7906
+ for (let i = 0;i < state.accounts.length; i++) {
7907
+ const acc = state.accounts[i];
7908
+ const q = quotas[i];
7909
+ out.push({
7910
+ label: acc.label,
7911
+ isActive: acc.label === state.active,
7912
+ quota: q && q.ok ? q.data : null,
7913
+ quotaError: q && !q.ok ? q.reason : undefined,
7914
+ expiresAtMs: acc.expiresAt,
7915
+ inService: acc.in_service,
7916
+ entitlementBlocked: acc.entitlement_blocked
7917
+ });
7918
+ }
7919
+ return out;
7920
+ }
7921
+ function reviveLastQuota(snap) {
7922
+ if (!snap)
7923
+ return null;
7924
+ return {
7925
+ fiveHourUtilizationPct: snap.fiveHourUtilizationPct,
7926
+ sevenDayUtilizationPct: snap.sevenDayUtilizationPct,
7927
+ fiveHourResetAt: snap.fiveHourResetAt ? new Date(snap.fiveHourResetAt) : null,
7928
+ sevenDayResetAt: snap.sevenDayResetAt ? new Date(snap.sevenDayResetAt) : null,
7929
+ representativeClaim: snap.representativeClaim,
7930
+ overageStatus: snap.overageStatus,
7931
+ overageDisabledReason: snap.overageDisabledReason,
7932
+ fiveHourUtilPresent: snap.fiveHourUtilPresent,
7933
+ sevenDayUtilPresent: snap.sevenDayUtilPresent
7934
+ };
7935
+ }
7936
+ function buildSnapshotsFromCachedState(state) {
7937
+ return state.accounts.map((acc) => {
7938
+ const lq = acc.last_quota ?? null;
7939
+ return {
7940
+ label: acc.label,
7941
+ isActive: acc.label === state.active,
7942
+ quota: reviveLastQuota(lq),
7943
+ quotaError: lq ? undefined : "no cached quota (no probe since broker start)",
7944
+ expiresAtMs: acc.expiresAt,
7945
+ capturedAtMs: lq?.capturedAt,
7946
+ inService: acc.in_service,
7947
+ entitlementBlocked: acc.entitlement_blocked
7948
+ };
7949
+ });
7950
+ }
7951
+ var THROTTLING_THRESHOLD_PCT = 80, OVERAGE_EXHAUSTED_REASONS, HEALTH_EMOJI, TABLE_HEALTH_RANK;
7952
+ var init_auth_snapshot_format = __esm(() => {
7953
+ init_demo_mask();
7954
+ init_card_format();
7955
+ OVERAGE_EXHAUSTED_REASONS = new Set(["out_of_credits"]);
7956
+ HEALTH_EMOJI = {
7957
+ healthy: "\uD83D\uDFE2",
7958
+ throttling: "\uD83D\uDFE1",
7959
+ blocked: "\uD83D\uDD34",
7960
+ unknown: "\u26aa",
7961
+ "org-blocked": "\u26d4",
7962
+ retired: "\u26ab"
7963
+ };
7964
+ TABLE_HEALTH_RANK = {
7965
+ "org-blocked": 0,
7966
+ blocked: 1,
7967
+ throttling: 2,
7968
+ unknown: 3,
7969
+ healthy: 4,
7970
+ retired: 5
7971
+ };
7972
+ });
7973
+
7386
7974
  // secret-detect/patterns.ts
7387
7975
  var ANCHORED_PATTERNS, STRUCTURED_PATTERNS, PROVIDER_PATTERNS, ALL_PATTERNS;
7388
7976
  var init_patterns = __esm(() => {
@@ -13034,51 +13622,6 @@ var init_tmux = __esm(() => {
13034
13622
  MAX_BYTES = 10 * 1024 * 1024;
13035
13623
  });
13036
13624
 
13037
- // demo-mask.ts
13038
- function stableAssign(input, cache, pool, overflow) {
13039
- const existing = cache.get(input);
13040
- if (existing !== undefined)
13041
- return existing;
13042
- const index = cache.size;
13043
- const value = index < pool.length ? pool[index] : overflow(index);
13044
- cache.set(input, value);
13045
- return value;
13046
- }
13047
- function maskEmail(label) {
13048
- return stableAssign(label, emailCache, EMAIL_POOL, (i) => `demo${i + 1}@example.com`);
13049
- }
13050
- function maskUsername(tag) {
13051
- return stableAssign(tag, usernameCache, USERNAME_POOL, (i) => `@demo_user${i + 1}`);
13052
- }
13053
- function maskVaultKey(name) {
13054
- return stableAssign(name, vaultKeyCache, [], (i) => `demo/secret-${i + 1}`);
13055
- }
13056
- var EMAIL_POOL, emailCache, USERNAME_POOL, usernameCache, vaultKeyCache;
13057
- var init_demo_mask = __esm(() => {
13058
- EMAIL_POOL = [
13059
- "ada@example.com",
13060
- "grace@example.com",
13061
- "linus@example.com",
13062
- "hopper@example.com",
13063
- "turing@example.com",
13064
- "lovelace@example.com",
13065
- "dijkstra@example.com",
13066
- "knuth@example.com",
13067
- "ritchie@example.com",
13068
- "torvalds@example.com"
13069
- ];
13070
- emailCache = new Map;
13071
- USERNAME_POOL = [
13072
- "@demo_user",
13073
- "@demo_user2",
13074
- "@demo_user3",
13075
- "@demo_user4",
13076
- "@demo_user5"
13077
- ];
13078
- usernameCache = new Map;
13079
- vaultKeyCache = new Map;
13080
- });
13081
-
13082
13625
  // ../node_modules/.bun/yaml@2.8.3/node_modules/yaml/dist/nodes/identity.js
13083
13626
  var require_identity = __commonJS((exports) => {
13084
13627
  var ALIAS = Symbol.for("yaml.alias");
@@ -22029,506 +22572,6 @@ var init_resolver = __esm(() => {
22029
22572
  materializedDirs = new Set;
22030
22573
  });
22031
22574
 
22032
- // ../src/auth/quota.ts
22033
- function isProbeThin(q) {
22034
- return q.fiveHourUtilPresent === false && q.sevenDayUtilPresent === false;
22035
- }
22036
- function refillNormalizedUtils(q, now) {
22037
- const nowMs = now.getTime();
22038
- const fiveHourRefilled = q.fiveHourResetAt != null && q.fiveHourResetAt.getTime() <= nowMs;
22039
- const sevenDayRefilled = q.sevenDayResetAt != null && q.sevenDayResetAt.getTime() <= nowMs;
22040
- return {
22041
- fiveHourUtilizationPct: fiveHourRefilled ? 0 : q.fiveHourUtilizationPct,
22042
- sevenDayUtilizationPct: sevenDayRefilled ? 0 : q.sevenDayUtilizationPct,
22043
- fiveHourRefilled,
22044
- sevenDayRefilled
22045
- };
22046
- }
22047
-
22048
- // auth-snapshot-format.ts
22049
- var exports_auth_snapshot_format = {};
22050
- __export(exports_auth_snapshot_format, {
22051
- zipProbeResults: () => zipProbeResults,
22052
- reviveLastQuota: () => reviveLastQuota,
22053
- renderFallbackAnnouncement: () => renderFallbackAnnouncement,
22054
- renderAuthSnapshotFormat2: () => renderAuthSnapshotFormat2,
22055
- recommendation: () => recommendation,
22056
- formatStatusTime: () => formatStatusTime,
22057
- formatRelative: () => formatRelative,
22058
- formatAbsolute: () => formatAbsolute,
22059
- fmtPct: () => fmtPct,
22060
- classifyHealth: () => classifyHealth,
22061
- buildSnapshotsFromState: () => buildSnapshotsFromState,
22062
- buildSnapshotsFromCachedState: () => buildSnapshotsFromCachedState,
22063
- buildSnapshotKeyboard: () => buildSnapshotKeyboard,
22064
- blockedReason: () => blockedReason,
22065
- bindingWindow: () => bindingWindow,
22066
- THROTTLING_THRESHOLD_PCT: () => THROTTLING_THRESHOLD_PCT
22067
- });
22068
- function classifyHealth(snap, now = new Date) {
22069
- if (!snap.quota)
22070
- return "unknown";
22071
- const q = snap.quota;
22072
- if (isProbeThin(q))
22073
- return "unknown";
22074
- const norm = refillNormalizedUtils(q, now);
22075
- const max = Math.max(norm.fiveHourUtilizationPct, norm.sevenDayUtilizationPct);
22076
- if (max >= 99.5)
22077
- return "blocked";
22078
- if (max >= THROTTLING_THRESHOLD_PCT)
22079
- return "throttling";
22080
- return "healthy";
22081
- }
22082
- function blockedReason(snap, now = new Date) {
22083
- if (classifyHealth(snap, now) !== "blocked")
22084
- return null;
22085
- return "quota-exhausted";
22086
- }
22087
- function bindingWindow(q) {
22088
- if (q.representativeClaim === "seven_day")
22089
- return "7d";
22090
- if (q.representativeClaim === "five_hour")
22091
- return "5h";
22092
- return q.sevenDayUtilizationPct >= q.fiveHourUtilizationPct ? "7d" : "5h";
22093
- }
22094
- function formatRelative(target, now = new Date) {
22095
- if (!target)
22096
- return "\u2014";
22097
- const deltaMs = target.getTime() - now.getTime();
22098
- if (deltaMs <= 0)
22099
- return "now";
22100
- const totalMin = Math.round(deltaMs / 60000);
22101
- if (totalMin < 60)
22102
- return `${totalMin}m`;
22103
- const h = Math.floor(totalMin / 60);
22104
- const m = totalMin % 60;
22105
- if (h < 24)
22106
- return m > 0 ? `${h}h ${m}m` : `${h}h`;
22107
- const d = Math.floor(h / 24);
22108
- const rh = h % 24;
22109
- return rh > 0 ? `${d}d ${rh}h` : `${d}d`;
22110
- }
22111
- function formatAbsolute(target, tz = "UTC") {
22112
- if (!target)
22113
- return "\u2014";
22114
- return target.toLocaleString("en-US", {
22115
- timeZone: tz,
22116
- weekday: "short",
22117
- hour: "numeric",
22118
- minute: "2-digit",
22119
- hour12: true
22120
- });
22121
- }
22122
- function formatStatusTime(target, now = new Date, tz = "UTC") {
22123
- if (!target)
22124
- return "\u2014";
22125
- const dayFmt = new Intl.DateTimeFormat("en-CA", {
22126
- timeZone: tz,
22127
- year: "numeric",
22128
- month: "2-digit",
22129
- day: "2-digit"
22130
- });
22131
- const timeFmt = new Intl.DateTimeFormat("en-US", {
22132
- timeZone: tz,
22133
- hour: "numeric",
22134
- minute: "2-digit",
22135
- hour12: true
22136
- });
22137
- const timeStr = timeFmt.format(target);
22138
- if (dayFmt.format(target) === dayFmt.format(now)) {
22139
- return timeStr;
22140
- }
22141
- const withinWeek = target.getTime() - now.getTime() < 7 * 24 * 60 * 60 * 1000;
22142
- const dateFmt = new Intl.DateTimeFormat("en-US", {
22143
- timeZone: tz,
22144
- weekday: "short",
22145
- ...withinWeek ? {} : { day: "numeric", month: "short" }
22146
- });
22147
- return `${dateFmt.format(target)} ${timeStr}`;
22148
- }
22149
- function fmtPct(pct) {
22150
- return `${Math.min(100, Math.round(pct))}%`;
22151
- }
22152
- function displayLabel(label, opts) {
22153
- return opts.demo ? maskEmail(label) : label;
22154
- }
22155
- function renderAccountRow(snap, opts) {
22156
- const now = opts.now ?? new Date;
22157
- const tz = opts.tz ?? "UTC";
22158
- const lines = [];
22159
- const marker = snap.isActive ? "\u25cf " : "";
22160
- const label = displayLabel(snap.label, opts);
22161
- if (!snap.quota) {
22162
- lines.push(`${marker}\`${codeSpanSafe(label)}\` _quota probe failed_`);
22163
- if (snap.quotaError) {
22164
- lines.push(` _${escapeMarkdown(snap.quotaError)}_`);
22165
- }
22166
- return lines;
22167
- }
22168
- const q = snap.quota;
22169
- if (isProbeThin(q)) {
22170
- lines.push(`${marker}\`${codeSpanSafe(label)}\` _quota unknown (thin probe)_`);
22171
- return lines;
22172
- }
22173
- const norm = refillNormalizedUtils(q, now);
22174
- const fiveStr = fmtPct(norm.fiveHourUtilizationPct);
22175
- const sevenStr = fmtPct(norm.sevenDayUtilizationPct);
22176
- lines.push(`${marker}\`${codeSpanSafe(label)}\` ${fiveStr} / ${sevenStr}`);
22177
- const health = classifyHealth(snap, now);
22178
- if (health === "blocked") {
22179
- const win = bindingWindow(q);
22180
- const reset = win === "5h" ? q.fiveHourResetAt : q.sevenDayResetAt;
22181
- const winLabel = win === "5h" ? "5-hour" : "7-day";
22182
- lines.push(reset ? ` _quota exhausted \u2014 back ${formatAbsolute(reset, tz)} (\`in ${formatRelative(reset, now)}\`, ${winLabel} cap)_` : ` _quota exhausted \u2014 ${winLabel} cap, reset time unknown_`);
22183
- return lines;
22184
- }
22185
- const fiveResetIn = q.fiveHourResetAt ? q.fiveHourResetAt.getTime() - now.getTime() : Infinity;
22186
- const sevenResetIn = q.sevenDayResetAt ? q.sevenDayResetAt.getTime() - now.getTime() : Infinity;
22187
- const fiveFirst = fiveResetIn <= sevenResetIn;
22188
- const fiveSeg = q.fiveHourResetAt ? `5h refills ${formatAbsolute(q.fiveHourResetAt, tz)} (\`in ${formatRelative(q.fiveHourResetAt, now)}\`)` : "5h refills \u2014";
22189
- const sevenSeg = q.sevenDayResetAt ? `7d resets ${formatAbsolute(q.sevenDayResetAt, tz)} (\`in ${formatRelative(q.sevenDayResetAt, now)}\`)` : "7d resets \u2014";
22190
- lines.push(` _${fiveFirst ? fiveSeg : sevenSeg}_`);
22191
- lines.push(` _${fiveFirst ? sevenSeg : fiveSeg}_`);
22192
- if (q.overageDisabledReason != null && OVERAGE_EXHAUSTED_REASONS.has(q.overageDisabledReason)) {
22193
- lines.push(` _overage off (${escapeMarkdown(q.overageDisabledReason)}) \u2014 serving from quota_`);
22194
- }
22195
- return lines;
22196
- }
22197
- function formatAgeStamp(atMs, now = new Date) {
22198
- const ageSec = Math.max(0, Math.round((now.getTime() - atMs) / 1000));
22199
- return ageSec < 60 ? `${ageSec}s ago` : `${Math.round(ageSec / 60)}m ago`;
22200
- }
22201
- function tableCell(s) {
22202
- return s.replace(/\\/g, "\\\\").replace(/\|/g, "\\|").replace(/\r?\n/g, " ");
22203
- }
22204
- function pctCells(snap, now) {
22205
- if (!snap.quota)
22206
- return { five: "\u2014", seven: "\u2014" };
22207
- if (isProbeThin(snap.quota))
22208
- return { five: "?", seven: "?" };
22209
- const norm = refillNormalizedUtils(snap.quota, now);
22210
- return { five: fmtPct(norm.fiveHourUtilizationPct), seven: fmtPct(norm.sevenDayUtilizationPct) };
22211
- }
22212
- function windowResetCell(snap, now, tz, win) {
22213
- if (!snap.quota) {
22214
- return snap.quotaError ? `probe failed (${snap.quotaError})` : "\u2014";
22215
- }
22216
- if (isProbeThin(snap.quota))
22217
- return "quota unknown";
22218
- const reset = win === "5h" ? snap.quota.fiveHourResetAt : snap.quota.sevenDayResetAt;
22219
- if (!reset)
22220
- return "\u2014";
22221
- return `${formatStatusTime(reset, now, tz)} (in ${formatRelative(reset, now)})`;
22222
- }
22223
- function renderAuthSnapshotFormat2(snapshots, opts = {}) {
22224
- const now = opts.now ?? new Date;
22225
- const tz = opts.tz ?? "UTC";
22226
- const lines = [];
22227
- lines.push("\uD83D\uDD0B **Auth \u2014 fleet status**");
22228
- const ordered = [...snapshots].sort((a, b) => {
22229
- if (a.isActive !== b.isActive)
22230
- return a.isActive ? -1 : 1;
22231
- const r = TABLE_HEALTH_RANK[classifyHealth(a, now)] - TABLE_HEALTH_RANK[classifyHealth(b, now)];
22232
- if (r !== 0)
22233
- return r;
22234
- return a.label.localeCompare(b.label);
22235
- });
22236
- if (ordered.length > 0) {
22237
- lines.push("");
22238
- lines.push("| State | Account | 5h | 5h resets | 7d | 7d resets |");
22239
- lines.push("| --- | --- | --- | --- | --- | --- |");
22240
- for (const s of ordered) {
22241
- const emoji = HEALTH_EMOJI[classifyHealth(s, now)];
22242
- const label = displayLabel(s.label, opts);
22243
- const accountCell = `\`${codeSpanSafe(s.isActive ? `${label} (active)` : label)}\``;
22244
- const { five, seven } = pctCells(s, now);
22245
- const fiveReset = windowResetCell(s, now, tz, "5h");
22246
- const sevenReset = windowResetCell(s, now, tz, "7d");
22247
- lines.push(`| ${emoji} | ${tableCell(accountCell)} | ${five} | ${tableCell(fiveReset)} | ${seven} | ${tableCell(sevenReset)} |`);
22248
- }
22249
- }
22250
- lines.push("");
22251
- lines.push(`_${recommendation(snapshots, now, opts.demo ?? false)}_`);
22252
- if (opts.staleCachedAtMs != null) {
22253
- lines.push(`_\u26a0 cached ${formatAgeStamp(opts.staleCachedAtMs, now)}_`);
22254
- } else if (opts.liveProbedAtMs != null) {
22255
- lines.push(`_Live \u00b7 refreshed ${formatAgeStamp(opts.liveProbedAtMs, now)}_`);
22256
- } else if (opts.probeFailed) {
22257
- lines.push("_\u26a0 probe failed \u2014 no live data_");
22258
- } else {
22259
- lines.push("_Live_");
22260
- }
22261
- return lines.join(`
22262
- `);
22263
- }
22264
- function recommendation(snapshots, now = new Date, demo = false) {
22265
- const active = snapshots.find((s) => s.isActive);
22266
- if (!active)
22267
- return "No active account set.";
22268
- const activeHealth = classifyHealth(active, now);
22269
- const others = snapshots.filter((s) => !s.isActive);
22270
- const healthyAlt = others.find((s) => classifyHealth(s, now) === "healthy");
22271
- const lbl = (s) => demo ? maskEmail(s.label) : s.label;
22272
- const activeLabel = lbl(active);
22273
- if (activeHealth === "healthy") {
22274
- return `Recommendation: stay on ${activeLabel}.`;
22275
- }
22276
- if (activeHealth === "throttling") {
22277
- if (healthyAlt) {
22278
- return `Recommendation: active ${activeLabel} is throttling. Switch to ${lbl(healthyAlt)} for headroom.`;
22279
- }
22280
- return `Recommendation: active ${activeLabel} is throttling; no healthy alternative \u2014 wait for refill.`;
22281
- }
22282
- if (activeHealth === "blocked") {
22283
- if (healthyAlt) {
22284
- return `Recommendation: active ${activeLabel} is BLOCKED \u2014 switch to ${lbl(healthyAlt)} now.`;
22285
- }
22286
- return summarizeNoHealthyAlt(snapshots, now, demo);
22287
- }
22288
- return `Active ${activeLabel}: quota probe failed; broker last_seen unknown.`;
22289
- }
22290
- function summarizeNoHealthyAlt(snapshots, now, demo = false) {
22291
- const mask = (label) => demo ? maskEmail(label) : label;
22292
- let throttlingLabel = null;
22293
- let allTrulyBlocked = true;
22294
- for (const s of snapshots) {
22295
- const h = classifyHealth(s, now);
22296
- if (h === "throttling") {
22297
- if (!throttlingLabel)
22298
- throttlingLabel = s.label;
22299
- allTrulyBlocked = false;
22300
- } else if (h === "healthy" || h === "unknown") {
22301
- allTrulyBlocked = false;
22302
- } else if (h === "blocked" && blockedReason(s, now) === "quota-exhausted") {
22303
- if (s.quota) {
22304
- const win = bindingWindow(s.quota);
22305
- const at = win === "5h" ? s.quota.fiveHourResetAt : s.quota.sevenDayResetAt;
22306
- if (at && at.getTime() > now.getTime())
22307
- allTrulyBlocked = false;
22308
- }
22309
- }
22310
- }
22311
- const earliestRecovery = pickEarliestRecovery(snapshots, now);
22312
- if (throttlingLabel) {
22313
- const eta = earliestRecovery ? ` Soonest full refill: ${mask(earliestRecovery.label)} in ${formatRelative(earliestRecovery.at, now)}.` : "";
22314
- return `No fully-healthy account; ${mask(throttlingLabel)} is throttling but still usable.${eta}`;
22315
- }
22316
- if (!allTrulyBlocked) {
22317
- if (earliestRecovery) {
22318
- return `All accounts at capacity; soonest refill: ${mask(earliestRecovery.label)} in ${formatRelative(earliestRecovery.at, now)}.`;
22319
- }
22320
- return `All accounts at capacity \u2014 waiting on a window refill.`;
22321
- }
22322
- if (earliestRecovery) {
22323
- return `All accounts blocked. Earliest recovery: ${mask(earliestRecovery.label)} in ${formatRelative(earliestRecovery.at, now)}.`;
22324
- }
22325
- return `All accounts blocked. Run /auth add to attach another subscription.`;
22326
- }
22327
- function pickEarliestRecovery(snapshots, now) {
22328
- let best = null;
22329
- for (const s of snapshots) {
22330
- if (!s.quota)
22331
- continue;
22332
- if (isProbeThin(s.quota))
22333
- continue;
22334
- const win = bindingWindow(s.quota);
22335
- const at = win === "5h" ? s.quota.fiveHourResetAt : s.quota.sevenDayResetAt;
22336
- if (!at || at.getTime() <= now.getTime())
22337
- continue;
22338
- if (!best || at.getTime() < best.at.getTime()) {
22339
- best = { label: s.label, at };
22340
- }
22341
- }
22342
- return best;
22343
- }
22344
- function renderFallbackAnnouncement(input) {
22345
- const now = input.now ?? new Date;
22346
- const tz = input.tz ?? "UTC";
22347
- const lines = [];
22348
- const limitWord = input.oldQuota ? limitWordFor(input.oldQuota) : "quota";
22349
- const headerLimit = input.cause === "rate-limit" ? "rate limit" : limitWord === "quota" ? "quota cap" : `${limitWord} limit`;
22350
- if (!input.newLabel) {
22351
- lines.push(`\uD83D\uDD34 **All accounts blocked \u00b7 ${headerLimit} on ${escapeMarkdown(input.oldLabel)}**`);
22352
- lines.push("");
22353
- lines.push(`Triggered by: agent **${escapeMarkdown(input.triggerAgent)}**`);
22354
- const fleet = input.fleetSnapshots ?? [];
22355
- if (fleet.length > 0) {
22356
- lines.push("");
22357
- const rowOpts = { now, tz };
22358
- const healthOrder = ["blocked", "throttling", "healthy", "unknown"];
22359
- const rank = (s) => healthOrder.indexOf(classifyHealth(s, now));
22360
- const ordered = [...fleet].sort((a, b) => rank(a) - rank(b) || Number(b.isActive) - Number(a.isActive));
22361
- for (const snap of ordered) {
22362
- for (const ln of renderAccountRow(snap, rowOpts))
22363
- lines.push(ln);
22364
- }
22365
- const earliest = pickEarliestRecovery(fleet, now);
22366
- if (earliest) {
22367
- lines.push("");
22368
- lines.push(`Earliest recovery: \`${codeSpanSafe(earliest.label)}\` ` + `${formatAbsolute(earliest.at, tz)} (in ${formatRelative(earliest.at, now)})`);
22369
- }
22370
- } else {
22371
- const recovery = (input.oldQuota ? recoveryAtFor(input.oldQuota) : null) ?? input.parsedResetAt ?? null;
22372
- if (recovery) {
22373
- lines.push(`${escapeMarkdown(input.oldLabel)} recovers ${formatAbsolute(recovery, tz)} ` + `(in ${formatRelative(recovery, now)})`);
22374
- }
22375
- }
22376
- lines.push("");
22377
- lines.push(`Run \`/auth add <label>\` to attach another subscription, ` + `or \`/auth refresh\` to re-probe.`);
22378
- return lines.join(`
22379
- `);
22380
- }
22381
- lines.push(`\u2713 **Switched fleet \u00b7 ${headerLimit} on ${escapeMarkdown(input.oldLabel)}**`);
22382
- lines.push("");
22383
- lines.push(`\`${codeSpanSafe(input.oldLabel)}\` \u2192 \`${codeSpanSafe(input.newLabel)}\``);
22384
- lines.push(`Triggered by: agent **${escapeMarkdown(input.triggerAgent)}**`);
22385
- lines.push("");
22386
- {
22387
- const recovery = (input.oldQuota ? recoveryAtFor(input.oldQuota) : null) ?? input.parsedResetAt ?? null;
22388
- if (recovery) {
22389
- lines.push(`\`${codeSpanSafe(input.oldLabel)}\` recovers ` + `${formatAbsolute(recovery, tz)} (in ${formatRelative(recovery, now)})`);
22390
- }
22391
- }
22392
- if (input.newQuota) {
22393
- const fiveStr = fmtPct(input.newQuota.fiveHourUtilizationPct);
22394
- const sevenStr = fmtPct(input.newQuota.sevenDayUtilizationPct);
22395
- const hasHeadroom = input.newQuota.fiveHourUtilizationPct < THROTTLING_THRESHOLD_PCT && input.newQuota.sevenDayUtilizationPct < THROTTLING_THRESHOLD_PCT;
22396
- const headroomStr = hasHeadroom ? "_(plenty of headroom)_" : "_(near limit \u2014 watch this)_";
22397
- lines.push(`\`${codeSpanSafe(input.newLabel)}\` now: ${fiveStr} of 5h \u00b7 ${sevenStr} of 7d ${headroomStr}`);
22398
- } else {
22399
- lines.push(`_(quota probe for new account is pending \u2014 will reflect on next /auth)_`);
22400
- }
22401
- return lines.join(`
22402
- `);
22403
- }
22404
- function limitWordFor(q) {
22405
- if (q.representativeClaim === "seven_day" && q.sevenDayUtilizationPct >= 99)
22406
- return "7-day";
22407
- if (q.representativeClaim === "five_hour" && q.fiveHourUtilizationPct >= 99)
22408
- return "5-hour";
22409
- if (q.sevenDayUtilizationPct >= 99)
22410
- return "7-day";
22411
- if (q.fiveHourUtilizationPct >= 99)
22412
- return "5-hour";
22413
- return q.sevenDayUtilizationPct >= q.fiveHourUtilizationPct ? "7-day" : "5-hour";
22414
- }
22415
- function recoveryAtFor(q) {
22416
- const word = limitWordFor(q);
22417
- if (word === "7-day")
22418
- return q.sevenDayResetAt;
22419
- if (word === "5-hour")
22420
- return q.fiveHourResetAt;
22421
- if (!q.fiveHourResetAt)
22422
- return q.sevenDayResetAt;
22423
- if (!q.sevenDayResetAt)
22424
- return q.fiveHourResetAt;
22425
- return q.fiveHourResetAt.getTime() < q.sevenDayResetAt.getTime() ? q.fiveHourResetAt : q.sevenDayResetAt;
22426
- }
22427
- function buildSnapshotKeyboard(snapshots, opts = {}) {
22428
- const max = opts.maxSwitchButtons ?? 3;
22429
- const now = opts.now ?? new Date;
22430
- const rows = [];
22431
- 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);
22432
- for (const t of switchTargets) {
22433
- rows.push([
22434
- {
22435
- text: `Switch fleet \u2192 ${opts.demo ? maskEmail(t.label) : t.label}`,
22436
- callbackData: `auth:use:${t.label}`
22437
- }
22438
- ]);
22439
- }
22440
- rows.push([
22441
- { text: "\u21bb Refresh", callbackData: opts.demo ? "auth:refresh:demo" : "auth:refresh" },
22442
- { text: "/usage", insertText: "/usage" },
22443
- { text: "+ Add", insertText: "/auth add " }
22444
- ]);
22445
- return rows;
22446
- }
22447
- function switchPriority(s, now = new Date) {
22448
- const h = classifyHealth(s, now);
22449
- if (h === "healthy")
22450
- return 0;
22451
- if (h === "throttling")
22452
- return 1;
22453
- if (h === "unknown")
22454
- return 2;
22455
- return 3;
22456
- }
22457
- function zipProbeResults(labels, results) {
22458
- let staleCachedAtMs;
22459
- const quotas = labels.map((label) => {
22460
- const hit = results.find((r) => r.label === label);
22461
- if (!hit)
22462
- return { ok: false, reason: "broker returned no result for account" };
22463
- if (hit.served === "cache" && hit.capturedAt != null) {
22464
- staleCachedAtMs = staleCachedAtMs == null ? hit.capturedAt : Math.min(staleCachedAtMs, hit.capturedAt);
22465
- }
22466
- return hit.result;
22467
- });
22468
- return staleCachedAtMs != null ? { quotas, staleCachedAtMs } : { quotas };
22469
- }
22470
- function buildSnapshotsFromState(state, quotas) {
22471
- const out = [];
22472
- for (let i = 0;i < state.accounts.length; i++) {
22473
- const acc = state.accounts[i];
22474
- const q = quotas[i];
22475
- out.push({
22476
- label: acc.label,
22477
- isActive: acc.label === state.active,
22478
- quota: q && q.ok ? q.data : null,
22479
- quotaError: q && !q.ok ? q.reason : undefined,
22480
- expiresAtMs: acc.expiresAt
22481
- });
22482
- }
22483
- return out;
22484
- }
22485
- function reviveLastQuota(snap) {
22486
- if (!snap)
22487
- return null;
22488
- return {
22489
- fiveHourUtilizationPct: snap.fiveHourUtilizationPct,
22490
- sevenDayUtilizationPct: snap.sevenDayUtilizationPct,
22491
- fiveHourResetAt: snap.fiveHourResetAt ? new Date(snap.fiveHourResetAt) : null,
22492
- sevenDayResetAt: snap.sevenDayResetAt ? new Date(snap.sevenDayResetAt) : null,
22493
- representativeClaim: snap.representativeClaim,
22494
- overageStatus: snap.overageStatus,
22495
- overageDisabledReason: snap.overageDisabledReason,
22496
- fiveHourUtilPresent: snap.fiveHourUtilPresent,
22497
- sevenDayUtilPresent: snap.sevenDayUtilPresent
22498
- };
22499
- }
22500
- function buildSnapshotsFromCachedState(state) {
22501
- return state.accounts.map((acc) => {
22502
- const lq = acc.last_quota ?? null;
22503
- return {
22504
- label: acc.label,
22505
- isActive: acc.label === state.active,
22506
- quota: reviveLastQuota(lq),
22507
- quotaError: lq ? undefined : "no cached quota (no probe since broker start)",
22508
- expiresAtMs: acc.expiresAt,
22509
- capturedAtMs: lq?.capturedAt
22510
- };
22511
- });
22512
- }
22513
- var THROTTLING_THRESHOLD_PCT = 80, OVERAGE_EXHAUSTED_REASONS, HEALTH_EMOJI, TABLE_HEALTH_RANK;
22514
- var init_auth_snapshot_format = __esm(() => {
22515
- init_demo_mask();
22516
- init_card_format();
22517
- OVERAGE_EXHAUSTED_REASONS = new Set(["out_of_credits"]);
22518
- HEALTH_EMOJI = {
22519
- healthy: "\uD83D\uDFE2",
22520
- throttling: "\uD83D\uDFE1",
22521
- blocked: "\uD83D\uDD34",
22522
- unknown: "\u26aa"
22523
- };
22524
- TABLE_HEALTH_RANK = {
22525
- blocked: 0,
22526
- throttling: 1,
22527
- unknown: 2,
22528
- healthy: 3
22529
- };
22530
- });
22531
-
22532
22575
  // ../src/auth/broker/protocol.ts
22533
22576
  function encodeRequest2(req) {
22534
22577
  const line = JSON.stringify(RequestSchema2.parse(req)) + `
@@ -22549,7 +22592,7 @@ function decodeResponse2(line) {
22549
22592
  }
22550
22593
  return ResponseSchema2.parse(parsed);
22551
22594
  }
22552
- var MAX_FRAME_BYTES2, PROTOCOL_VERSION = 1, ProviderNameSchema, GetCredentialsRequestSchema, ListStateRequestSchema, SetActiveRequestSchema, MarkExhaustedRequestSchema, MarkThrottledRequestSchema, RefreshAccountRequestSchema, AnthropicCredentialsSchema, GoogleCredentialsSchema, MicrosoftCredentialsSchema, ProviderCredentialsSchema, AddAccountRequestSchema, RmAccountRequestSchema, SetOverrideRequestSchema, ListGoogleAccountsRequestSchema, ListMicrosoftAccountsRequestSchema, ProbeQuotaRequestSchema, ClaimNotificationRequestSchema, RequestSchema2, GetCredentialsDataSchema, AccountStateSchema, AgentStateSchema, ConsumerStateSchema, ListStateDataSchema, SetActiveDataSchema, MarkExhaustedDataSchema, MarkThrottledDataSchema, RefreshAccountDataSchema, AddAccountDataSchema, RmAccountDataSchema, SetOverrideDataSchema, ClaimNotificationDataSchema, GoogleAccountStateSchema, ListGoogleAccountsDataSchema, MicrosoftAccountStateSchema, ListMicrosoftAccountsDataSchema, ErrorBodySchema, SuccessResponseSchema, ErrorResponseSchema2, ResponseSchema2;
22595
+ var MAX_FRAME_BYTES2, PROTOCOL_VERSION = 1, ProviderNameSchema, GetCredentialsRequestSchema, ListStateRequestSchema, SetActiveRequestSchema, MarkExhaustedRequestSchema, MarkThrottledRequestSchema, RefreshAccountRequestSchema, AnthropicCredentialsSchema, GoogleCredentialsSchema, MicrosoftCredentialsSchema, ProviderCredentialsSchema, AddAccountRequestSchema, RmAccountRequestSchema, SetOverrideRequestSchema, ListGoogleAccountsRequestSchema, ListMicrosoftAccountsRequestSchema, ProbeQuotaRequestSchema, ClaimNotificationRequestSchema, GetExternalSpendRequestSchema, RequestSchema2, GetCredentialsDataSchema, AccountStateSchema, AgentStateSchema, ConsumerStateSchema, ListStateDataSchema, SetActiveDataSchema, MarkExhaustedDataSchema, MarkThrottledDataSchema, RefreshAccountDataSchema, AddAccountDataSchema, RmAccountDataSchema, SetOverrideDataSchema, ClaimNotificationDataSchema, GetExternalSpendDataSchema, GoogleAccountStateSchema, ListGoogleAccountsDataSchema, MicrosoftAccountStateSchema, ListMicrosoftAccountsDataSchema, ErrorBodySchema, SuccessResponseSchema, ErrorResponseSchema2, ResponseSchema2;
22553
22596
  var init_protocol2 = __esm(() => {
22554
22597
  init_zod();
22555
22598
  MAX_FRAME_BYTES2 = 64 * 1024;
@@ -22680,6 +22723,12 @@ var init_protocol2 = __esm(() => {
22680
22723
  key: exports_external.string().min(1).max(512),
22681
22724
  windowMs: exports_external.number().int().positive().max(86400000)
22682
22725
  });
22726
+ GetExternalSpendRequestSchema = exports_external.object({
22727
+ v: exports_external.literal(PROTOCOL_VERSION),
22728
+ op: exports_external.literal("get-external-spend"),
22729
+ id: exports_external.string().min(1),
22730
+ forceLive: exports_external.boolean().optional()
22731
+ });
22683
22732
  RequestSchema2 = exports_external.discriminatedUnion("op", [
22684
22733
  GetCredentialsRequestSchema,
22685
22734
  ListStateRequestSchema,
@@ -22693,7 +22742,8 @@ var init_protocol2 = __esm(() => {
22693
22742
  ListGoogleAccountsRequestSchema,
22694
22743
  ListMicrosoftAccountsRequestSchema,
22695
22744
  ProbeQuotaRequestSchema,
22696
- ClaimNotificationRequestSchema
22745
+ ClaimNotificationRequestSchema,
22746
+ GetExternalSpendRequestSchema
22697
22747
  ]);
22698
22748
  GetCredentialsDataSchema = exports_external.object({
22699
22749
  account: exports_external.string(),
@@ -22704,6 +22754,8 @@ var init_protocol2 = __esm(() => {
22704
22754
  label: exports_external.string(),
22705
22755
  expiresAt: exports_external.number().optional(),
22706
22756
  exhausted: exports_external.boolean(),
22757
+ in_service: exports_external.boolean().optional(),
22758
+ entitlement_blocked: exports_external.boolean().optional(),
22707
22759
  exhausted_until: exports_external.number().optional(),
22708
22760
  throttled_until: exports_external.number().optional(),
22709
22761
  threshold_violations: exports_external.number().int().nonnegative().optional(),
@@ -22760,6 +22812,18 @@ var init_protocol2 = __esm(() => {
22760
22812
  ClaimNotificationDataSchema = exports_external.object({
22761
22813
  granted: exports_external.boolean()
22762
22814
  });
22815
+ GetExternalSpendDataSchema = exports_external.object({
22816
+ available: exports_external.boolean(),
22817
+ day24hUsd: exports_external.number().optional(),
22818
+ day7dUsd: exports_external.number().optional(),
22819
+ top: exports_external.array(exports_external.object({
22820
+ label: exports_external.string(),
22821
+ usd: exports_external.number()
22822
+ })).optional(),
22823
+ capturedAtMs: exports_external.number().int().nonnegative().optional(),
22824
+ served: exports_external.enum(["live", "cache"]).optional(),
22825
+ reason: exports_external.string().optional()
22826
+ });
22763
22827
  GoogleAccountStateSchema = exports_external.object({
22764
22828
  account: exports_external.string(),
22765
22829
  expiresAt: exports_external.number(),
@@ -22930,6 +22994,15 @@ class AuthBrokerClient {
22930
22994
  }
22931
22995
  return parsed;
22932
22996
  }
22997
+ async getExternalSpend(forceLive) {
22998
+ const data = await this.send({
22999
+ v: PROTOCOL_VERSION,
23000
+ id: randomUUID(),
23001
+ op: "get-external-spend",
23002
+ ...forceLive ? { forceLive: true } : {}
23003
+ });
23004
+ return data;
23005
+ }
22933
23006
  async setActive(account) {
22934
23007
  const data = await this.send({
22935
23008
  v: PROTOCOL_VERSION,
@@ -29337,7 +29410,7 @@ var FLUSH_SUPPRESSION_WINDOW_MS = 2000;
29337
29410
  var init_turn_flush_suppression = () => {};
29338
29411
 
29339
29412
  // ../src/util/atomic.ts
29340
- import { closeSync as closeSync6, constants as constants2, fsyncSync as fsyncSync2, openSync as openSync6, renameSync as renameSync11, rmSync as rmSync5, writeSync as writeSync4 } from "node:fs";
29413
+ import { closeSync as closeSync6, constants as constants2, fchmodSync, fchownSync, fsyncSync as fsyncSync2, openSync as openSync6, renameSync as renameSync11, rmSync as rmSync5, writeSync as writeSync4 } from "node:fs";
29341
29414
  var TMP_OPEN_FLAGS;
29342
29415
  var init_atomic = __esm(() => {
29343
29416
  TMP_OPEN_FLAGS = constants2.O_WRONLY | constants2.O_CREAT | constants2.O_EXCL | (constants2.O_NOFOLLOW ?? 0);
@@ -36742,6 +36815,7 @@ __export(exports_auth_snapshot_format2, {
36742
36815
  formatRelative: () => formatRelative2,
36743
36816
  formatAbsolute: () => formatAbsolute2,
36744
36817
  fmtPct: () => fmtPct2,
36818
+ deriveUsageFooterFreshness: () => deriveUsageFooterFreshness2,
36745
36819
  classifyHealth: () => classifyHealth2,
36746
36820
  buildSnapshotsFromState: () => buildSnapshotsFromState2,
36747
36821
  buildSnapshotsFromCachedState: () => buildSnapshotsFromCachedState2,
@@ -36751,6 +36825,12 @@ __export(exports_auth_snapshot_format2, {
36751
36825
  THROTTLING_THRESHOLD_PCT: () => THROTTLING_THRESHOLD_PCT2
36752
36826
  });
36753
36827
  function classifyHealth2(snap, now = new Date) {
36828
+ if (!snap.isActive) {
36829
+ if (snap.entitlementBlocked === true)
36830
+ return "org-blocked";
36831
+ if (snap.inService === false)
36832
+ return "retired";
36833
+ }
36754
36834
  if (!snap.quota)
36755
36835
  return "unknown";
36756
36836
  const q = snap.quota;
@@ -36843,6 +36923,12 @@ function renderAccountRow2(snap, opts) {
36843
36923
  const lines = [];
36844
36924
  const marker = snap.isActive ? "\u25cf " : "";
36845
36925
  const label = displayLabel2(snap.label, opts);
36926
+ const health = classifyHealth2(snap, now);
36927
+ if (health === "org-blocked" || health === "retired") {
36928
+ const note = health === "org-blocked" ? "DISABLED (org) \u2014 no fleet routing" : "retired \u2014 removed from fleet rotation";
36929
+ lines.push(`${marker}\`${codeSpanSafe(label)}\` _${note}_`);
36930
+ return lines;
36931
+ }
36846
36932
  if (!snap.quota) {
36847
36933
  lines.push(`${marker}\`${codeSpanSafe(label)}\` _quota probe failed_`);
36848
36934
  if (snap.quotaError) {
@@ -36859,7 +36945,6 @@ function renderAccountRow2(snap, opts) {
36859
36945
  const fiveStr = fmtPct2(norm.fiveHourUtilizationPct);
36860
36946
  const sevenStr = fmtPct2(norm.sevenDayUtilizationPct);
36861
36947
  lines.push(`${marker}\`${codeSpanSafe(label)}\` ${fiveStr} / ${sevenStr}`);
36862
- const health = classifyHealth2(snap, now);
36863
36948
  if (health === "blocked") {
36864
36949
  const win = bindingWindow3(q);
36865
36950
  const reset2 = win === "5h" ? q.fiveHourResetAt : q.sevenDayResetAt;
@@ -36951,7 +37036,11 @@ function recommendation2(snapshots, now = new Date, demo = false) {
36951
37036
  if (!active)
36952
37037
  return "No active account set.";
36953
37038
  const activeHealth = classifyHealth2(active, now);
36954
- const others = snapshots.filter((s) => !s.isActive);
37039
+ const inServiceFleet = snapshots.filter((s) => {
37040
+ const h = classifyHealth2(s, now);
37041
+ return h !== "retired" && h !== "org-blocked";
37042
+ });
37043
+ const others = inServiceFleet.filter((s) => !s.isActive);
36955
37044
  const healthyAlt = others.find((s) => classifyHealth2(s, now) === "healthy");
36956
37045
  const lbl = (s) => demo ? maskEmail(s.label) : s.label;
36957
37046
  const activeLabel = lbl(active);
@@ -36968,7 +37057,7 @@ function recommendation2(snapshots, now = new Date, demo = false) {
36968
37057
  if (healthyAlt) {
36969
37058
  return `Recommendation: active ${activeLabel} is BLOCKED \u2014 switch to ${lbl(healthyAlt)} now.`;
36970
37059
  }
36971
- return summarizeNoHealthyAlt2(snapshots, now, demo);
37060
+ return summarizeNoHealthyAlt2(inServiceFleet, now, demo);
36972
37061
  }
36973
37062
  return `Active ${activeLabel}: quota probe failed; broker last_seen unknown.`;
36974
37063
  }
@@ -37040,7 +37129,14 @@ function renderFallbackAnnouncement2(input) {
37040
37129
  if (fleet.length > 0) {
37041
37130
  lines.push("");
37042
37131
  const rowOpts = { now, tz };
37043
- const healthOrder = ["blocked", "throttling", "healthy", "unknown"];
37132
+ const healthOrder = [
37133
+ "org-blocked",
37134
+ "blocked",
37135
+ "throttling",
37136
+ "healthy",
37137
+ "unknown",
37138
+ "retired"
37139
+ ];
37044
37140
  const rank = (s) => healthOrder.indexOf(classifyHealth2(s, now));
37045
37141
  const ordered = [...fleet].sort((a, b) => rank(a) - rank(b) || Number(b.isActive) - Number(a.isActive));
37046
37142
  for (const snap of ordered) {
@@ -37113,7 +37209,10 @@ function buildSnapshotKeyboard2(snapshots, opts = {}) {
37113
37209
  const max = opts.maxSwitchButtons ?? 3;
37114
37210
  const now = opts.now ?? new Date;
37115
37211
  const rows = [];
37116
- const switchTargets = snapshots.filter((s) => !s.isActive).sort((a, b) => switchPriority2(a, now) - switchPriority2(b, now)).filter((s) => classifyHealth2(s, now) !== "blocked" && classifyHealth2(s, now) !== "unknown").slice(0, max);
37212
+ const switchTargets = snapshots.filter((s) => !s.isActive).sort((a, b) => switchPriority2(a, now) - switchPriority2(b, now)).filter((s) => {
37213
+ const h = classifyHealth2(s, now);
37214
+ return h !== "blocked" && h !== "unknown" && h !== "retired" && h !== "org-blocked";
37215
+ }).slice(0, max);
37117
37216
  for (const t of switchTargets) {
37118
37217
  rows.push([
37119
37218
  {
@@ -37137,7 +37236,9 @@ function switchPriority2(s, now = new Date) {
37137
37236
  return 1;
37138
37237
  if (h === "unknown")
37139
37238
  return 2;
37140
- return 3;
37239
+ if (h === "blocked")
37240
+ return 3;
37241
+ return 4;
37141
37242
  }
37142
37243
  function zipProbeResults2(labels, results) {
37143
37244
  let staleCachedAtMs;
@@ -37152,6 +37253,13 @@ function zipProbeResults2(labels, results) {
37152
37253
  });
37153
37254
  return staleCachedAtMs != null ? { quotas, staleCachedAtMs } : { quotas };
37154
37255
  }
37256
+ function deriveUsageFooterFreshness2(results, staleCachedAtMs, liveProbedAtMs) {
37257
+ if (staleCachedAtMs != null)
37258
+ return { staleCachedAtMs };
37259
+ if (results.some((r) => r.result.ok))
37260
+ return { liveProbedAtMs };
37261
+ return { probeFailed: true };
37262
+ }
37155
37263
  function buildSnapshotsFromState2(state7, quotas) {
37156
37264
  const out = [];
37157
37265
  for (let i = 0;i < state7.accounts.length; i++) {
@@ -37162,7 +37270,9 @@ function buildSnapshotsFromState2(state7, quotas) {
37162
37270
  isActive: acc.label === state7.active,
37163
37271
  quota: q && q.ok ? q.data : null,
37164
37272
  quotaError: q && !q.ok ? q.reason : undefined,
37165
- expiresAtMs: acc.expiresAt
37273
+ expiresAtMs: acc.expiresAt,
37274
+ inService: acc.in_service,
37275
+ entitlementBlocked: acc.entitlement_blocked
37166
37276
  });
37167
37277
  }
37168
37278
  return out;
@@ -37191,7 +37301,9 @@ function buildSnapshotsFromCachedState2(state7) {
37191
37301
  quota: reviveLastQuota2(lq),
37192
37302
  quotaError: lq ? undefined : "no cached quota (no probe since broker start)",
37193
37303
  expiresAtMs: acc.expiresAt,
37194
- capturedAtMs: lq?.capturedAt
37304
+ capturedAtMs: lq?.capturedAt,
37305
+ inService: acc.in_service,
37306
+ entitlementBlocked: acc.entitlement_blocked
37195
37307
  };
37196
37308
  });
37197
37309
  }
@@ -37204,13 +37316,17 @@ var init_auth_snapshot_format2 = __esm(() => {
37204
37316
  healthy: "\uD83D\uDFE2",
37205
37317
  throttling: "\uD83D\uDFE1",
37206
37318
  blocked: "\uD83D\uDD34",
37207
- unknown: "\u26aa"
37319
+ unknown: "\u26aa",
37320
+ "org-blocked": "\u26d4",
37321
+ retired: "\u26ab"
37208
37322
  };
37209
37323
  TABLE_HEALTH_RANK2 = {
37210
- blocked: 0,
37211
- throttling: 1,
37212
- unknown: 2,
37213
- healthy: 3
37324
+ "org-blocked": 0,
37325
+ blocked: 1,
37326
+ throttling: 2,
37327
+ unknown: 3,
37328
+ healthy: 4,
37329
+ retired: 5
37214
37330
  };
37215
37331
  });
37216
37332
 
@@ -37626,6 +37742,140 @@ var init_config_approval_handler = __esm(() => {
37626
37742
  pending = new Map;
37627
37743
  });
37628
37744
 
37745
+ // ../src/litellm/external-spend.ts
37746
+ function isExternalModel(model) {
37747
+ const m = model.trim().toLowerCase();
37748
+ if (!m)
37749
+ return false;
37750
+ if (m.startsWith("claude"))
37751
+ return false;
37752
+ if (m.includes("anthropic/claude"))
37753
+ return false;
37754
+ if (m.startsWith("openrouter/"))
37755
+ return true;
37756
+ if (m.startsWith("sr-"))
37757
+ return true;
37758
+ for (const needle of BARE_EXTERNAL_NEEDLES) {
37759
+ if (m.includes(needle))
37760
+ return true;
37761
+ }
37762
+ return false;
37763
+ }
37764
+ function shortModelLabel(model) {
37765
+ let m = model.trim();
37766
+ if (m.toLowerCase().startsWith("openrouter/")) {
37767
+ m = m.slice("openrouter/".length);
37768
+ }
37769
+ const parts = m.split("/").filter(Boolean);
37770
+ if (parts.length >= 2) {
37771
+ m = parts[parts.length - 1];
37772
+ }
37773
+ if (m.toLowerCase().startsWith("sr-")) {
37774
+ m = m.slice(3);
37775
+ }
37776
+ return m || model.trim();
37777
+ }
37778
+ function formatUsd(n) {
37779
+ if (!Number.isFinite(n) || n < 0)
37780
+ return "$0.00";
37781
+ return `$${n.toFixed(2)}`;
37782
+ }
37783
+ function utcDateString(d) {
37784
+ return d.toISOString().slice(0, 10);
37785
+ }
37786
+ function addUtcDays(dateStr, days) {
37787
+ const [y, m, d] = dateStr.split("-").map(Number);
37788
+ const dt = new Date(Date.UTC(y, m - 1, d));
37789
+ dt.setUTCDate(dt.getUTCDate() + days);
37790
+ return utcDateString(dt);
37791
+ }
37792
+ function rowDay(row) {
37793
+ const st = row.startTime;
37794
+ if (!st || typeof st !== "string")
37795
+ return null;
37796
+ return st.length >= 10 ? st.slice(0, 10) : null;
37797
+ }
37798
+ function externalModelsFromRow(row) {
37799
+ const out = {};
37800
+ const models = row.models ?? {};
37801
+ for (const [name, raw] of Object.entries(models)) {
37802
+ if (!isExternalModel(name))
37803
+ continue;
37804
+ const n = typeof raw === "number" ? raw : Number(raw);
37805
+ if (!Number.isFinite(n) || n === 0)
37806
+ continue;
37807
+ out[name] = (out[name] ?? 0) + n;
37808
+ }
37809
+ return out;
37810
+ }
37811
+ function summarizeExternalSpend(days, now = new Date) {
37812
+ const today = utcDateString(now);
37813
+ const start7 = addUtcDays(today, -6);
37814
+ let day24hUsd = 0;
37815
+ let day7dUsd = 0;
37816
+ const byModel = {};
37817
+ for (const row of days) {
37818
+ const day = rowDay(row);
37819
+ if (!day)
37820
+ continue;
37821
+ if (day < start7 || day > today)
37822
+ continue;
37823
+ const ext = externalModelsFromRow(row);
37824
+ let rowSum = 0;
37825
+ for (const [name, usd] of Object.entries(ext)) {
37826
+ rowSum += usd;
37827
+ byModel[name] = (byModel[name] ?? 0) + usd;
37828
+ }
37829
+ day7dUsd += rowSum;
37830
+ if (day === today)
37831
+ day24hUsd += rowSum;
37832
+ }
37833
+ const top = Object.entries(byModel).filter(([, usd]) => usd > 0).sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])).slice(0, EXTERNAL_SPEND_TOP_N).map(([name, usd]) => ({ label: shortModelLabel(name), usd }));
37834
+ return { day24hUsd, day7dUsd, top };
37835
+ }
37836
+ function normalizeSpendLogRows(body) {
37837
+ if (Array.isArray(body))
37838
+ return body;
37839
+ if (body && typeof body === "object") {
37840
+ const data = body.data;
37841
+ if (Array.isArray(data))
37842
+ return data;
37843
+ }
37844
+ return [];
37845
+ }
37846
+ var EXTERNAL_SPEND_TOP_N = 3, EXTERNAL_SPEND_CACHE_TTL_MS = 90000, BARE_EXTERNAL_NEEDLES;
37847
+ var init_external_spend = __esm(() => {
37848
+ BARE_EXTERNAL_NEEDLES = [
37849
+ "gpt-oss",
37850
+ "grok",
37851
+ "gemini",
37852
+ "deepseek",
37853
+ "kimi",
37854
+ "glm-",
37855
+ "qwen",
37856
+ "llama"
37857
+ ];
37858
+ });
37859
+
37860
+ // external-spend.ts
37861
+ function formatExternalSpendBlock(summary) {
37862
+ if (summary == null)
37863
+ return [];
37864
+ const lines = [
37865
+ "- \uD83D\uDCB8 External",
37866
+ `- 24h \`${formatUsd(summary.day24hUsd)}\` \u00b7 7d \`${formatUsd(summary.day7dUsd)}\``
37867
+ ];
37868
+ if (summary.top.length > 0) {
37869
+ const topParts = summary.top.map((t) => `\`${t.label} ${formatUsd(t.usd)}\``);
37870
+ lines.push(`- top ${topParts.join(" \u00b7 ")}`);
37871
+ }
37872
+ return lines;
37873
+ }
37874
+ var init_external_spend2 = __esm(() => {
37875
+ init_external_spend();
37876
+ init_external_spend();
37877
+ });
37878
+
37629
37879
  // quota-bar-format.ts
37630
37880
  var exports_quota_bar_format = {};
37631
37881
  __export(exports_quota_bar_format, {
@@ -37683,9 +37933,13 @@ function buildBar(pct, elapsedFrac) {
37683
37933
  cells[tickIndex] = "\u2503";
37684
37934
  return cells.join("");
37685
37935
  }
37686
- function accountStatus(isActive, exhausted) {
37936
+ function accountStatus(isActive, exhausted, opts = {}) {
37687
37937
  if (isActive)
37688
37938
  return "active";
37939
+ if (opts.entitlementBlocked === true)
37940
+ return "org-disabled";
37941
+ if (opts.inService === false)
37942
+ return "retired";
37689
37943
  if (exhausted)
37690
37944
  return "exhausted";
37691
37945
  return "idle";
@@ -37698,10 +37952,15 @@ function formatWindowRow(window2, pct, resetAt, now = new Date) {
37698
37952
  const timeLeft = formatTimeLeft(resetAt, now);
37699
37953
  return `- ${dot} ${window2} \`[${bar}] ${pctStr} / ${timeLeft}\``;
37700
37954
  }
37701
- function renderQuotaBarAccount(label, isActive, exhausted, quota, now = new Date, demo = false) {
37702
- const status = accountStatus(isActive, exhausted);
37955
+ function renderQuotaBarAccount(label, isActive, exhausted, quota, now = new Date, demo = false, service = {}) {
37956
+ const status = accountStatus(isActive, exhausted, service);
37703
37957
  const displayLabel3 = demo ? maskEmail(label) : label;
37704
- const lines = [`- **${escapeMarkdown(displayLabel3)}** (${status})`];
37958
+ const lines = [`- **${escapeMarkdown(displayLabel3)}** (${STATUS_LABEL[status]})`];
37959
+ if (status === "retired" || status === "org-disabled") {
37960
+ const note = status === "org-disabled" ? "disabled by org \u2014 no fleet routing" : "retired \u2014 removed from fleet rotation";
37961
+ lines.push(`- \u26ab \`${note}\``);
37962
+ return lines;
37963
+ }
37705
37964
  if (!quota || isProbeThin(quota)) {
37706
37965
  const reason = !quota ? "no data \u2014 probe failed" : "no data \u2014 thin probe";
37707
37966
  lines.push(`- \u26a0\ufe0f 5h \`${reason}\``);
@@ -37721,9 +37980,17 @@ function renderQuotaBarBlock(snapshots, exhaustedByLabel, opts = {}) {
37721
37980
  const now = opts.now ?? new Date;
37722
37981
  const demo = opts.demo ?? false;
37723
37982
  const lines = [];
37724
- for (const snap of snapshots) {
37983
+ const outOfService = (s) => !s.isActive && (s.entitlementBlocked === true || s.inService === false);
37984
+ const ordered = [
37985
+ ...snapshots.filter((s) => !outOfService(s)),
37986
+ ...snapshots.filter(outOfService)
37987
+ ];
37988
+ for (const snap of ordered) {
37725
37989
  const exhausted = exhaustedByLabel.get(snap.label) ?? false;
37726
- lines.push(...renderQuotaBarAccount(snap.label, snap.isActive, exhausted, snap.quota, now, demo));
37990
+ lines.push(...renderQuotaBarAccount(snap.label, snap.isActive, exhausted, snap.quota, now, demo, {
37991
+ inService: snap.inService,
37992
+ entitlementBlocked: snap.entitlementBlocked
37993
+ }));
37727
37994
  }
37728
37995
  return lines.join(`
37729
37996
  `);
@@ -37737,7 +38004,9 @@ function renderQuotaBarBlockFromListState(state7, opts = {}) {
37737
38004
  quota: reviveLastQuota(acc.last_quota ?? null),
37738
38005
  quotaError: acc.last_quota ? undefined : "no cached quota (no probe since broker start)",
37739
38006
  expiresAtMs: acc.expiresAt,
37740
- capturedAtMs: acc.last_quota?.capturedAt
38007
+ capturedAtMs: acc.last_quota?.capturedAt,
38008
+ inService: acc.in_service,
38009
+ entitlementBlocked: acc.entitlement_blocked
37741
38010
  }));
37742
38011
  return renderQuotaBarBlock(snapshots, exhaustedByLabel, { now });
37743
38012
  }
@@ -37747,6 +38016,9 @@ function renderUsageCard(snapshots, exhaustedByLabel, opts = {}) {
37747
38016
  const bar = renderQuotaBarBlock(snapshots, exhaustedByLabel, { now, demo });
37748
38017
  const lines = [bar];
37749
38018
  lines.push(`_${recommendation(snapshots, now, demo)}_`);
38019
+ if (opts.externalSpend != null) {
38020
+ lines.push(...formatExternalSpendBlock(opts.externalSpend));
38021
+ }
37750
38022
  if (opts.staleCachedAtMs != null) {
37751
38023
  lines.push(`_\u26a0 cached ${formatAgeStamp3(opts.staleCachedAtMs, now)}_`);
37752
38024
  } else if (opts.liveProbedAtMs != null) {
@@ -37759,13 +38031,95 @@ function renderUsageCard(snapshots, exhaustedByLabel, opts = {}) {
37759
38031
  return lines.join(`
37760
38032
  `);
37761
38033
  }
37762
- var FIVE_HOUR_MS, SEVEN_DAY_MS, BAR_WIDTH = 10;
38034
+ var FIVE_HOUR_MS, SEVEN_DAY_MS, BAR_WIDTH = 10, STATUS_LABEL;
37763
38035
  var init_quota_bar_format = __esm(() => {
37764
38036
  init_auth_snapshot_format();
37765
38037
  init_card_format();
37766
38038
  init_demo_mask();
38039
+ init_external_spend2();
37767
38040
  FIVE_HOUR_MS = 5 * 60 * 60 * 1000;
37768
38041
  SEVEN_DAY_MS = 7 * 24 * 60 * 60 * 1000;
38042
+ STATUS_LABEL = {
38043
+ active: "active",
38044
+ "org-disabled": "DISABLED (org)",
38045
+ retired: "retired",
38046
+ exhausted: "exhausted",
38047
+ idle: "idle"
38048
+ };
38049
+ });
38050
+
38051
+ // external-spend.ts
38052
+ var exports_external_spend = {};
38053
+ __export(exports_external_spend, {
38054
+ utcDateString: () => utcDateString,
38055
+ summarizeExternalSpend: () => summarizeExternalSpend,
38056
+ shortModelLabel: () => shortModelLabel,
38057
+ normalizeSpendLogRows: () => normalizeSpendLogRows,
38058
+ isExternalModel: () => isExternalModel,
38059
+ formatUsd: () => formatUsd,
38060
+ formatExternalSpendBlock: () => formatExternalSpendBlock2,
38061
+ fetchExternalSpendSummary: () => fetchExternalSpendSummary,
38062
+ clearExternalSpendCache: () => clearExternalSpendCache,
38063
+ addUtcDays: () => addUtcDays,
38064
+ EXTERNAL_SPEND_CACHE_TTL_MS: () => EXTERNAL_SPEND_CACHE_TTL_MS
38065
+ });
38066
+ function clearExternalSpendCache() {
38067
+ clientCache = null;
38068
+ }
38069
+ function formatExternalSpendBlock2(summary) {
38070
+ if (summary == null)
38071
+ return [];
38072
+ const lines = [
38073
+ "- \uD83D\uDCB8 External",
38074
+ `- 24h \`${formatUsd(summary.day24hUsd)}\` \u00b7 7d \`${formatUsd(summary.day7dUsd)}\``
38075
+ ];
38076
+ if (summary.top.length > 0) {
38077
+ const topParts = summary.top.map((t) => `\`${t.label} ${formatUsd(t.usd)}\``);
38078
+ lines.push(`- top ${topParts.join(" \u00b7 ")}`);
38079
+ }
38080
+ return lines;
38081
+ }
38082
+ async function fetchExternalSpendSummary(nowOrDeps = {}) {
38083
+ const deps = nowOrDeps instanceof Date ? { now: nowOrDeps } : nowOrDeps;
38084
+ const ttl = deps.cacheTtlMs ?? CLIENT_CACHE_TTL_MS;
38085
+ if (!deps.bypassCache && clientCache && Date.now() - clientCache.atMs < ttl) {
38086
+ return clientCache.summary;
38087
+ }
38088
+ try {
38089
+ let data;
38090
+ if (deps.getExternalSpend) {
38091
+ data = await deps.getExternalSpend(deps.forceLive);
38092
+ } else {
38093
+ const { AuthBrokerClient: AuthBrokerClient3 } = await Promise.resolve().then(() => (init_client2(), exports_client));
38094
+ const client3 = new AuthBrokerClient3;
38095
+ try {
38096
+ data = await client3.getExternalSpend(deps.forceLive);
38097
+ } finally {
38098
+ try {
38099
+ await client3.close();
38100
+ } catch {}
38101
+ }
38102
+ }
38103
+ if (!data?.available)
38104
+ return null;
38105
+ if (typeof data.day24hUsd !== "number" || typeof data.day7dUsd !== "number" || !Array.isArray(data.top)) {
38106
+ return null;
38107
+ }
38108
+ const summary = {
38109
+ day24hUsd: data.day24hUsd,
38110
+ day7dUsd: data.day7dUsd,
38111
+ top: data.top.map((t) => ({ label: String(t.label), usd: Number(t.usd) }))
38112
+ };
38113
+ clientCache = { atMs: Date.now(), summary };
38114
+ return summary;
38115
+ } catch {
38116
+ return null;
38117
+ }
38118
+ }
38119
+ var CLIENT_CACHE_TTL_MS = 30000, clientCache = null;
38120
+ var init_external_spend3 = __esm(() => {
38121
+ init_external_spend();
38122
+ init_external_spend();
37769
38123
  });
37770
38124
 
37771
38125
  // ../src/vault/approvals/client.ts
@@ -38411,6 +38765,16 @@ function buildStopReply(turnInFlight, queuedSessionCmds) {
38411
38765
  `) };
38412
38766
  }
38413
38767
 
38768
+ // gateway/usage-mask.ts
38769
+ function shouldMaskUsageLabels(chatType, groupAllowFrom) {
38770
+ if (chatType === "private")
38771
+ return false;
38772
+ if (chatType === "group" || chatType === "supergroup") {
38773
+ return (groupAllowFrom ?? []).length === 0;
38774
+ }
38775
+ return true;
38776
+ }
38777
+
38414
38778
  // gateway/busy-ack.ts
38415
38779
  var BUSY_ACK_STEP_AGE_THRESHOLD_MS = 12000;
38416
38780
  function shouldPostBusyAck(input) {
@@ -40001,6 +40365,23 @@ function normalizeForDedup(text) {
40001
40365
 
40002
40366
  // flushed-turn-supersede.ts
40003
40367
  var DEFAULT_SUPERSEDE_TTL_MS = 60000;
40368
+ var SUPERSEDE_MATCH_MIN_CONTAINMENT_CHARS = 32;
40369
+ function normalizeForMatch(text) {
40370
+ return text.replace(/\s+/g, " ").trim();
40371
+ }
40372
+ function flushedAnswerMatchesReply(flushedText, replyText) {
40373
+ const flushed = normalizeForMatch(flushedText);
40374
+ const reply = normalizeForMatch(replyText);
40375
+ if (flushed.length === 0 || reply.length === 0)
40376
+ return false;
40377
+ if (flushed === reply)
40378
+ return true;
40379
+ if (reply.length >= SUPERSEDE_MATCH_MIN_CONTAINMENT_CHARS && flushed.includes(reply))
40380
+ return true;
40381
+ if (flushed.length >= SUPERSEDE_MATCH_MIN_CONTAINMENT_CHARS && reply.includes(flushed))
40382
+ return true;
40383
+ return false;
40384
+ }
40004
40385
  function decideSupersede(record, args) {
40005
40386
  const ttlMs = args.ttlMs ?? DEFAULT_SUPERSEDE_TTL_MS;
40006
40387
  if (record == null)
@@ -40012,7 +40393,15 @@ function decideSupersede(record, args) {
40012
40393
  if (!sameTurn) {
40013
40394
  return { supersede: false, deleteMessageIds: [], reason: "different-turn" };
40014
40395
  }
40015
- return { supersede: true, deleteMessageIds: [...record.messageIds], reason: "supersede" };
40396
+ if (args.replyText != null && !flushedAnswerMatchesReply(record.text, args.replyText)) {
40397
+ return { supersede: false, deleteMessageIds: [], reason: "new-content", recordText: record.text };
40398
+ }
40399
+ return {
40400
+ supersede: true,
40401
+ deleteMessageIds: [...record.messageIds],
40402
+ reason: "supersede",
40403
+ recordText: record.text
40404
+ };
40016
40405
  }
40017
40406
  var NULL_TURN_KEY = "<<null-turn>>";
40018
40407
  function turnKey(turnId) {
@@ -40044,7 +40433,12 @@ class FlushedTurnSupersedeRegistry {
40044
40433
  }
40045
40434
  peek(chatId, threadId, args) {
40046
40435
  const rec = this.entries.get(makeKey2(chatId, threadId))?.get(turnKey(args.liveTurnId));
40047
- return decideSupersede(rec, { liveTurnId: args.liveTurnId, now: args.now, ttlMs: this.ttlMs });
40436
+ return decideSupersede(rec, {
40437
+ liveTurnId: args.liveTurnId,
40438
+ replyText: args.replyText,
40439
+ now: args.now,
40440
+ ttlMs: this.ttlMs
40441
+ });
40048
40442
  }
40049
40443
  take(chatId, threadId, args) {
40050
40444
  const lane = makeKey2(chatId, threadId);
@@ -40633,6 +41027,63 @@ function buildStopReply2(turnInFlight, queuedSessionCmds) {
40633
41027
  `) };
40634
41028
  }
40635
41029
 
41030
+ // gateway/auth-command.ts
41031
+ init_format();
41032
+ init_auth_snapshot_format();
41033
+ init_demo_mask();
41034
+ var pendingAuthRmFlows = new Map;
41035
+ function isAuthAdmin(args) {
41036
+ return args.isAdmin === true;
41037
+ }
41038
+ var REQUIRED_USAGE_SCOPE = "user:profile";
41039
+ function readdPrecheckError(label, replace, labelExists) {
41040
+ if (replace && !labelExists) {
41041
+ return `**/auth readd:** no account named \`${codeSpanSafe(label)}\` to re-auth. ` + `Run \`/auth show\` for the current list, or \`/auth add ${codeSpanSafe(label)}\` to add it fresh.`;
41042
+ }
41043
+ if (!replace && labelExists) {
41044
+ return `**/auth add:** \`${codeSpanSafe(label)}\` already exists. ` + `Use \`/auth readd ${codeSpanSafe(label)}\` to re-authenticate it in place (e.g. to widen scope).`;
41045
+ }
41046
+ return null;
41047
+ }
41048
+ async function runReaddPrecheck(getClient, label, replace) {
41049
+ try {
41050
+ const client = await getClient();
41051
+ if (!client)
41052
+ return null;
41053
+ const state = await client.listState();
41054
+ const exists = state.accounts.some((a) => a.label === label);
41055
+ return readdPrecheckError(label, replace, exists);
41056
+ } catch {
41057
+ return null;
41058
+ }
41059
+ }
41060
+ function formatGrantedScopesReply(scopes) {
41061
+ const list = Array.isArray(scopes) ? scopes.filter((s) => typeof s === "string") : [];
41062
+ const hasUsageScope = list.includes(REQUIRED_USAGE_SCOPE);
41063
+ if (list.length === 0) {
41064
+ return {
41065
+ text: `
41066
+ \u26a0\ufe0f **No scopes reported** on the new token \u2014 could not confirm \`${REQUIRED_USAGE_SCOPE}\`. ` + `7-day usage reporting may not work. Re-run \`/auth readd\` if usage is missing.`,
41067
+ hasUsageScope: false
41068
+ };
41069
+ }
41070
+ const rendered = list.map((s) => `\`${codeSpanSafe(s)}\``).join(", ");
41071
+ if (hasUsageScope) {
41072
+ return {
41073
+ text: `
41074
+ Granted scopes: ${rendered}
41075
+ \u2713 \`${REQUIRED_USAGE_SCOPE}\` present \u2014 7-day usage reporting is unlocked.`,
41076
+ hasUsageScope: true
41077
+ };
41078
+ }
41079
+ return {
41080
+ text: `
41081
+ Granted scopes: ${rendered}
41082
+ ` + `\u26a0\ufe0f **\`${REQUIRED_USAGE_SCOPE}\` is MISSING** \u2014 7-day usage reporting will not work for this account. ` + `This usually means the narrow \`setup-token\` minter was used. Re-run \`/auth readd <label>\` with the broad login to fix.`,
41083
+ hasUsageScope: false
41084
+ };
41085
+ }
41086
+
40636
41087
  // gateway/interrupt-defer.ts
40637
41088
  class ToolFlightTracker2 {
40638
41089
  inFlight = new Set;
@@ -41678,11 +42129,14 @@ async function interceptAuthAdd(p, deps) {
41678
42129
  deps.pendingAuthAddFlows.delete(p.interceptKey);
41679
42130
  try {
41680
42131
  const credentials = await deps.submitAccountAuthCode(pendingAdd, p.text.trim());
42132
+ const replace = pendingAdd.replace === true;
41681
42133
  try {
41682
- await deps.addAccountViaBroker(pendingAdd.label, credentials, { replace: false });
42134
+ await deps.addAccountViaBroker(pendingAdd.label, credentials, { replace });
41683
42135
  deps.cleanAuthAddScratchDir(pendingAdd.scratchDir);
41684
- await deps.switchroomReply(p.ctx, `\u2713 Account \`${pendingAdd.label}\` added.
41685
- The fleet's active account hasn't changed. Send \`/auth use ${deps.escapeHtmlForTg(pendingAdd.label)}\` to switch to it.`, { html: true });
42136
+ const scopeReply = formatGrantedScopesReply(credentials.claudeAiOauth?.scopes);
42137
+ const verb = replace ? "re-authenticated" : "added";
42138
+ await deps.switchroomReply(p.ctx, `\u2713 Account \`${pendingAdd.label}\` ${verb}.
42139
+ The fleet's active account hasn't changed. Send \`/auth use ${deps.escapeHtmlForTg(pendingAdd.label)}\` to switch to it.` + scopeReply.text, { html: true });
41686
42140
  } catch (brokerErr) {
41687
42141
  deps.cleanAuthAddScratchDir(pendingAdd.scratchDir);
41688
42142
  await deps.switchroomReply(p.ctx, `**/auth add failed at broker:** ${deps.escapeHtmlForTg(brokerErr?.message ?? String(brokerErr))}`, { html: true });
@@ -42308,11 +42762,14 @@ async function interceptAuthAdd2(p, deps) {
42308
42762
  deps.pendingAuthAddFlows.delete(p.interceptKey);
42309
42763
  try {
42310
42764
  const credentials = await deps.submitAccountAuthCode(pendingAdd, p.text.trim());
42765
+ const replace = pendingAdd.replace === true;
42311
42766
  try {
42312
- await deps.addAccountViaBroker(pendingAdd.label, credentials, { replace: false });
42767
+ await deps.addAccountViaBroker(pendingAdd.label, credentials, { replace });
42313
42768
  deps.cleanAuthAddScratchDir(pendingAdd.scratchDir);
42314
- await deps.switchroomReply(p.ctx, `\u2713 Account \`${pendingAdd.label}\` added.
42315
- The fleet's active account hasn't changed. Send \`/auth use ${deps.escapeHtmlForTg(pendingAdd.label)}\` to switch to it.`, { html: true });
42769
+ const scopeReply = formatGrantedScopesReply(credentials.claudeAiOauth?.scopes);
42770
+ const verb = replace ? "re-authenticated" : "added";
42771
+ await deps.switchroomReply(p.ctx, `\u2713 Account \`${pendingAdd.label}\` ${verb}.
42772
+ The fleet's active account hasn't changed. Send \`/auth use ${deps.escapeHtmlForTg(pendingAdd.label)}\` to switch to it.` + scopeReply.text, { html: true });
42316
42773
  } catch (brokerErr) {
42317
42774
  deps.cleanAuthAddScratchDir(pendingAdd.scratchDir);
42318
42775
  await deps.switchroomReply(p.ctx, `**/auth add failed at broker:** ${deps.escapeHtmlForTg(brokerErr?.message ?? String(brokerErr))}`, { html: true });
@@ -42809,6 +43266,7 @@ function pinnedMessageIsOurs(tracked, chatId, pinnedMessageId) {
42809
43266
  return false;
42810
43267
  }
42811
43268
  var storeLockTails = new Map;
43269
+ var pinReconcileTails = new Map;
42812
43270
 
42813
43271
  // gateway/pinned-message-handler.ts
42814
43272
  async function handlePinnedMessage(ctx, deps) {
@@ -43272,14 +43730,23 @@ function relaunchErrorReply(deps, model, err) {
43272
43730
  const msg = err instanceof Error ? err.message : String(err);
43273
43731
  return { text: `\u274c Could not schedule model switch: ${deps.escapeHtml(msg)}`, html: true };
43274
43732
  }
43733
+ function unvalidatedIdCaveat(deps, model) {
43734
+ if (!model.trim().toLowerCase().startsWith("claude-"))
43735
+ return null;
43736
+ return `_\`${deps.escapeHtml(model)}\` can't be validated before launch \u2014 if it isn't a real Claude model id, claude will silently serve the configured fallback model instead. I check the first reply and will warn if that happens._`;
43737
+ }
43275
43738
  async function scheduleRelaunchReply(deps, model, reason) {
43276
43739
  try {
43277
43740
  await deps.scheduleModelRelaunch(model, reason);
43278
43741
  } catch (err) {
43279
43742
  return relaunchErrorReply(deps, model, err);
43280
43743
  }
43281
- return { text: [switchingLine(deps, model), PERSIST_NOTE].join(`
43282
- `), html: true };
43744
+ const caveat = unvalidatedIdCaveat(deps, model);
43745
+ return {
43746
+ text: [switchingLine(deps, model), ...caveat ? [caveat] : [], PERSIST_NOTE].join(`
43747
+ `),
43748
+ html: true
43749
+ };
43283
43750
  }
43284
43751
  async function scheduleDefaultRelaunchReply(deps, reason) {
43285
43752
  try {
@@ -50565,16 +51032,28 @@ function formatModelLabel2(model) {
50565
51032
  }
50566
51033
 
50567
51034
  // gateway/session-model-source.ts
50568
- function createSessionModelSource() {
51035
+ function createSessionModelSource(options = {}) {
50569
51036
  let seq = 0;
50570
51037
  let transcript = null;
50571
51038
  let override = null;
51039
+ let overrideUnverified = false;
51040
+ let onDivergence = null;
50572
51041
  return {
50573
- noteTranscriptModel(model) {
51042
+ noteTranscriptModel(model, opts) {
50574
51043
  transcript = { model, seq: ++seq };
51044
+ if (opts?.replayed === true)
51045
+ return;
51046
+ if (override != null && overrideUnverified) {
51047
+ overrideUnverified = false;
51048
+ const matches = options.servedMatchesRequested;
51049
+ if (matches != null && !matches(override.model, model)) {
51050
+ onDivergence?.({ requested: override.model, served: model });
51051
+ }
51052
+ }
50575
51053
  },
50576
- setOverride(model) {
51054
+ setOverride(model, opts) {
50577
51055
  override = model == null ? null : { model, seq: ++seq };
51056
+ overrideUnverified = model != null && opts?.verify === true;
50578
51057
  },
50579
51058
  getOverride() {
50580
51059
  return override?.model ?? null;
@@ -50588,6 +51067,9 @@ function createSessionModelSource() {
50588
51067
  return { model: override.model, source: "override" };
50589
51068
  }
50590
51069
  return { model: transcript.model, source: "transcript" };
51070
+ },
51071
+ setDivergenceHandler(handler) {
51072
+ onDivergence = handler;
50591
51073
  }
50592
51074
  };
50593
51075
  }
@@ -68599,7 +69081,7 @@ init_format();
68599
69081
  init_auth_snapshot_format();
68600
69082
  init_demo_mask();
68601
69083
  var AUTH_RM_CONFIRM_TTL_MS = 60000;
68602
- var pendingAuthRmFlows = new Map;
69084
+ var pendingAuthRmFlows2 = new Map;
68603
69085
  var LABEL_RE = /^[A-Za-z0-9._@+-]+$/;
68604
69086
  var LABEL_MAX = 64;
68605
69087
  function validateAuthAddLabel(label) {
@@ -68647,14 +69129,24 @@ function parseAuthCommand(text4) {
68647
69129
  return { kind: "help", reason: "Usage: /auth use <label>" };
68648
69130
  return { kind: "use", label };
68649
69131
  }
68650
- case "add": {
68651
- const label = parts[1];
69132
+ case "add":
69133
+ case "readd": {
69134
+ const positional = parts.slice(1).filter((t) => !t.startsWith("--"));
69135
+ const flags = new Set(parts.slice(1).filter((t) => t.startsWith("--")).map((t) => t.toLowerCase()));
69136
+ const label = positional[0];
69137
+ const usage = verb === "readd" ? "Usage: /auth readd <label>" : "Usage: /auth add <label>";
68652
69138
  if (!label)
68653
- return { kind: "help", reason: "Usage: /auth add <label>" };
69139
+ return { kind: "help", reason: usage };
68654
69140
  const err = validateAuthAddLabel(label);
68655
69141
  if (err)
68656
69142
  return { kind: "help", reason: err };
68657
- return { kind: "add", label };
69143
+ for (const f of flags) {
69144
+ if (f !== "--replace") {
69145
+ return { kind: "help", reason: `Unknown flag \`${codeSpanSafe(f)}\` for \`/auth ${verb}\`.` };
69146
+ }
69147
+ }
69148
+ const replace2 = verb === "readd" || flags.has("--replace");
69149
+ return { kind: "add", label, replace: replace2 };
68658
69150
  }
68659
69151
  case "cancel":
68660
69152
  return { kind: "cancel" };
@@ -68774,6 +69266,7 @@ async function handleAuthCommand(parsed, ctx) {
68774
69266
  ` + ` \`/auth use <label>\` \u2014 admin: swap the fleet to <label>
68775
69267
  ` + ` \`/auth rotate\` \u2014 admin: cycle to next non-exhausted fallback
68776
69268
  ` + ` \`/auth add <label>\` \u2014 admin: OAuth-add a new Anthropic account from chat
69269
+ ` + ` \`/auth readd <label>\` \u2014 admin: re-auth an EXISTING account in place (widen scope)
68777
69270
  ` + ` \`/auth google add <email>\` \u2014 admin: Telegram-native Google account add/re-auth
68778
69271
  ` + ` \`/auth microsoft add <email>\` \u2014 admin: Telegram-native Microsoft account add/re-auth
68779
69272
  ` + ` \`/auth cancel\` \u2014 abort an \`/auth add\` or provider add in progress
@@ -68928,7 +69421,7 @@ async function handleAuthCommand(parsed, ctx) {
68928
69421
  };
68929
69422
  }
68930
69423
  if (ctx.chatId) {
68931
- pendingAuthRmFlows.set(ctx.chatId, {
69424
+ pendingAuthRmFlows2.set(ctx.chatId, {
68932
69425
  label: parsed.label,
68933
69426
  expiresAt: Date.now() + AUTH_RM_CONFIRM_TTL_MS
68934
69427
  });
@@ -68942,11 +69435,11 @@ async function handleAuthCommand(parsed, ctx) {
68942
69435
  };
68943
69436
  }
68944
69437
  if (parsed.kind === "rm-confirmed") {
68945
- const pending = ctx.chatId ? pendingAuthRmFlows.get(ctx.chatId) : undefined;
69438
+ const pending = ctx.chatId ? pendingAuthRmFlows2.get(ctx.chatId) : undefined;
68946
69439
  const now = Date.now();
68947
69440
  if (!pending || pending.label !== parsed.label || pending.expiresAt <= now) {
68948
69441
  if (ctx.chatId && pending && pending.expiresAt <= now) {
68949
- pendingAuthRmFlows.delete(ctx.chatId);
69442
+ pendingAuthRmFlows2.delete(ctx.chatId);
68950
69443
  }
68951
69444
  return {
68952
69445
  text: `**/auth rm:** no pending confirm for \`${codeSpanSafe(parsed.label)}\` (expired or not started). ` + `Send \`/auth rm ${codeSpanSafe(parsed.label)}\` first.`,
@@ -68954,7 +69447,7 @@ async function handleAuthCommand(parsed, ctx) {
68954
69447
  };
68955
69448
  }
68956
69449
  if (ctx.chatId)
68957
- pendingAuthRmFlows.delete(ctx.chatId);
69450
+ pendingAuthRmFlows2.delete(ctx.chatId);
68958
69451
  try {
68959
69452
  const data = await ctx.client.rmAccount(parsed.label);
68960
69453
  return {
@@ -69044,7 +69537,7 @@ ${failures.map((f) => ` ${f}`).join(`
69044
69537
  html: true
69045
69538
  };
69046
69539
  }
69047
- function isAuthAdmin(args) {
69540
+ function isAuthAdmin2(args) {
69048
69541
  return args.isAdmin === true;
69049
69542
  }
69050
69543
  function isAdmin(ctx) {
@@ -69626,6 +70119,15 @@ class AuthBrokerClient2 {
69626
70119
  }
69627
70120
  return parsed;
69628
70121
  }
70122
+ async getExternalSpend(forceLive) {
70123
+ const data = await this.send({
70124
+ v: PROTOCOL_VERSION,
70125
+ id: randomUUID4(),
70126
+ op: "get-external-spend",
70127
+ ...forceLive ? { forceLive: true } : {}
70128
+ });
70129
+ return data;
70130
+ }
69629
70131
  async setActive(account) {
69630
70132
  const data = await this.send({
69631
70133
  v: PROTOCOL_VERSION,
@@ -70119,6 +70621,12 @@ function readTokenFromCredentialsFile(credentialsFilePath) {
70119
70621
  }
70120
70622
  }
70121
70623
 
70624
+ // ../src/auth/via-claude.ts
70625
+ var PRE_PASTE_RULES = [
70626
+ { name: "theme", match: /Choose.{1,30}text.{1,30}style/, keys: ["Enter"] },
70627
+ { name: "login-method", match: /Select login method/, keys: ["Enter"] }
70628
+ ];
70629
+
70122
70630
  // gateway/auth-add-flow.ts
70123
70631
  function makeAuthAddTmuxOps(tmuxBin = "tmux") {
70124
70632
  return {
@@ -70144,6 +70652,11 @@ function makeAuthAddTmuxOps(tmuxBin = "tmux") {
70144
70652
  stdio: ["pipe", "pipe", "pipe"]
70145
70653
  });
70146
70654
  },
70655
+ sendKey(socket, session, key) {
70656
+ execFileSync4(tmuxBin, ["-L", socket, "send-keys", "-t", session, key], {
70657
+ stdio: ["pipe", "pipe", "pipe"]
70658
+ });
70659
+ },
70147
70660
  hasSession(socket, session) {
70148
70661
  try {
70149
70662
  execFileSync4(tmuxBin, ["-L", socket, "has-session", "-t", session], {
@@ -70216,10 +70729,11 @@ async function startAccountAuthSession(label, opts = {}) {
70216
70729
  throw new Error('tmux supervisor required for /auth add: SWITCHROOM_TMUX_SUPERVISOR is not set to "1". ' + "Legacy pipe-based setup-token is unsupported (setup-token writes to /dev/tty, not stdout/stderr).");
70217
70730
  }
70218
70731
  const home2 = opts.home ?? homedir9();
70219
- const urlTimeoutMs = opts.urlTimeoutMs ?? 12000;
70732
+ const urlTimeoutMs = opts.urlTimeoutMs ?? 30000;
70220
70733
  const agentName3 = opts.agentName ?? process.env.SWITCHROOM_AGENT_NAME ?? "gateway";
70221
70734
  const tmux = opts.tmuxOps ?? makeAuthAddTmuxOps(opts.tmuxBin);
70222
70735
  const binary = opts.claudeBinary ?? "claude";
70736
+ const mode = opts.mode ?? "via-claude";
70223
70737
  const scratchDir = pickScratchDir(label, home2);
70224
70738
  mkdirSync23(scratchDir, { recursive: true, mode: 448 });
70225
70739
  sweepOrphanSessions(home2, tmux);
@@ -70240,18 +70754,21 @@ ${tmuxSession}`, "utf8");
70240
70754
  sessionEnv["CLAUDE_CONFIG_DIR"] = scratchDir;
70241
70755
  if (process.env.XDG_CONFIG_HOME)
70242
70756
  sessionEnv["XDG_CONFIG_HOME"] = process.env.XDG_CONFIG_HOME;
70757
+ const sessionCmd = mode === "via-claude" ? binary : binary + " setup-token";
70758
+ const minterLabel = mode === "via-claude" ? "claude login" : "claude setup-token";
70243
70759
  try {
70244
- tmux.newSession(tmuxSocket, tmuxSession, sessionEnv, binary + " setup-token");
70760
+ tmux.newSession(tmuxSocket, tmuxSession, sessionEnv, sessionCmd);
70245
70761
  } catch (err) {
70246
70762
  cleanScratchDir(scratchDir);
70247
- throw new Error(`Failed to start tmux session for claude setup-token: ${err.message}`);
70763
+ throw new Error(`Failed to start tmux session for ${minterLabel}: ${err.message}`);
70248
70764
  }
70765
+ const preFired = new Set;
70249
70766
  const loginUrl = await new Promise((resolve7, reject) => {
70250
70767
  const deadline = setTimeout(() => {
70251
70768
  clearInterval(ticker);
70252
70769
  tmux.killSession(tmuxSocket, tmuxSession);
70253
70770
  cleanScratchDir(scratchDir);
70254
- reject(new Error(`claude setup-token did not print an OAuth URL within ${urlTimeoutMs}ms`));
70771
+ reject(new Error(`${minterLabel} did not print an OAuth URL within ${urlTimeoutMs}ms`));
70255
70772
  }, urlTimeoutMs);
70256
70773
  const ticker = setInterval(() => {
70257
70774
  const pane = tmux.capture(tmuxSocket, tmuxSession);
@@ -70259,9 +70776,20 @@ ${tmuxSession}`, "utf8");
70259
70776
  clearTimeout(deadline);
70260
70777
  clearInterval(ticker);
70261
70778
  cleanScratchDir(scratchDir);
70262
- reject(new Error("claude setup-token exited before printing OAuth URL"));
70779
+ reject(new Error(`${minterLabel} exited before printing OAuth URL`));
70263
70780
  return;
70264
70781
  }
70782
+ if (mode === "via-claude") {
70783
+ for (const rule of PRE_PASTE_RULES) {
70784
+ if (preFired.has(rule.name))
70785
+ continue;
70786
+ if (rule.match.test(pane)) {
70787
+ preFired.add(rule.name);
70788
+ for (const key of rule.keys)
70789
+ tmux.sendKey(tmuxSocket, tmuxSession, key);
70790
+ }
70791
+ }
70792
+ }
70265
70793
  const url = parseSetupTokenUrl(pane);
70266
70794
  if (url) {
70267
70795
  clearTimeout(deadline);
@@ -70270,12 +70798,14 @@ ${tmuxSession}`, "utf8");
70270
70798
  }
70271
70799
  }, 500);
70272
70800
  });
70273
- return { loginUrl, scratchDir, tmuxSocket, tmuxSession };
70801
+ return { loginUrl, scratchDir, tmuxSocket, tmuxSession, mode };
70274
70802
  }
70275
70803
  async function submitAccountAuthCode(flow3, code2, opts = {}) {
70276
70804
  const pollIntervalMs = opts.pollIntervalMs ?? 250;
70277
70805
  const pollTimeoutMs = opts.pollTimeoutMs ?? 300000;
70278
70806
  const tmux = opts.tmuxOps ?? makeAuthAddTmuxOps();
70807
+ const mode = flow3.mode ?? "via-claude";
70808
+ const blindEnterDelaysMs = opts.blindEnterDelaysMs ?? (mode === "via-claude" ? [1500, 3000, 5000] : []);
70279
70809
  const credentialsPath = join26(flow3.scratchDir, ".credentials.json");
70280
70810
  try {
70281
70811
  tmux.send(flow3.tmuxSocket, flow3.tmuxSession, code2);
@@ -70283,9 +70813,19 @@ async function submitAccountAuthCode(flow3, code2, opts = {}) {
70283
70813
  cleanScratchDir(flow3.scratchDir);
70284
70814
  throw new Error(`Failed to submit auth code to tmux session: ${err.message}`);
70285
70815
  }
70816
+ const pasteAt = Date.now();
70817
+ const blindEnters = blindEnterDelaysMs.map((d) => ({ at: pasteAt + d, fired: false }));
70286
70818
  const deadline = Date.now() + pollTimeoutMs;
70287
70819
  while (Date.now() < deadline) {
70288
70820
  await new Promise((r) => setTimeout(r, pollIntervalMs));
70821
+ for (const be of blindEnters) {
70822
+ if (!be.fired && Date.now() >= be.at) {
70823
+ be.fired = true;
70824
+ try {
70825
+ tmux.sendKey(flow3.tmuxSocket, flow3.tmuxSession, "Enter");
70826
+ } catch {}
70827
+ }
70828
+ }
70289
70829
  if (existsSync22(credentialsPath)) {
70290
70830
  const token = readTokenFromCredentialsFile(credentialsPath);
70291
70831
  if (token) {
@@ -70315,6 +70855,59 @@ function cancelAccountAuthSession(flow3, tmuxOps) {
70315
70855
  tmux.killSession(flow3.tmuxSocket, flow3.tmuxSession);
70316
70856
  cleanScratchDir(flow3.scratchDir);
70317
70857
  }
70858
+ async function handleAuthAddOrCancel(opts) {
70859
+ const { parsed, isAdmin: isAdmin2, currentAgent, chatId, threadId, reply, escapeHtml: escapeHtml5 } = opts;
70860
+ if (!isAuthAdmin({ isAdmin: isAdmin2 })) {
70861
+ await reply(`**Not authorized.** \`/auth ${parsed.kind}\` is admin-only.
70862
+ ` + `Set \`admin: true\` on this agent in switchroom.yaml to unlock ` + `(the same flag that gates \`/agents\`, \`/restart\`, ` + `\`/update\` etc.).`);
70863
+ return;
70864
+ }
70865
+ const authAddKey = chatKey(chatId, threadId);
70866
+ if (parsed.kind === "cancel") {
70867
+ const existing = pendingAuthAddFlows.get(authAddKey);
70868
+ if (!existing) {
70869
+ await reply("_No pending `/auth add` flow in this chat._");
70870
+ return;
70871
+ }
70872
+ cancelAccountAuthSession(existing);
70873
+ pendingAuthAddFlows.delete(authAddKey);
70874
+ await reply("Cancelled.");
70875
+ return;
70876
+ }
70877
+ if (pendingAuthAddFlows.has(authAddKey)) {
70878
+ await reply("_An `/auth add` flow is already in progress for this chat. " + "Finish the paste, or send `/auth cancel` to abort._");
70879
+ return;
70880
+ }
70881
+ const precheckErr = await runReaddPrecheck(() => getAuthBrokerClient(currentAgent), parsed.label, parsed.replace);
70882
+ if (precheckErr) {
70883
+ await reply(precheckErr);
70884
+ return;
70885
+ }
70886
+ try {
70887
+ const { loginUrl, scratchDir, tmuxSocket, tmuxSession, mode } = await startAccountAuthSession(parsed.label);
70888
+ pendingAuthAddFlows.set(authAddKey, {
70889
+ label: parsed.label,
70890
+ scratchDir,
70891
+ tmuxSocket,
70892
+ tmuxSession,
70893
+ startedAt: Date.now(),
70894
+ replace: parsed.replace,
70895
+ mode
70896
+ });
70897
+ const verbNoun = parsed.replace ? "Re-authenticating account" : "Adding account";
70898
+ await reply(`**${verbNoun}** \`${parsed.label}\`
70899
+
70900
+ ` + `1. Open this URL on your phone:
70901
+ ${loginUrl}
70902
+
70903
+ ` + `2. Log into Anthropic, copy the code Claude shows.
70904
+ ` + `3. Paste it back here.
70905
+
70906
+ ` + `Send \`/auth cancel\` to abort.`);
70907
+ } catch (err) {
70908
+ await reply(`**/auth ${parsed.replace ? "readd" : "add"} failed:** ${escapeHtml5(err?.message ?? String(err))}`);
70909
+ }
70910
+ }
70318
70911
 
70319
70912
  // gateway/auth-loopback-relay.ts
70320
70913
  import { spawn } from "node:child_process";
@@ -73154,6 +73747,23 @@ ${input.newReplyText}`;
73154
73747
 
73155
73748
  // flushed-turn-supersede.ts
73156
73749
  var DEFAULT_SUPERSEDE_TTL_MS2 = 60000;
73750
+ var SUPERSEDE_MATCH_MIN_CONTAINMENT_CHARS2 = 32;
73751
+ function normalizeForMatch2(text4) {
73752
+ return text4.replace(/\s+/g, " ").trim();
73753
+ }
73754
+ function flushedAnswerMatchesReply2(flushedText, replyText) {
73755
+ const flushed = normalizeForMatch2(flushedText);
73756
+ const reply = normalizeForMatch2(replyText);
73757
+ if (flushed.length === 0 || reply.length === 0)
73758
+ return false;
73759
+ if (flushed === reply)
73760
+ return true;
73761
+ if (reply.length >= SUPERSEDE_MATCH_MIN_CONTAINMENT_CHARS2 && flushed.includes(reply))
73762
+ return true;
73763
+ if (flushed.length >= SUPERSEDE_MATCH_MIN_CONTAINMENT_CHARS2 && reply.includes(flushed))
73764
+ return true;
73765
+ return false;
73766
+ }
73157
73767
  function decideSupersede2(record, args) {
73158
73768
  const ttlMs = args.ttlMs ?? DEFAULT_SUPERSEDE_TTL_MS2;
73159
73769
  if (record == null)
@@ -73165,7 +73775,15 @@ function decideSupersede2(record, args) {
73165
73775
  if (!sameTurn) {
73166
73776
  return { supersede: false, deleteMessageIds: [], reason: "different-turn" };
73167
73777
  }
73168
- return { supersede: true, deleteMessageIds: [...record.messageIds], reason: "supersede" };
73778
+ if (args.replyText != null && !flushedAnswerMatchesReply2(record.text, args.replyText)) {
73779
+ return { supersede: false, deleteMessageIds: [], reason: "new-content", recordText: record.text };
73780
+ }
73781
+ return {
73782
+ supersede: true,
73783
+ deleteMessageIds: [...record.messageIds],
73784
+ reason: "supersede",
73785
+ recordText: record.text
73786
+ };
73169
73787
  }
73170
73788
  function decideSupersedeCorrection(input) {
73171
73789
  const eligible = input.flushMessageIds.length === 1 && input.chunkCount === 1 && !input.hasFiles && !input.suppressText && !input.hasOpenPreview;
@@ -73204,7 +73822,12 @@ class FlushedTurnSupersedeRegistry2 {
73204
73822
  }
73205
73823
  peek(chatId, threadId, args) {
73206
73824
  const rec = this.entries.get(makeKey3(chatId, threadId))?.get(turnKey2(args.liveTurnId));
73207
- return decideSupersede2(rec, { liveTurnId: args.liveTurnId, now: args.now, ttlMs: this.ttlMs });
73825
+ return decideSupersede2(rec, {
73826
+ liveTurnId: args.liveTurnId,
73827
+ replyText: args.replyText,
73828
+ now: args.now,
73829
+ ttlMs: this.ttlMs
73830
+ });
73208
73831
  }
73209
73832
  take(chatId, threadId, args) {
73210
73833
  const lane = makeKey3(chatId, threadId);
@@ -73253,7 +73876,9 @@ function decideAnswerLatchSuppression(input) {
73253
73876
  return false;
73254
73877
  if (!input.isLateReply)
73255
73878
  return false;
73256
- return input.ownerAnswerDelivered;
73879
+ if (input.replyMatchesFlushedAnswer === false)
73880
+ return false;
73881
+ return input.ownerAnswerDelivered === "flush";
73257
73882
  }
73258
73883
 
73259
73884
  // telegraph.ts
@@ -73854,31 +74479,40 @@ async function sendReply(deps, req) {
73854
74479
  const replyThreadId = args.message_thread_id != null ? Number(args.message_thread_id) : undefined;
73855
74480
  const ownerTurn = resolveReplyOwnerTurn(turn, chat_id, args);
73856
74481
  const resolvedTurnId = ownerTurn?.turnId ?? null;
73857
- const decision = flushedTurnSupersede.take(chat_id, replyThreadId, { liveTurnId: resolvedTurnId, now: Date.now() });
74482
+ const decision = flushedTurnSupersede.take(chat_id, replyThreadId, { liveTurnId: resolvedTurnId, replyText: text4, now: Date.now() });
73858
74483
  if (decision.supersede) {
73859
74484
  process.stderr.write(`telegram gateway: reply: superseding flushed turn message(s) ` + `chatId=${chat_id} ids=${JSON.stringify(decision.deleteMessageIds)}
73860
74485
  `);
73861
74486
  supersedeFlushIds = decision.deleteMessageIds;
73862
- if (ownerTurn != null)
73863
- ownerTurn.answerDelivered = true;
74487
+ if (ownerTurn != null) {
74488
+ ownerTurn.answerDelivered = "flush";
74489
+ if (decision.recordText != null)
74490
+ ownerTurn.flushedAnswerText = decision.recordText;
74491
+ }
73864
74492
  } else {
73865
74493
  const replySubstantive = isSubstantiveFinalReply({
73866
74494
  text: rawText,
73867
74495
  disableNotification: args.disable_notification === true
73868
74496
  });
74497
+ const replyMatchesFlushedAnswer = decision.reason === "new-content" ? false : ownerTurn?.flushedAnswerText != null ? flushedAnswerMatchesReply2(ownerTurn.flushedAnswerText, text4) : null;
73869
74498
  const suppressByLatch = decideAnswerLatchSuppression({
73870
74499
  superseded: false,
73871
74500
  replySubstantive,
73872
74501
  isLateReply: turn == null,
73873
- ownerAnswerDelivered: ownerTurn?.answerDelivered ?? false
74502
+ ownerAnswerDelivered: ownerTurn?.answerDelivered ?? false,
74503
+ replyMatchesFlushedAnswer
73874
74504
  });
74505
+ if (decision.reason === "new-content") {
74506
+ process.stderr.write(`telegram gateway: reply: flush supersede declined \u2014 new content (#3429) ` + `chatId=${chat_id} ownerTurnId=${JSON.stringify(resolvedTurnId)}; sending fresh
74507
+ `);
74508
+ }
73875
74509
  if (suppressByLatch) {
73876
74510
  process.stderr.write(`telegram gateway: reply: suppressed by answer-delivered latch ` + `(flush already delivered this turn's answer) chatId=${chat_id} ` + `ownerTurnId=${JSON.stringify(resolvedTurnId)}
73877
74511
  `);
73878
74512
  return { content: [{ type: "text", text: "sent (deduped \u2014 answer already delivered via turn-flush)" }] };
73879
74513
  }
73880
74514
  if (replySubstantive && ownerTurn != null) {
73881
- ownerTurn.answerDelivered = true;
74515
+ ownerTurn.answerDelivered = "reply";
73882
74516
  }
73883
74517
  }
73884
74518
  }
@@ -75853,6 +76487,7 @@ function handleSessionEvent(deps, ev) {
75853
76487
  finalAnswerSubstantive: false,
75854
76488
  finalAnswerEverDelivered: false,
75855
76489
  answerDelivered: false,
76490
+ flushedAnswerText: null,
75856
76491
  endedAt: null,
75857
76492
  firstPingAt: null,
75858
76493
  firstPingWasSubstantive: false,
@@ -75952,7 +76587,7 @@ function handleSessionEvent(deps, ev) {
75952
76587
  if (turn != null) {
75953
76588
  turn.currentModel = ev.model;
75954
76589
  }
75955
- sessionModelSource.noteTranscriptModel(ev.model);
76590
+ sessionModelSource.noteTranscriptModel(ev.model, { replayed: ev.replayed === true });
75956
76591
  return;
75957
76592
  }
75958
76593
  case "usage": {
@@ -76358,7 +76993,8 @@ function handleSessionEvent(deps, ev) {
76358
76993
  }
76359
76994
  turn.finalAnswerDelivered = true;
76360
76995
  turn.finalAnswerSubstantive = true;
76361
- turn.answerDelivered = true;
76996
+ turn.answerDelivered = "flush";
76997
+ turn.flushedAnswerText = capturedText;
76362
76998
  const backstopLatchClaimed = backstopDeliveryLedger.claim(turn.turnId);
76363
76999
  const cardTakeover = progressDriver?.takeOverCard({
76364
77000
  chatId: backstopChatId,
@@ -76430,6 +77066,7 @@ function handleSessionEvent(deps, ev) {
76430
77066
  backstopCtrl.finalize("error");
76431
77067
  backstopDeliveryLedger.release(turn.turnId);
76432
77068
  turn.answerDelivered = false;
77069
+ turn.flushedAnswerText = null;
76433
77070
  } else if (backstopCtrl) {
76434
77071
  backstopCtrl.finalize("done");
76435
77072
  }
@@ -76447,6 +77084,7 @@ function handleSessionEvent(deps, ev) {
76447
77084
  `);
76448
77085
  if (!delivered) {
76449
77086
  turn.answerDelivered = false;
77087
+ turn.flushedAnswerText = null;
76450
77088
  backstopDeliveryLedger.release(turn.turnId);
76451
77089
  if (backstopCtrl)
76452
77090
  backstopCtrl.finalize("error");
@@ -78479,8 +79117,10 @@ function formatQuotaBlock(q, now = new Date) {
78479
79117
  const lines = [];
78480
79118
  lines.push("**Claude plan quota**");
78481
79119
  lines.push("");
78482
- lines.push(`**5h window** \`${Math.round(q.fiveHourUtilizationPct)}%\` \u00b7 \`${formatResetRelative2(q.fiveHourResetAt, now)}\``);
78483
- lines.push(`**7d window** \`${Math.round(q.sevenDayUtilizationPct)}%\` \u00b7 \`${formatResetRelative2(q.sevenDayResetAt, now)}\``);
79120
+ const fiveHour = q.fiveHourUtilPresent === false ? "no data" : `\`${Math.round(q.fiveHourUtilizationPct)}%\``;
79121
+ const sevenDay = q.sevenDayUtilPresent === false ? "no data" : `\`${Math.round(q.sevenDayUtilizationPct)}%\``;
79122
+ lines.push(`**5h window** ${fiveHour} \u00b7 \`${formatResetRelative2(q.fiveHourResetAt, now)}\``);
79123
+ lines.push(`**7d window** ${sevenDay} \u00b7 \`${formatResetRelative2(q.sevenDayResetAt, now)}\``);
78484
79124
  if (q.representativeClaim) {
78485
79125
  lines.push("");
78486
79126
  lines.push(`_Binding window: ${q.representativeClaim.replace(/_/g, " ")}_`);
@@ -79172,6 +79812,100 @@ function classifyModelSwitchConfirmation(input) {
79172
79812
  }
79173
79813
  return { kind: "default", launched: revertedTo };
79174
79814
  }
79815
+ function formatModelRelaunchDiagLog(input) {
79816
+ const { agent, launched, configured, confirmation, isApplyBoot } = input;
79817
+ const L = launched || "(none)";
79818
+ if (confirmation == null) {
79819
+ return "telegram gateway: gw /model relaunch applied agent=" + agent + " launched=" + L + " configured=" + configured + " override=" + (isApplyBoot ? "set" : "cleared") + `
79820
+ `;
79821
+ }
79822
+ if (confirmation.kind === "not-applied") {
79823
+ return "telegram gateway: gw /model relaunch NOT-APPLIED agent=" + agent + " target=" + confirmation.target + " launched=" + L + " configured=" + configured + " revertedTo=" + confirmation.revertedTo + `
79824
+ `;
79825
+ }
79826
+ if (confirmation.kind === "applied") {
79827
+ return "telegram gateway: gw /model relaunch applied agent=" + agent + " launched=" + L + " configured=" + configured + ` override=set outcome=applied
79828
+ `;
79829
+ }
79830
+ return "telegram gateway: gw /model relaunch applied agent=" + agent + " launched=" + L + " configured=" + configured + ` override=cleared outcome=default
79831
+ `;
79832
+ }
79833
+ function formatModelSwitchConfirmationBody(confirmation) {
79834
+ if (confirmation.kind === "applied") {
79835
+ return "\u2705 Now running `" + confirmation.launched + "` \u2014 session-only, reverts to the configured model on the next restart. Fresh session; memory and the handoff briefing carry the context.";
79836
+ }
79837
+ if (confirmation.kind === "not-applied") {
79838
+ return "\u26a0\ufe0f Your switch to `" + confirmation.target + "` didn't apply \u2014 the agent reverted to `" + confirmation.revertedTo + "` (the apply-boot didn't complete). Re-issue `/model " + confirmation.target + "` to try again.";
79839
+ }
79840
+ return "\u2705 Now running `" + confirmation.launched + "` (the configured default) \u2014 fresh session; memory and the handoff briefing carry the context.";
79841
+ }
79842
+ function formatModelRelaunchSuppressNotAppliedLog(input) {
79843
+ return "telegram gateway: gw /model relaunch NOT-APPLIED \u2014 suppressing not-applied confirmation (a .session-model-alert is present and will be relayed) agent=" + input.agent + " target=" + input.target + `
79844
+ `;
79845
+ }
79846
+ function resolveModelSwitchBootNotice(input) {
79847
+ const { agent, confirmation, hasSessionModelAlert } = input;
79848
+ if (confirmation.kind === "not-applied" && hasSessionModelAlert) {
79849
+ return {
79850
+ kind: "suppress",
79851
+ log: formatModelRelaunchSuppressNotAppliedLog({ agent, target: confirmation.target })
79852
+ };
79853
+ }
79854
+ return { kind: "card", body: formatModelSwitchConfirmationBody(confirmation) };
79855
+ }
79856
+ function servedModelMatchesRequested(requested, served) {
79857
+ const req = requested.trim().toLowerCase();
79858
+ const srv = served.trim().toLowerCase();
79859
+ if (!srv.startsWith("claude-"))
79860
+ return true;
79861
+ if (MODEL_ALIASES2.includes(req)) {
79862
+ if (req === "default")
79863
+ return true;
79864
+ if (modelFamilyToken(srv) === req)
79865
+ return true;
79866
+ return srv.slice("claude-".length).split("-").includes(req);
79867
+ }
79868
+ if (req.startsWith("claude-")) {
79869
+ return srv === req || srv.startsWith(req + "-");
79870
+ }
79871
+ return true;
79872
+ }
79873
+ function formatServedModelDivergenceLog(input) {
79874
+ return "telegram gateway: gw /model served-model DIVERGENCE agent=" + input.agent + " requested=" + input.requested + " served=" + input.served + ` (--fallback-model substituted: requested id invalid/unknown OR model transiently unavailable)
79875
+ `;
79876
+ }
79877
+ function formatServedModelDivergenceCard(input) {
79878
+ return "\u26a0\ufe0f The first reply was served by `" + input.served + "`, not the requested `" + input.requested + "` \u2014 claude substituted the fallback model. Either the requested id is invalid/unknown, or the model was temporarily unavailable for that call (a transient substitution self-corrects on later replies). If it persists, re-issue `/model <valid id>` or `/model default`. `/status` always shows the model actually serving calls.";
79879
+ }
79880
+ function sendModelBootCard(deps, chat, body, failLabel) {
79881
+ deps.sendCard(chat.chatId, body, {
79882
+ parse_mode: "Markdown",
79883
+ ...chat.threadId != null ? { message_thread_id: chat.threadId } : {}
79884
+ }).catch((err) => deps.log(`telegram gateway: ${failLabel} send failed: ${err?.message ?? String(err)}
79885
+ `));
79886
+ }
79887
+ function buildServedModelDivergenceHandler(deps) {
79888
+ return (d) => {
79889
+ deps.log(formatServedModelDivergenceLog({ agent: deps.agent, requested: d.requested, served: d.served }));
79890
+ if (deps.chat == null)
79891
+ return;
79892
+ sendModelBootCard(deps, deps.chat, formatServedModelDivergenceCard(d), "served-model divergence");
79893
+ };
79894
+ }
79895
+ function deliverModelSwitchBootNotice(deps) {
79896
+ if (deps.chat == null)
79897
+ return;
79898
+ const notice = resolveModelSwitchBootNotice({
79899
+ agent: deps.agent,
79900
+ confirmation: deps.confirmation,
79901
+ hasSessionModelAlert: deps.hasSessionModelAlert
79902
+ });
79903
+ if (notice.kind === "suppress") {
79904
+ deps.log(notice.log);
79905
+ return;
79906
+ }
79907
+ sendModelBootCard(deps, deps.chat, notice.body, "model-switch confirmation");
79908
+ }
79175
79909
  function resolveStaleAwareBusy(input) {
79176
79910
  const turnStale = input.currentTurnActive && input.turnAgeMs !== null && input.turnAgeMs > input.hardTtlMs;
79177
79911
  const approvalLive = input.oldestPendingApprovalAgeMs !== null && input.oldestPendingApprovalAgeMs <= input.hardTtlMs;
@@ -79240,14 +79974,23 @@ function relaunchErrorReply2(deps, model, err) {
79240
79974
  const msg = err instanceof Error ? err.message : String(err);
79241
79975
  return { text: `\u274c Could not schedule model switch: ${deps.escapeHtml(msg)}`, html: true };
79242
79976
  }
79977
+ function unvalidatedIdCaveat2(deps, model) {
79978
+ if (!model.trim().toLowerCase().startsWith("claude-"))
79979
+ return null;
79980
+ return `_\`${deps.escapeHtml(model)}\` can't be validated before launch \u2014 if it isn't a real Claude model id, claude will silently serve the configured fallback model instead. I check the first reply and will warn if that happens._`;
79981
+ }
79243
79982
  async function scheduleRelaunchReply2(deps, model, reason) {
79244
79983
  try {
79245
79984
  await deps.scheduleModelRelaunch(model, reason);
79246
79985
  } catch (err) {
79247
79986
  return relaunchErrorReply2(deps, model, err);
79248
79987
  }
79249
- return { text: [switchingLine2(deps, model), PERSIST_NOTE3].join(`
79250
- `), html: true };
79988
+ const caveat = unvalidatedIdCaveat2(deps, model);
79989
+ return {
79990
+ text: [switchingLine2(deps, model), ...caveat ? [caveat] : [], PERSIST_NOTE3].join(`
79991
+ `),
79992
+ html: true
79993
+ };
79251
79994
  }
79252
79995
  async function scheduleDefaultRelaunchReply2(deps, reason) {
79253
79996
  try {
@@ -80073,7 +80816,7 @@ var HINDSIGHT_HEALTHCHECK_PY = 'import urllib.request,sys; sys.exit(0 if urllib.
80073
80816
  var HINDSIGHT_HEALTHCHECK_CMD = `python3 -c '${HINDSIGHT_HEALTHCHECK_PY}'`;
80074
80817
 
80075
80818
  // ../src/memory/hindsight.ts
80076
- var DEFAULT_RETAIN_MISSION = "Extract user preferences, ongoing projects, recurring commitments, " + "important context, and durable facts that should help across future " + "conversations. Skip one-off chatter and temporary task noise.";
80819
+ var DEFAULT_RETAIN_MISSION = "Extract user preferences, ongoing projects, recurring commitments, " + "important context, and durable facts that should help across future " + "conversations. Skip one-off chatter and temporary task noise, " + "including in-flight workflow/process narration (a sub-task started, " + "paused, or is still running) \u2014 only retain the outcome once a task " + "actually completes or a decision is made.";
80077
80820
  var PROFILE_MEMORY_DEFAULTS = {
80078
80821
  "health-coach": {
80079
80822
  disposition: { skepticism: 2, literalism: 2, empathy: 5 },
@@ -84201,6 +84944,17 @@ function withStoreLock(path2, fn) {
84201
84944
  }));
84202
84945
  return run3;
84203
84946
  }
84947
+ var pinReconcileTails2 = new Map;
84948
+ function withPinReconcileLock(pinKey, fn) {
84949
+ const prev = pinReconcileTails2.get(pinKey) ?? Promise.resolve();
84950
+ const run3 = prev.then(fn, fn);
84951
+ pinReconcileTails2.set(pinKey, run3.then(() => {
84952
+ return;
84953
+ }, () => {
84954
+ return;
84955
+ }));
84956
+ return run3;
84957
+ }
84204
84958
  function applyStatusPinRow(path2, fs2, pinKey, row, log) {
84205
84959
  const current = loadStatusPins(path2, fs2);
84206
84960
  const others = current.filter((p) => p.pinKey !== pinKey);
@@ -84227,7 +84981,11 @@ function reconcileAndPersistStatusPin(args) {
84227
84981
  return next2;
84228
84982
  }
84229
84983
  const next = await args.applyPin();
84230
- applyStatusPinRow(path2, fs2, pinKey, null, log);
84984
+ if (next == null) {
84985
+ applyStatusPinRow(path2, fs2, pinKey, null, log);
84986
+ } else {
84987
+ applyStatusPinRow(path2, fs2, pinKey, { pinKey, chatId, messageId: next.messageId }, log);
84988
+ }
84231
84989
  return next;
84232
84990
  });
84233
84991
  }
@@ -92377,10 +93135,10 @@ function effectiveTurnAgeMs(markerAgeMs, turnStartedAt, now) {
92377
93135
  }
92378
93136
 
92379
93137
  // ../src/build-info.ts
92380
- var VERSION = "0.19.1";
92381
- var COMMIT_SHA = "370b7c4b";
92382
- var COMMIT_DATE = "2026-07-19T03:50:00Z";
92383
- var LATEST_PR = 3406;
93138
+ var VERSION = "0.19.3";
93139
+ var COMMIT_SHA = "41896be4";
93140
+ var COMMIT_DATE = "2026-07-20T10:37:39+10:00";
93141
+ var LATEST_PR = 3456;
92384
93142
  var COMMITS_AHEAD_OF_TAG = 0;
92385
93143
 
92386
93144
  // gateway/boot-version.ts
@@ -95198,7 +95956,7 @@ var progressUpdateLastSent = new Map;
95198
95956
  var progressUpdateTurnCount = new Map;
95199
95957
  var currentTurn = null;
95200
95958
  var currentTurnMap = new CurrentTurnMap;
95201
- var sessionModelSource = createSessionModelSource();
95959
+ var sessionModelSource = createSessionModelSource({ servedMatchesRequested: servedModelMatchesRequested });
95202
95960
  var lastActiveTurnChatId;
95203
95961
  function setCurrentTurn(turn, key) {
95204
95962
  currentTurnMap.set(turn, key);
@@ -96990,9 +97748,9 @@ var pendingStateReaper = isGatewayMain ? setInterval(() => {
96990
97748
  if (now - v > 60000)
96991
97749
  lastAuthRefreshAtMs.delete(k);
96992
97750
  }
96993
- for (const [k, v] of pendingAuthRmFlows) {
97751
+ for (const [k, v] of pendingAuthRmFlows2) {
96994
97752
  if (now >= v.expiresAt)
96995
- pendingAuthRmFlows.delete(k);
97753
+ pendingAuthRmFlows2.delete(k);
96996
97754
  }
96997
97755
  pendingVaultOps.sweep(now);
96998
97756
  sweepPermissionTtl({
@@ -97645,7 +98403,7 @@ var midSessionCardReaper = isGatewayMain ? setInterval(() => {
97645
98403
  midSessionCardReaper?.unref();
97646
98404
  async function reconcileStatusPin(pinKey, chatId, desired) {
97647
98405
  try {
97648
- await reconcileStatusPinInner(pinKey, chatId, desired);
98406
+ await withPinReconcileLock(pinKey, () => reconcileStatusPinInner(pinKey, chatId, desired));
97649
98407
  } catch (err) {
97650
98408
  const msg = err instanceof Error ? err.message : String(err);
97651
98409
  process.stderr.write(`telegram gateway: status-pin reconcile absorbed error (key=${pinKey} chat=${chatId}): ${msg}
@@ -100546,6 +101304,10 @@ function isAuthorizedSender(ctx) {
100546
101304
  }
100547
101305
  return false;
100548
101306
  }
101307
+ function shouldMaskAccountLabels(ctx) {
101308
+ const groupAllowFrom = ctx.chat?.type === "group" || ctx.chat?.type === "supergroup" ? loadAccess().groups[String(ctx.chat.id)]?.allowFrom : undefined;
101309
+ return shouldMaskUsageLabels(ctx.chat?.type, groupAllowFrom);
101310
+ }
100549
101311
  var __inboundRouterTestSeam = {
100550
101312
  activeStatusReactions,
100551
101313
  activeTurnStartedAt,
@@ -103832,7 +104594,7 @@ The gateway will restart as part of the recreate step; watch for the post-restar
103832
104594
  const me = cfg?.agents?.[getMyAgentName()];
103833
104595
  isAdmin2 = me?.admin === true || me?.root === true;
103834
104596
  } catch {}
103835
- if (!isAuthAdmin({ isAdmin: isAdmin2 })) {
104597
+ if (!isAuthAdmin2({ isAdmin: isAdmin2 })) {
103836
104598
  await switchroomReply(ctx, `**Not authorized.** \`/connect\` requires this agent to have \`admin: true\` in switchroom.yaml.`, { html: true });
103837
104599
  return;
103838
104600
  }
@@ -103923,52 +104685,19 @@ ${appNote}`), {
103923
104685
  } catch {}
103924
104686
  const chatId = String(ctx.chat?.id ?? "");
103925
104687
  if (parsed.kind === "add" || parsed.kind === "cancel") {
103926
- if (!isAuthAdmin({ isAdmin: isAdmin2 })) {
103927
- await switchroomReply(ctx, `**Not authorized.** \`/auth ${parsed.kind}\` is admin-only.
103928
- Set \`admin: true\` on this agent in switchroom.yaml to unlock (the same flag that gates \`/agents\`, \`/restart\`, \`/update\` etc.).`, { html: true });
103929
- return;
103930
- }
103931
- const authAddKey = chatKey2(chatId, ctx.message?.message_thread_id ?? null);
103932
- if (parsed.kind === "cancel") {
103933
- const existing = pendingAuthAddFlows.get(authAddKey);
103934
- if (!existing) {
103935
- await switchroomReply(ctx, "_No pending `/auth add` flow in this chat._", { html: true });
103936
- return;
103937
- }
103938
- cancelAccountAuthSession(existing);
103939
- pendingAuthAddFlows.delete(authAddKey);
103940
- await switchroomReply(ctx, "Cancelled.", { html: true });
103941
- return;
103942
- }
103943
- if (pendingAuthAddFlows.has(authAddKey)) {
103944
- await switchroomReply(ctx, "_An `/auth add` flow is already in progress for this chat. Finish the paste, or send `/auth cancel` to abort._", { html: true });
103945
- return;
103946
- }
103947
- try {
103948
- const { loginUrl, scratchDir, tmuxSocket, tmuxSession } = await startAccountAuthSession(parsed.label);
103949
- pendingAuthAddFlows.set(authAddKey, {
103950
- label: parsed.label,
103951
- scratchDir,
103952
- tmuxSocket,
103953
- tmuxSession,
103954
- startedAt: Date.now()
103955
- });
103956
- await switchroomReply(ctx, `**Adding account** \`${parsed.label}\`
103957
-
103958
- 1. Open this URL on your phone:
103959
- ${loginUrl}
103960
-
103961
- 2. Log into Anthropic, copy the code Claude shows.
103962
- 3. Paste it back here.
103963
-
103964
- Send \`/auth cancel\` to abort.`, { html: true });
103965
- } catch (err) {
103966
- await switchroomReply(ctx, `**/auth add failed:** ${escapeHtmlForTg2(err?.message ?? String(err))}`, { html: true });
103967
- }
104688
+ await handleAuthAddOrCancel({
104689
+ parsed,
104690
+ isAdmin: isAdmin2,
104691
+ currentAgent,
104692
+ chatId,
104693
+ threadId: ctx.message?.message_thread_id ?? null,
104694
+ reply: (text6) => switchroomReply(ctx, text6, { html: true }),
104695
+ escapeHtml: escapeHtmlForTg2
104696
+ });
103968
104697
  return;
103969
104698
  }
103970
104699
  if (parsed.kind === "provider-add" || parsed.kind === "provider-cancel") {
103971
- if (!isAuthAdmin({ isAdmin: isAdmin2 })) {
104700
+ if (!isAuthAdmin2({ isAdmin: isAdmin2 })) {
103972
104701
  await switchroomReply(ctx, `**Not authorized.** \`/auth ${parsed.provider}\` is admin-only.
103973
104702
  Set \`admin: true\` on this agent in switchroom.yaml to unlock.`, { html: true });
103974
104703
  return;
@@ -104403,7 +105132,7 @@ Send \`/auth ${parsed.provider} cancel\` to abort.`, { html: true });
104403
105132
  bot2.command("usage", async (ctx) => {
104404
105133
  if (!isAuthorizedSender(ctx))
104405
105134
  return;
104406
- const demo = hasDemoFlag(getCommandArgs(ctx));
105135
+ const demo = hasDemoFlag(getCommandArgs(ctx)) || shouldMaskAccountLabels(ctx);
104407
105136
  const currentAgent = getMyAgentName();
104408
105137
  try {
104409
105138
  const client3 = await getAuthBrokerClient2(currentAgent);
@@ -104416,11 +105145,14 @@ Send \`/auth ${parsed.provider} cancel\` to abort.`, { html: true });
104416
105145
  const { buildSnapshotsFromState: buildSnapshotsFromState4, buildSnapshotKeyboard: buildSnapshotKeyboard3 } = await Promise.resolve().then(() => (init_auth_snapshot_format2(), exports_auth_snapshot_format2));
104417
105146
  const { renderUsageCard: renderUsageCard2 } = await Promise.resolve().then(() => (init_quota_bar_format(), exports_quota_bar_format));
104418
105147
  const snapshots = buildSnapshotsFromState4(state7, quotas);
105148
+ const { fetchExternalSpendSummary: fetchExternalSpendSummary2 } = await Promise.resolve().then(() => (init_external_spend3(), exports_external_spend));
105149
+ const externalSpend = await fetchExternalSpendSummary2(renderNow).catch(() => null);
104419
105150
  const exhaustedByLabel = new Map(state7.accounts.map((a) => [a.label, a.exhausted]));
104420
105151
  const text5 = renderUsageCard2(snapshots, exhaustedByLabel, {
104421
105152
  now: renderNow,
104422
105153
  demo,
104423
- ...staleCachedAtMs != null ? { staleCachedAtMs } : probeResp.results.length > 0 ? { liveProbedAtMs: renderNow.getTime() } : { probeFailed: true }
105154
+ externalSpend,
105155
+ ...deriveUsageFooterFreshness2(probeResp.results, staleCachedAtMs, renderNow.getTime())
104424
105156
  });
104425
105157
  let kbRows = buildSnapshotKeyboard3(snapshots, { now: new Date, demo });
104426
105158
  if (ctx.chat?.type !== "private") {
@@ -105749,28 +106481,34 @@ async function startGateway() {
105749
106481
  return resolveMainModel(raw ?? undefined);
105750
106482
  })();
105751
106483
  const isApplyBoot = launched.length > 0 && launched !== configured;
105752
- sessionModelSource.setOverride(isApplyBoot ? launched : null);
105753
- process.stderr.write(`telegram gateway: gw /model relaunch applied agent=${getMyAgentName()} launched=${launched || "(none)"} configured=${configured} override=${isApplyBoot ? "set" : "cleared"}
105754
- `);
105755
- if (modelSwitchReason != null && modelSwitchMarkerChat) {
105756
- const chat = modelSwitchMarkerChat;
105757
- const confirmation = classifyModelSwitchConfirmation({
105758
- reason: modelSwitchReason,
105759
- launched,
105760
- configured
106484
+ sessionModelSource.setOverride(isApplyBoot ? launched : null, { verify: true });
106485
+ const modelBootCardDeps = {
106486
+ agent: getMyAgentName(),
106487
+ chat: modelSwitchMarkerChat,
106488
+ log: (line) => process.stderr.write(line),
106489
+ sendCard: (chatId, body, opts) => lockedBot.api.sendMessage(chatId, body, opts)
106490
+ };
106491
+ if (isApplyBoot) {
106492
+ sessionModelSource.setDivergenceHandler(buildServedModelDivergenceHandler(modelBootCardDeps));
106493
+ }
106494
+ const confirmation = modelSwitchReason != null ? classifyModelSwitchConfirmation({
106495
+ reason: modelSwitchReason,
106496
+ launched,
106497
+ configured
106498
+ }) : null;
106499
+ process.stderr.write(formatModelRelaunchDiagLog({
106500
+ agent: getMyAgentName(),
106501
+ launched,
106502
+ configured,
106503
+ confirmation,
106504
+ isApplyBoot
106505
+ }));
106506
+ if (confirmation != null) {
106507
+ deliverModelSwitchBootNotice({
106508
+ ...modelBootCardDeps,
106509
+ confirmation,
106510
+ hasSessionModelAlert: existsSync54(join59(smAgentDir, ".session-model-alert"))
105761
106511
  });
105762
- const hasSessionModelAlert = existsSync54(join59(smAgentDir, ".session-model-alert"));
105763
- if (confirmation.kind === "not-applied" && hasSessionModelAlert) {
105764
- process.stderr.write(`telegram gateway: gw /model relaunch applied \u2014 suppressing not-applied confirmation (a .session-model-alert is present and will be relayed) agent=${getMyAgentName()} target=${confirmation.target}
105765
- `);
105766
- } else {
105767
- const body = confirmation.kind === "applied" ? `\u2705 Now running \`${confirmation.launched}\` \u2014 session-only, reverts to the configured model on the next restart. Fresh session; memory and the handoff briefing carry the context.` : confirmation.kind === "not-applied" ? `\u26A0\uFE0F Your switch to \`${confirmation.target}\` didn't apply \u2014 the agent reverted to \`${confirmation.revertedTo}\` (the apply-boot didn't complete). Re-issue \`/model ${confirmation.target}\` to try again.` : `\u2705 Now running \`${confirmation.launched}\` (the configured default) \u2014 fresh session; memory and the handoff briefing carry the context.`;
105768
- lockedBot.api.sendMessage(chat.chatId, body, {
105769
- parse_mode: "Markdown",
105770
- ...chat.threadId != null ? { message_thread_id: chat.threadId } : {}
105771
- }).catch((err) => process.stderr.write(`telegram gateway: model-switch confirmation send failed: ${err?.message ?? String(err)}
105772
- `));
105773
- }
105774
106512
  }
105775
106513
  } catch {}
105776
106514
  }