switchroom 0.18.24 → 0.18.25

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 (42) hide show
  1. package/dist/cli/switchroom.js +59 -11
  2. package/dist/host-control/main.js +1 -1
  3. package/package.json +1 -1
  4. package/telegram-plugin/dist/bridge/bridge.js +26 -0
  5. package/telegram-plugin/dist/gateway/gateway.js +1489 -829
  6. package/telegram-plugin/dist/server.js +26 -0
  7. package/telegram-plugin/gateway/callback-query-handlers.ts +7 -0
  8. package/telegram-plugin/gateway/gateway.ts +314 -3
  9. package/telegram-plugin/gateway/model-command.ts +188 -56
  10. package/telegram-plugin/gateway/redelivery-decision.ts +139 -0
  11. package/telegram-plugin/gateway/vault-grant-inbound-builders.ts +42 -1
  12. package/telegram-plugin/history.ts +118 -0
  13. package/telegram-plugin/registry/turns-schema.ts +89 -1
  14. package/telegram-plugin/session-tail.ts +185 -0
  15. package/telegram-plugin/subagent-watcher.ts +45 -0
  16. package/telegram-plugin/tests/crash-redelivery-resume-exclusion.test.ts +133 -0
  17. package/telegram-plugin/tests/crash-redelivery-wiring.test.ts +72 -0
  18. package/telegram-plugin/tests/history.test.ts +91 -0
  19. package/telegram-plugin/tests/model-command.test.ts +189 -12
  20. package/telegram-plugin/tests/redelivery-decision.test.ts +84 -0
  21. package/telegram-plugin/tests/registry-turns.test.ts +51 -0
  22. package/telegram-plugin/tests/session-model-source.test.ts +11 -0
  23. package/telegram-plugin/tests/session-tail.test.ts +145 -0
  24. package/telegram-plugin/tests/subagent-watcher.test.ts +50 -0
  25. package/telegram-plugin/tests/tool-activity-summary.test.ts +109 -0
  26. package/telegram-plugin/tests/trailing-answer-projector.test.ts +124 -0
  27. package/telegram-plugin/tests/vault-grant-inbound-builders.test.ts +125 -0
  28. package/telegram-plugin/tests/worker-feed-pin-persistence.test.ts +306 -0
  29. package/telegram-plugin/tool-activity-summary.ts +54 -3
  30. package/telegram-plugin/worker-activity-feed.ts +104 -0
  31. package/vendor/hindsight-memory/scripts/backfill_transcripts.py +762 -0
  32. package/vendor/hindsight-memory/scripts/drain_pending.py +13 -1
  33. package/vendor/hindsight-memory/scripts/lib/client.py +14 -4
  34. package/vendor/hindsight-memory/scripts/lib/config.py +8 -0
  35. package/vendor/hindsight-memory/scripts/lib/pacing.py +102 -0
  36. package/vendor/hindsight-memory/scripts/lib/watermark.py +213 -0
  37. package/vendor/hindsight-memory/scripts/reconcile_tail.py +344 -0
  38. package/vendor/hindsight-memory/scripts/retain.py +299 -143
  39. package/vendor/hindsight-memory/scripts/session_start.py +14 -0
  40. package/vendor/hindsight-memory/scripts/tests/test_backfill.py +362 -0
  41. package/vendor/hindsight-memory/scripts/tests/test_reconcile_durability.py +350 -0
  42. package/vendor/hindsight-memory/tests/test_hooks.py +8 -2
@@ -21866,6 +21866,142 @@ var init_loader = __esm(() => {
21866
21866
  };
21867
21867
  });
21868
21868
 
21869
+ // quota-check.ts
21870
+ import { readFileSync as readFileSync17, existsSync as existsSync15 } from "fs";
21871
+ import { join as join20 } from "path";
21872
+ function readOauthToken(claudeConfigDir) {
21873
+ const tokenFile = join20(claudeConfigDir, ".oauth-token");
21874
+ if (!existsSync15(tokenFile))
21875
+ return null;
21876
+ try {
21877
+ const raw = readFileSync17(tokenFile, "utf-8").trim();
21878
+ return raw.length > 0 ? raw : null;
21879
+ } catch {
21880
+ return null;
21881
+ }
21882
+ }
21883
+ function parseFloatHeader(headers, name) {
21884
+ const v = headers.get(name);
21885
+ if (v == null || v.trim().length === 0)
21886
+ return null;
21887
+ const n = Number(v);
21888
+ return Number.isFinite(n) ? n : null;
21889
+ }
21890
+ function parseEpochHeader(headers, name) {
21891
+ const v = headers.get(name);
21892
+ if (v == null)
21893
+ return null;
21894
+ const n = Number(v);
21895
+ if (!Number.isFinite(n) || n <= 0)
21896
+ return null;
21897
+ return new Date(n * 1000);
21898
+ }
21899
+ function parseQuotaHeaders(headers) {
21900
+ const fiveHour = parseFloatHeader(headers, "anthropic-ratelimit-unified-5h-utilization");
21901
+ const sevenDay = parseFloatHeader(headers, "anthropic-ratelimit-unified-7d-utilization");
21902
+ if (fiveHour == null && sevenDay == null) {
21903
+ return {
21904
+ ok: false,
21905
+ reason: "no unified rate-limit headers in response (API token, not OAuth?)"
21906
+ };
21907
+ }
21908
+ return {
21909
+ ok: true,
21910
+ data: {
21911
+ fiveHourUtilizationPct: (fiveHour ?? 0) * 100,
21912
+ sevenDayUtilizationPct: (sevenDay ?? 0) * 100,
21913
+ fiveHourUtilPresent: fiveHour != null,
21914
+ sevenDayUtilPresent: sevenDay != null,
21915
+ fiveHourResetAt: parseEpochHeader(headers, "anthropic-ratelimit-unified-5h-reset"),
21916
+ sevenDayResetAt: parseEpochHeader(headers, "anthropic-ratelimit-unified-7d-reset"),
21917
+ representativeClaim: headers.get("anthropic-ratelimit-unified-representative-claim"),
21918
+ overageStatus: headers.get("anthropic-ratelimit-unified-overage-status"),
21919
+ overageDisabledReason: headers.get("anthropic-ratelimit-unified-overage-disabled-reason")
21920
+ }
21921
+ };
21922
+ }
21923
+ async function fetchQuota(opts) {
21924
+ let token;
21925
+ if (opts.accessToken && opts.claudeConfigDir) {
21926
+ return {
21927
+ ok: false,
21928
+ reason: "pass only one of `accessToken` or `claudeConfigDir`, not both"
21929
+ };
21930
+ }
21931
+ if (opts.accessToken) {
21932
+ token = opts.accessToken.trim().length > 0 ? opts.accessToken : null;
21933
+ } else if (opts.claudeConfigDir) {
21934
+ token = readOauthToken(opts.claudeConfigDir);
21935
+ } else {
21936
+ return {
21937
+ ok: false,
21938
+ reason: "fetchQuota requires `accessToken` or `claudeConfigDir`"
21939
+ };
21940
+ }
21941
+ if (!token) {
21942
+ return { ok: false, reason: "no OAuth token at .oauth-token" };
21943
+ }
21944
+ const controller = new AbortController;
21945
+ const timeout = setTimeout(() => controller.abort(), opts.timeoutMs ?? 1e4);
21946
+ const fetchFn = opts.fetchImpl ?? fetch;
21947
+ let resp;
21948
+ try {
21949
+ resp = await fetchFn("https://api.anthropic.com/v1/messages", {
21950
+ method: "POST",
21951
+ headers: {
21952
+ "anthropic-version": "2023-06-01",
21953
+ "anthropic-beta": OAUTH_BETA,
21954
+ authorization: `Bearer ${token}`,
21955
+ "x-app": "cli",
21956
+ "user-agent": DEFAULT_USER_AGENT,
21957
+ "content-type": "application/json"
21958
+ },
21959
+ body: JSON.stringify({
21960
+ model: opts.model ?? DEFAULT_PROBE_MODEL,
21961
+ max_tokens: 1,
21962
+ messages: [{ role: "user", content: "hi" }]
21963
+ }),
21964
+ signal: controller.signal
21965
+ });
21966
+ } catch (err) {
21967
+ const msg = err?.message ?? String(err);
21968
+ return { ok: false, reason: `request failed: ${msg}` };
21969
+ } finally {
21970
+ clearTimeout(timeout);
21971
+ }
21972
+ if (resp.status === 401 || resp.status === 403) {
21973
+ return { ok: false, reason: `auth rejected (HTTP ${resp.status})` };
21974
+ }
21975
+ const parsed = parseQuotaHeaders(resp.headers);
21976
+ if (!parsed.ok && resp.status >= 400) {
21977
+ return { ok: false, reason: `HTTP ${resp.status}, ${parsed.reason}` };
21978
+ }
21979
+ return parsed;
21980
+ }
21981
+ function formatQuotaLine(q) {
21982
+ const fmt = (n) => `${Math.round(n)}%`;
21983
+ return `${fmt(q.fiveHourUtilizationPct)} / 5h \u00b7 ${fmt(q.sevenDayUtilizationPct)} / 7d`;
21984
+ }
21985
+ function formatResetRelative(target, now = new Date) {
21986
+ if (!target)
21987
+ return "\u2014";
21988
+ const deltaMs = target.getTime() - now.getTime();
21989
+ if (deltaMs <= 0)
21990
+ return "resets now";
21991
+ const totalMin = Math.round(deltaMs / 60000);
21992
+ if (totalMin < 60)
21993
+ return `resets in ${totalMin}m`;
21994
+ const hours = Math.floor(totalMin / 60);
21995
+ const mins = totalMin % 60;
21996
+ if (hours < 24)
21997
+ return mins > 0 ? `resets in ${hours}h ${mins}m` : `resets in ${hours}h`;
21998
+ const days = Math.floor(hours / 24);
21999
+ const remH = hours % 24;
22000
+ return remH > 0 ? `resets in ${days}d ${remH}h` : `resets in ${days}d`;
22001
+ }
22002
+ var OAUTH_BETA = "oauth-2025-04-20", DEFAULT_USER_AGENT = "claude-cli/1.0.0 (external, cli)", DEFAULT_PROBE_MODEL = "claude-haiku-4-5-20251001";
22003
+ var init_quota_check = () => {};
22004
+
21869
22005
  // ../node_modules/.bun/@xterm+headless@6.0.0/node_modules/@xterm/headless/lib-headless/xterm-headless.js
21870
22006
  var require_xterm_headless = __commonJS((exports2) => {
21871
22007
  (() => {
@@ -27427,9 +27563,9 @@ var init_flock = () => {};
27427
27563
  // ../src/vault/vault.ts
27428
27564
  import { randomBytes as randomBytes4, scryptSync, createCipheriv, createDecipheriv } from "node:crypto";
27429
27565
  import {
27430
- readFileSync as readFileSync17,
27566
+ readFileSync as readFileSync18,
27431
27567
  writeSync as writeSync2,
27432
- existsSync as existsSync15,
27568
+ existsSync as existsSync16,
27433
27569
  renameSync as renameSync6,
27434
27570
  mkdirSync as mkdirSync16,
27435
27571
  unlinkSync as unlinkSync10,
@@ -27470,12 +27606,12 @@ function normalizeSecrets(raw) {
27470
27606
  return out;
27471
27607
  }
27472
27608
  function openVault(passphrase, vaultPath) {
27473
- if (!existsSync15(vaultPath)) {
27609
+ if (!existsSync16(vaultPath)) {
27474
27610
  throw new VaultError(`Vault file not found: ${vaultPath}`);
27475
27611
  }
27476
27612
  let vaultFile;
27477
27613
  try {
27478
- vaultFile = JSON.parse(readFileSync17(vaultPath, "utf8"));
27614
+ vaultFile = JSON.parse(readFileSync18(vaultPath, "utf8"));
27479
27615
  } catch {
27480
27616
  throw new VaultError(`Failed to read vault file: ${vaultPath}`);
27481
27617
  }
@@ -27529,7 +27665,7 @@ import {
27529
27665
  statSync as statSync5,
27530
27666
  writeSync as writeSync3
27531
27667
  } from "node:fs";
27532
- import { join as join21 } from "node:path";
27668
+ import { join as join23 } from "node:path";
27533
27669
  import { tmpdir as tmpdir2 } from "node:os";
27534
27670
  import { constants as fsConstants } from "node:fs";
27535
27671
  function isVaultReference(value) {
@@ -27581,11 +27717,11 @@ function materializationRoot() {
27581
27717
  return cachedRoot;
27582
27718
  const xdg = process.env.XDG_RUNTIME_DIR;
27583
27719
  if (xdg) {
27584
- const base = join21(xdg, "switchroom", "vault");
27720
+ const base = join23(xdg, "switchroom", "vault");
27585
27721
  mkdirSync17(base, { recursive: true, mode: 448 });
27586
- cachedRoot = mkdtempSync(join21(base, "run-"));
27722
+ cachedRoot = mkdtempSync(join23(base, "run-"));
27587
27723
  } else {
27588
- cachedRoot = mkdtempSync(join21(tmpdir2(), "switchroom-vault-"));
27724
+ cachedRoot = mkdtempSync(join23(tmpdir2(), "switchroom-vault-"));
27589
27725
  }
27590
27726
  chmodSync5(cachedRoot, 448);
27591
27727
  return cachedRoot;
@@ -27600,7 +27736,7 @@ function writeFileExclusive(filePath, content3) {
27600
27736
  }
27601
27737
  }
27602
27738
  function materializeFilesEntry(key, files) {
27603
- const dir = join21(materializationRoot(), key);
27739
+ const dir = join23(materializationRoot(), key);
27604
27740
  if (materializedDirs.has(dir)) {
27605
27741
  try {
27606
27742
  rmSync(dir, { recursive: true, force: true });
@@ -27616,7 +27752,7 @@ function materializeFilesEntry(key, files) {
27616
27752
  if (filename.includes("/") || filename.includes("\\") || filename === ".." || filename === "." || filename.includes("\x00")) {
27617
27753
  throw new Error(`Refusing to materialize vault file with unsafe name: ${filename}`);
27618
27754
  }
27619
- const filePath = join21(dir, filename);
27755
+ const filePath = join23(dir, filename);
27620
27756
  const content3 = encoding === "base64" ? Buffer.from(value, "base64") : value;
27621
27757
  writeFileExclusive(filePath, content3);
27622
27758
  }
@@ -28184,17 +28320,21 @@ __export(exports_history, {
28184
28320
  recordEdit: () => recordEdit,
28185
28321
  query: () => query,
28186
28322
  pruneMessagesOlderThanDays: () => pruneMessagesOlderThanDays,
28323
+ normalizeDeliveryText: () => normalizeDeliveryText,
28187
28324
  lookupMessageRoleAndText: () => lookupMessageRoleAndText,
28188
28325
  initHistory: () => initHistory,
28326
+ hasOutboundWithText: () => hasOutboundWithText,
28189
28327
  hasOutboundDeliveredSince: () => hasOutboundDeliveredSince,
28190
28328
  getRecentOutboundCount: () => getRecentOutboundCount,
28191
28329
  getLatestInboundMessageId: () => getLatestInboundMessageId,
28330
+ deliveryTextMatch: () => deliveryTextMatch,
28192
28331
  deleteFromHistory: () => deleteFromHistory,
28193
28332
  checkpointWal: () => checkpointWal,
28194
- _resetForTests: () => _resetForTests
28333
+ _resetForTests: () => _resetForTests,
28334
+ MIN_PREFIX_MATCH_CHARS: () => MIN_PREFIX_MATCH_CHARS
28195
28335
  });
28196
- import { chmodSync as chmodSync7, existsSync as existsSync21, mkdirSync as mkdirSync21 } from "fs";
28197
- import { join as join24 } from "path";
28336
+ import { chmodSync as chmodSync7, existsSync as existsSync22, mkdirSync as mkdirSync21 } from "fs";
28337
+ import { join as join26 } from "path";
28198
28338
  function loadDatabaseClass() {
28199
28339
  if (DatabaseClass != null)
28200
28340
  return DatabaseClass;
@@ -28217,7 +28357,7 @@ function initHistory(stateDir, retentionDays = 30) {
28217
28357
  return;
28218
28358
  const Database = loadDatabaseClass();
28219
28359
  mkdirSync21(stateDir, { recursive: true, mode: 448 });
28220
- const path2 = join24(stateDir, "history.db");
28360
+ const path2 = join26(stateDir, "history.db");
28221
28361
  dbPath = path2;
28222
28362
  db = new Database(path2, { create: true });
28223
28363
  db.exec("PRAGMA journal_mode = WAL");
@@ -28283,7 +28423,7 @@ function initHistory(stateDir, retentionDays = 30) {
28283
28423
  }
28284
28424
  for (const suffix of ["", "-shm", "-wal"]) {
28285
28425
  const f = path2 + suffix;
28286
- if (existsSync21(f)) {
28426
+ if (existsSync22(f)) {
28287
28427
  try {
28288
28428
  chmodSync7(f, 420);
28289
28429
  } catch {}
@@ -28308,7 +28448,7 @@ function checkpointWal() {
28308
28448
  if (dbPath) {
28309
28449
  for (const suffix of ["-shm", "-wal"]) {
28310
28450
  const f = dbPath + suffix;
28311
- if (existsSync21(f)) {
28451
+ if (existsSync22(f)) {
28312
28452
  try {
28313
28453
  chmodSync7(f, 420);
28314
28454
  } catch {}
@@ -28447,6 +28587,50 @@ function hasOutboundDeliveredSince(chatId, sinceMs, threadId, minChars = 200) {
28447
28587
  return false;
28448
28588
  }
28449
28589
  }
28590
+ function hasOutboundWithText(chatId, text4, threadId, sinceMs) {
28591
+ const needle = normalizeDeliveryText(text4);
28592
+ if (needle.length === 0)
28593
+ return false;
28594
+ try {
28595
+ const params = [chatId];
28596
+ let sql = "SELECT text FROM messages WHERE chat_id = ? AND role = 'assistant'";
28597
+ if (threadId !== undefined) {
28598
+ if (threadId === null) {
28599
+ sql += " AND thread_id IS NULL";
28600
+ } else {
28601
+ sql += " AND thread_id = ?";
28602
+ params.push(threadId);
28603
+ }
28604
+ }
28605
+ if (sinceMs != null && Number.isFinite(sinceMs)) {
28606
+ sql += " AND ts >= ?";
28607
+ params.push(Math.floor(sinceMs / 1000));
28608
+ }
28609
+ sql += " ORDER BY ts DESC LIMIT 500";
28610
+ const rows = requireDb().prepare(sql).all(...params);
28611
+ for (const r of rows) {
28612
+ const hay = normalizeDeliveryText(r.text ?? "");
28613
+ if (hay.length === 0)
28614
+ continue;
28615
+ if (deliveryTextMatch(hay, needle))
28616
+ return true;
28617
+ }
28618
+ return false;
28619
+ } catch {
28620
+ return false;
28621
+ }
28622
+ }
28623
+ function normalizeDeliveryText(text4) {
28624
+ return text4.replace(/\s+/g, " ").trim();
28625
+ }
28626
+ function deliveryTextMatch(hay, needle) {
28627
+ if (hay === needle)
28628
+ return true;
28629
+ const shorter = Math.min(hay.length, needle.length);
28630
+ if (shorter < MIN_PREFIX_MATCH_CHARS)
28631
+ return false;
28632
+ return hay.startsWith(needle) || needle.startsWith(hay);
28633
+ }
28450
28634
  function query(opts) {
28451
28635
  const limit = Math.min(MAX_LIMIT, Math.max(1, opts.limit ?? DEFAULT_LIMIT));
28452
28636
  const params = [opts.chat_id];
@@ -28469,147 +28653,11 @@ function query(opts) {
28469
28653
  rows.reverse();
28470
28654
  return rows;
28471
28655
  }
28472
- var DatabaseClass = null, DEFAULT_LIMIT = 10, MAX_LIMIT = 50, db = null, dbPath = null;
28656
+ var DatabaseClass = null, DEFAULT_LIMIT = 10, MAX_LIMIT = 50, db = null, dbPath = null, MIN_PREFIX_MATCH_CHARS = 40;
28473
28657
  var init_history = __esm(() => {
28474
28658
  init_redact();
28475
28659
  });
28476
28660
 
28477
- // quota-check.ts
28478
- import { readFileSync as readFileSync21, existsSync as existsSync22 } from "fs";
28479
- import { join as join25 } from "path";
28480
- function readOauthToken(claudeConfigDir) {
28481
- const tokenFile = join25(claudeConfigDir, ".oauth-token");
28482
- if (!existsSync22(tokenFile))
28483
- return null;
28484
- try {
28485
- const raw = readFileSync21(tokenFile, "utf-8").trim();
28486
- return raw.length > 0 ? raw : null;
28487
- } catch {
28488
- return null;
28489
- }
28490
- }
28491
- function parseFloatHeader(headers, name) {
28492
- const v = headers.get(name);
28493
- if (v == null || v.trim().length === 0)
28494
- return null;
28495
- const n = Number(v);
28496
- return Number.isFinite(n) ? n : null;
28497
- }
28498
- function parseEpochHeader(headers, name) {
28499
- const v = headers.get(name);
28500
- if (v == null)
28501
- return null;
28502
- const n = Number(v);
28503
- if (!Number.isFinite(n) || n <= 0)
28504
- return null;
28505
- return new Date(n * 1000);
28506
- }
28507
- function parseQuotaHeaders(headers) {
28508
- const fiveHour = parseFloatHeader(headers, "anthropic-ratelimit-unified-5h-utilization");
28509
- const sevenDay = parseFloatHeader(headers, "anthropic-ratelimit-unified-7d-utilization");
28510
- if (fiveHour == null && sevenDay == null) {
28511
- return {
28512
- ok: false,
28513
- reason: "no unified rate-limit headers in response (API token, not OAuth?)"
28514
- };
28515
- }
28516
- return {
28517
- ok: true,
28518
- data: {
28519
- fiveHourUtilizationPct: (fiveHour ?? 0) * 100,
28520
- sevenDayUtilizationPct: (sevenDay ?? 0) * 100,
28521
- fiveHourUtilPresent: fiveHour != null,
28522
- sevenDayUtilPresent: sevenDay != null,
28523
- fiveHourResetAt: parseEpochHeader(headers, "anthropic-ratelimit-unified-5h-reset"),
28524
- sevenDayResetAt: parseEpochHeader(headers, "anthropic-ratelimit-unified-7d-reset"),
28525
- representativeClaim: headers.get("anthropic-ratelimit-unified-representative-claim"),
28526
- overageStatus: headers.get("anthropic-ratelimit-unified-overage-status"),
28527
- overageDisabledReason: headers.get("anthropic-ratelimit-unified-overage-disabled-reason")
28528
- }
28529
- };
28530
- }
28531
- async function fetchQuota(opts) {
28532
- let token;
28533
- if (opts.accessToken && opts.claudeConfigDir) {
28534
- return {
28535
- ok: false,
28536
- reason: "pass only one of `accessToken` or `claudeConfigDir`, not both"
28537
- };
28538
- }
28539
- if (opts.accessToken) {
28540
- token = opts.accessToken.trim().length > 0 ? opts.accessToken : null;
28541
- } else if (opts.claudeConfigDir) {
28542
- token = readOauthToken(opts.claudeConfigDir);
28543
- } else {
28544
- return {
28545
- ok: false,
28546
- reason: "fetchQuota requires `accessToken` or `claudeConfigDir`"
28547
- };
28548
- }
28549
- if (!token) {
28550
- return { ok: false, reason: "no OAuth token at .oauth-token" };
28551
- }
28552
- const controller = new AbortController;
28553
- const timeout = setTimeout(() => controller.abort(), opts.timeoutMs ?? 1e4);
28554
- const fetchFn = opts.fetchImpl ?? fetch;
28555
- let resp;
28556
- try {
28557
- resp = await fetchFn("https://api.anthropic.com/v1/messages", {
28558
- method: "POST",
28559
- headers: {
28560
- "anthropic-version": "2023-06-01",
28561
- "anthropic-beta": OAUTH_BETA,
28562
- authorization: `Bearer ${token}`,
28563
- "x-app": "cli",
28564
- "user-agent": DEFAULT_USER_AGENT,
28565
- "content-type": "application/json"
28566
- },
28567
- body: JSON.stringify({
28568
- model: opts.model ?? DEFAULT_PROBE_MODEL,
28569
- max_tokens: 1,
28570
- messages: [{ role: "user", content: "hi" }]
28571
- }),
28572
- signal: controller.signal
28573
- });
28574
- } catch (err) {
28575
- const msg = err?.message ?? String(err);
28576
- return { ok: false, reason: `request failed: ${msg}` };
28577
- } finally {
28578
- clearTimeout(timeout);
28579
- }
28580
- if (resp.status === 401 || resp.status === 403) {
28581
- return { ok: false, reason: `auth rejected (HTTP ${resp.status})` };
28582
- }
28583
- const parsed = parseQuotaHeaders(resp.headers);
28584
- if (!parsed.ok && resp.status >= 400) {
28585
- return { ok: false, reason: `HTTP ${resp.status}, ${parsed.reason}` };
28586
- }
28587
- return parsed;
28588
- }
28589
- function formatQuotaLine(q) {
28590
- const fmt = (n) => `${Math.round(n)}%`;
28591
- return `${fmt(q.fiveHourUtilizationPct)} / 5h \u00b7 ${fmt(q.sevenDayUtilizationPct)} / 7d`;
28592
- }
28593
- function formatResetRelative(target, now = new Date) {
28594
- if (!target)
28595
- return "\u2014";
28596
- const deltaMs = target.getTime() - now.getTime();
28597
- if (deltaMs <= 0)
28598
- return "resets now";
28599
- const totalMin = Math.round(deltaMs / 60000);
28600
- if (totalMin < 60)
28601
- return `resets in ${totalMin}m`;
28602
- const hours = Math.floor(totalMin / 60);
28603
- const mins = totalMin % 60;
28604
- if (hours < 24)
28605
- return mins > 0 ? `resets in ${hours}h ${mins}m` : `resets in ${hours}h`;
28606
- const days = Math.floor(hours / 24);
28607
- const remH = hours % 24;
28608
- return remH > 0 ? `resets in ${days}d ${remH}h` : `resets in ${days}d`;
28609
- }
28610
- var OAUTH_BETA = "oauth-2025-04-20", DEFAULT_USER_AGENT = "claude-cli/1.0.0 (external, cli)", DEFAULT_PROBE_MODEL = "claude-haiku-4-5-20251001";
28611
- var init_quota_check = () => {};
28612
-
28613
28661
  // ../src/util/atomic.ts
28614
28662
  import { closeSync as closeSync5, constants as constants2, fsyncSync as fsyncSync2, openSync as openSync5, renameSync as renameSync10, rmSync as rmSync5, writeSync as writeSync4 } from "node:fs";
28615
28663
  var TMP_OPEN_FLAGS;
@@ -31573,7 +31621,7 @@ var require_util = __commonJS((exports2) => {
31573
31621
  return path2;
31574
31622
  }
31575
31623
  exports2.normalize = normalize;
31576
- function join31(aRoot, aPath) {
31624
+ function join32(aRoot, aPath) {
31577
31625
  if (aRoot === "") {
31578
31626
  aRoot = ".";
31579
31627
  }
@@ -31605,7 +31653,7 @@ var require_util = __commonJS((exports2) => {
31605
31653
  }
31606
31654
  return joined;
31607
31655
  }
31608
- exports2.join = join31;
31656
+ exports2.join = join32;
31609
31657
  exports2.isAbsolute = function(aPath) {
31610
31658
  return aPath.charAt(0) === "/" || urlRegexp.test(aPath);
31611
31659
  };
@@ -31778,7 +31826,7 @@ var require_util = __commonJS((exports2) => {
31778
31826
  parsed.path = parsed.path.substring(0, index2 + 1);
31779
31827
  }
31780
31828
  }
31781
- sourceURL = join31(urlGenerate(parsed), sourceURL);
31829
+ sourceURL = join32(urlGenerate(parsed), sourceURL);
31782
31830
  }
31783
31831
  return normalize(sourceURL);
31784
31832
  }
@@ -34384,9 +34432,9 @@ function renderAuthLine(state4, agentName3, now = Date.now()) {
34384
34432
 
34385
34433
  // gateway/quota-cache.ts
34386
34434
  import { existsSync as existsSync37, readFileSync as readFileSync39, writeFileSync as writeFileSync31, mkdirSync as mkdirSync30 } from "fs";
34387
- import { join as join41, dirname as dirname11 } from "path";
34435
+ import { join as join42, dirname as dirname11 } from "path";
34388
34436
  function defaultCachePath() {
34389
- return process.env.SWITCHROOM_QUOTA_CACHE_PATH ?? join41(process.env.HOME ?? "/tmp", ".switchroom", "quota-cache.json");
34437
+ return process.env.SWITCHROOM_QUOTA_CACHE_PATH ?? join42(process.env.HOME ?? "/tmp", ".switchroom", "quota-cache.json");
34390
34438
  }
34391
34439
  function readQuotaCache(opts = {}) {
34392
34440
  const path2 = opts.path ?? defaultCachePath();
@@ -34433,7 +34481,7 @@ var init_quota_cache = __esm(() => {
34433
34481
 
34434
34482
  // gateway/boot-probes.ts
34435
34483
  import { readFileSync as readFileSync40, readdirSync as readdirSync8, existsSync as existsSync38 } from "fs";
34436
- import { join as join42 } from "path";
34484
+ import { join as join43 } from "path";
34437
34485
  import { execFile as execFileCb } from "child_process";
34438
34486
  import { promisify } from "util";
34439
34487
  async function withTimeout(label, p, timeoutMs = PROBE_TIMEOUT_MS) {
@@ -34475,8 +34523,8 @@ function mapPlan(billingType, hasExtra) {
34475
34523
  }
34476
34524
  async function probeAccount(agentDir) {
34477
34525
  return withTimeout("Account", (async () => {
34478
- const claudeDir = join42(agentDir, ".claude");
34479
- const claudeJsonPath = join42(claudeDir, ".claude.json");
34526
+ const claudeDir = join43(agentDir, ".claude");
34527
+ const claudeJsonPath = join43(claudeDir, ".claude.json");
34480
34528
  let cfg = {};
34481
34529
  try {
34482
34530
  const raw = readFileSync40(claudeJsonPath, "utf8");
@@ -34497,8 +34545,8 @@ async function probeAccount(agentDir) {
34497
34545
  let tokenStr = "";
34498
34546
  let status = "ok";
34499
34547
  for (const candidate of [
34500
- join42(claudeDir, ".oauth-token.meta.json"),
34501
- join42(claudeDir, "accounts", "default", ".oauth-token.meta.json")
34548
+ join43(claudeDir, ".oauth-token.meta.json"),
34549
+ join43(claudeDir, "accounts", "default", ".oauth-token.meta.json")
34502
34550
  ]) {
34503
34551
  if (existsSync38(candidate)) {
34504
34552
  try {
@@ -34884,9 +34932,9 @@ async function probeQuota(claudeConfigDir, _agentDir, fetchImpl = fetch, opts =
34884
34932
  let claudeDirForProbe = null;
34885
34933
  for (const candidate of [
34886
34934
  claudeConfigDir,
34887
- join42(claudeConfigDir, "accounts", "default")
34935
+ join43(claudeConfigDir, "accounts", "default")
34888
34936
  ]) {
34889
- if (existsSync38(join42(candidate, ".oauth-token"))) {
34937
+ if (existsSync38(join43(candidate, ".oauth-token"))) {
34890
34938
  claudeDirForProbe = candidate;
34891
34939
  break;
34892
34940
  }
@@ -35121,7 +35169,7 @@ async function probeSkills(agentDir, opts = {}) {
35121
35169
  return withTimeout("Skills", (async () => {
35122
35170
  const fs2 = opts.fs ?? realSkillsFs;
35123
35171
  const max = opts.maxNamesShown ?? 3;
35124
- const skillsDir = join42(agentDir, ".claude", "skills");
35172
+ const skillsDir = join43(agentDir, ".claude", "skills");
35125
35173
  if (!fs2.exists(skillsDir)) {
35126
35174
  return { status: "ok", label: "Skills", detail: "no skills dir" };
35127
35175
  }
@@ -35136,17 +35184,17 @@ async function probeSkills(agentDir, opts = {}) {
35136
35184
  }
35137
35185
  const dangling = [];
35138
35186
  for (const name of entries) {
35139
- const skillPath = join42(skillsDir, name);
35187
+ const skillPath = join43(skillsDir, name);
35140
35188
  if (!fs2.exists(skillPath)) {
35141
35189
  dangling.push(name);
35142
35190
  continue;
35143
35191
  }
35144
- const skillMd = join42(skillPath, "SKILL.md");
35192
+ const skillMd = join43(skillPath, "SKILL.md");
35145
35193
  if (!fs2.exists(skillMd) && !fs2.exists(skillPath + ".md")) {
35146
35194
  continue;
35147
35195
  }
35148
35196
  }
35149
- const overlayDir = opts.overlaySkillsDir ?? join42(agentDir, "skills.d");
35197
+ const overlayDir = opts.overlaySkillsDir ?? join43(agentDir, "skills.d");
35150
35198
  const overlaySlugs = new Set;
35151
35199
  if (fs2.exists(overlayDir)) {
35152
35200
  let overlayEntries = [];
@@ -35188,7 +35236,7 @@ function renderBucketedSkills(switchroom, agent) {
35188
35236
  }
35189
35237
  async function probeConnections(agentDir, opts = {}) {
35190
35238
  return withTimeout("Connections", (async () => {
35191
- const path2 = join42(agentDir, ".claude", "connection-health.json");
35239
+ const path2 = join43(agentDir, ".claude", "connection-health.json");
35192
35240
  const read = opts.readFileImpl ?? ((p) => readFileSync40(p, "utf8"));
35193
35241
  let issues = [];
35194
35242
  try {
@@ -35530,7 +35578,7 @@ __export(exports_boot_card, {
35530
35578
  renderBootCard: () => renderBootCard,
35531
35579
  renderAccountRows: () => renderAuthLine
35532
35580
  });
35533
- import { join as join43 } from "path";
35581
+ import { join as join44 } from "path";
35534
35582
  function resolvePersonaName(slug, loadConfig3) {
35535
35583
  try {
35536
35584
  const config = loadConfig3 ? loadConfig3() : loadConfig();
@@ -35621,7 +35669,7 @@ function renderBootCard(opts) {
35621
35669
  return stackCardLines(flatLines);
35622
35670
  }
35623
35671
  async function runAllProbes(opts) {
35624
- const claudeDir = join43(opts.agentDir, ".claude");
35672
+ const claudeDir = join44(opts.agentDir, ".claude");
35625
35673
  const probes = {};
35626
35674
  const slug = opts.agentSlug ?? opts.agentName;
35627
35675
  await Promise.allSettled([
@@ -37300,8 +37348,8 @@ import {
37300
37348
  unlinkSync as unlinkSync24,
37301
37349
  appendFileSync as appendFileSync6
37302
37350
  } from "fs";
37303
- import { homedir as homedir17 } from "os";
37304
- import { join as join54, extname, sep as sep3, basename as basename12 } from "path";
37351
+ import { homedir as homedir18 } from "os";
37352
+ import { join as join55, extname, sep as sep3, basename as basename13 } from "path";
37305
37353
 
37306
37354
  // plugin-logger.ts
37307
37355
  import { appendFileSync, mkdirSync, renameSync, statSync, existsSync } from "fs";
@@ -40211,16 +40259,34 @@ function clipNarrative(s) {
40211
40259
  return s.split(`
40212
40260
  `)[0].trim().slice(0, STATUS_LINE_MAX);
40213
40261
  }
40214
- function renderActivityHeader(emoji, label, description, elapsedMs, toolCount, state, model) {
40262
+ function renderActivityHeader(emoji, label, description, elapsedMs, toolCount, state, model, totalTokens) {
40215
40263
  const toolWord = toolCount === 1 ? "tool" : "tools";
40216
40264
  const elapsed = formatFeedElapsed(elapsedMs);
40217
40265
  const descPart = description.length > 0 ? ` \u00b7 _${escapeMarkdown(description)}_` : "";
40218
40266
  const line1 = `${emoji} **${escapeMarkdown(label)}**${descPart}`;
40267
+ const tokPart = tokenSegment(totalTokens);
40219
40268
  const modelLabel = formatModelLabel(model);
40220
40269
  const modelPart = modelLabel != null ? ` \u00b7 ${escapeMarkdown(modelLabel)}` : "";
40221
- const line2 = state === "running" ? `_${elapsed} \u00b7 ${toolCount} ${toolWord}${modelPart}_` : `_${state} \u00b7 ${toolCount} ${toolWord} \u00b7 ${elapsed}${modelPart}_`;
40270
+ const line2 = state === "running" ? `_${elapsed} \u00b7 ${toolCount} ${toolWord}${tokPart}${modelPart}_` : `_${state} \u00b7 ${toolCount} ${toolWord}${tokPart} \u00b7 ${elapsed}${modelPart}_`;
40222
40271
  return [line1, line2];
40223
40272
  }
40273
+ function formatTokenCount(n) {
40274
+ if (!Number.isFinite(n) || n <= 0)
40275
+ return "0";
40276
+ if (n < 1000)
40277
+ return String(Math.floor(n));
40278
+ if (n < 1e6) {
40279
+ const k = Number((n / 1000).toFixed(1));
40280
+ if (k < 1000)
40281
+ return `${k.toFixed(1)}k`;
40282
+ }
40283
+ return `${(n / 1e6).toFixed(1)}M`;
40284
+ }
40285
+ function tokenSegment(totalTokens) {
40286
+ if (totalTokens == null || totalTokens <= 0)
40287
+ return "";
40288
+ return ` \u00b7 ${formatTokenCount(totalTokens)} tok`;
40289
+ }
40224
40290
  function formatFeedElapsed(ms) {
40225
40291
  const s = Math.floor(ms / 1000);
40226
40292
  if (s < 60)
@@ -40257,7 +40323,7 @@ function renderStatusCard(opts) {
40257
40323
  const hasChildren = rawChildren.length > 0;
40258
40324
  const steps = rawSteps.map(escapeStepLine);
40259
40325
  const children = rawChildren.map(escapeStepLine);
40260
- const headerLines = header != null ? renderActivityHeader(header.emoji, header.label, header.description ?? "", header.elapsedMs, header.toolCount, header.state, header.model) : [];
40326
+ const headerLines = header != null ? renderActivityHeader(header.emoji, header.label, header.description ?? "", header.elapsedMs, header.toolCount, header.state, header.model, header.totalTokens) : [];
40261
40327
  const out = [...headerLines];
40262
40328
  if (hasChildren) {
40263
40329
  const shownParent = steps.slice(-STATUS_ROLLING_LINES);
@@ -40354,7 +40420,8 @@ function renderActivityFeed(lines, final = false, liveSuffix = "", stepCount, he
40354
40420
  elapsedMs: header.elapsedMs,
40355
40421
  toolCount: header.toolCount,
40356
40422
  state: header.state,
40357
- model: header.model
40423
+ model: header.model,
40424
+ totalTokens: header.totalTokens
40358
40425
  } : undefined,
40359
40426
  steps: lines,
40360
40427
  final,
@@ -40373,7 +40440,8 @@ function renderActivityFeedWithNested(lines, childLines, final = false, liveSuff
40373
40440
  elapsedMs: header.elapsedMs,
40374
40441
  toolCount: header.toolCount,
40375
40442
  state: header.state,
40376
- model: header.model
40443
+ model: header.model,
40444
+ totalTokens: header.totalTokens
40377
40445
  } : undefined,
40378
40446
  steps: lines,
40379
40447
  childSteps: children,
@@ -40398,9 +40466,10 @@ function renderCombinedWorkerFeed(rows, opts) {
40398
40466
  const rowHeader = (r) => {
40399
40467
  const desc = escapeMarkdown(truncate(stripMarkdown(r.description).replace(/\s+/g, " ").trim() || "background task", COMBINED_ROW_DESC_MAX));
40400
40468
  const toolWord = r.toolCount === 1 ? "tool" : "tools";
40469
+ const tokPart = tokenSegment(r.totalTokens);
40401
40470
  const modelLabel = formatModelLabel(r.model);
40402
40471
  const modelPart = modelLabel != null ? ` \u00b7 ${escapeMarkdown(modelLabel)}` : "";
40403
- return `**${desc}** _\u00b7 ${formatFeedElapsed(r.elapsedMs)} \u00b7 ${r.toolCount} ${toolWord}${modelPart}_`;
40472
+ return `**${desc}** _\u00b7 ${formatFeedElapsed(r.elapsedMs)} \u00b7 ${r.toolCount} ${toolWord}${tokPart}${modelPart}_`;
40404
40473
  };
40405
40474
  const rowHistory = (r) => {
40406
40475
  const src = r.historyLines != null && r.historyLines.length > 0 ? r.historyLines : [r.currentStep];
@@ -40511,7 +40580,8 @@ function renderWorkerActivity(v, liveSuffix = "") {
40511
40580
  elapsedMs: v.elapsedMs,
40512
40581
  toolCount: v.toolCount,
40513
40582
  state: v.state,
40514
- model: v.model
40583
+ model: v.model,
40584
+ totalTokens: v.totalTokens
40515
40585
  };
40516
40586
  let result;
40517
40587
  if (finished && v.state !== "incomplete") {
@@ -40536,6 +40606,9 @@ _starting\u2026_`;
40536
40606
  return card;
40537
40607
  }
40538
40608
  var COOLDOWN_JITTER_MS = 500;
40609
+ var WORKER_CARD_SUPERSEDED_BODY = `\uD83D\uDEE0 **Worker** \u00b7 _continued_
40610
+
40611
+ _Live progress moved to a fresh card to stay pinned._`;
40539
40612
  function extractRetryAfterSecs(err) {
40540
40613
  if (err == null || typeof err !== "object")
40541
40614
  return null;
@@ -40570,6 +40643,7 @@ function createWorkerActivityFeed(opts) {
40570
40643
  const maxRows = Math.max(1, Math.floor(opts.maxRows ?? 8));
40571
40644
  const staleWorkerTtlMs = Math.max(1, Math.floor(opts.staleWorkerTtlMs ?? 50 * 60000));
40572
40645
  const absoluteRowLifetimeCapMs = Math.max(1, Math.floor(opts.absoluteRowLifetimeCapMs ?? 6 * 60 * 60000));
40646
+ const groupMessageLifetimeCapMs = Math.max(1, Math.floor(opts.groupMessageLifetimeCapMs ?? 60 * 60000));
40573
40647
  const reconcilePinFn = opts.reconcilePin ?? (() => {});
40574
40648
  const setIntervalFn = opts.setInterval ?? ((cb, ms) => {
40575
40649
  const t = setInterval(cb, ms);
@@ -40670,6 +40744,7 @@ function createWorkerActivityFeed(opts) {
40670
40744
  description: v.description,
40671
40745
  elapsedMs: elapsedFor(r),
40672
40746
  toolCount: v.toolCount,
40747
+ totalTokens: v.totalTokens,
40673
40748
  currentStep,
40674
40749
  historyLines: r.narrative.length > 0 ? [...r.narrative] : undefined,
40675
40750
  model: v.model
@@ -40747,6 +40822,7 @@ function createWorkerActivityFeed(opts) {
40747
40822
  return;
40748
40823
  }
40749
40824
  g.messageId = sent.message_id;
40825
+ g.messageCreatedAtMs = now;
40750
40826
  g.lastBody = body;
40751
40827
  g.lastEditAt = now;
40752
40828
  g.terminalPainted = false;
@@ -40777,6 +40853,7 @@ function createWorkerActivityFeed(opts) {
40777
40853
  log(`worker-feed: finish feed=${g.feedKey} chat=${g.chatId} thread=${g.threadId ?? "-"} ` + `msgId=${g.messageId} agent=${finishingAgentId ?? "-"} ` + `state=${opts2.terminalRecap?.state ?? "done"} bytes=${body.length}`);
40778
40854
  } else {
40779
40855
  log(`worker-feed: edit feed=${g.feedKey} chat=${g.chatId} ` + `thread=${g.threadId ?? "-"} msgId=${g.messageId} workers=${g.workers.size} bytes=${body.length}`);
40856
+ syncPin(g);
40780
40857
  }
40781
40858
  if (isTerminal)
40782
40859
  clearStaged();
@@ -40795,6 +40872,7 @@ function createWorkerActivityFeed(opts) {
40795
40872
  }
40796
40873
  if (outcome === "gone") {
40797
40874
  g.messageId = null;
40875
+ g.messageCreatedAtMs = 0;
40798
40876
  g.lastBody = null;
40799
40877
  if (isTerminal)
40800
40878
  clearStaged();
@@ -40838,6 +40916,7 @@ function createWorkerActivityFeed(opts) {
40838
40916
  description: lv?.description ?? "background task",
40839
40917
  lastTool: null,
40840
40918
  toolCount: lv?.toolCount ?? 0,
40919
+ totalTokens: lv?.totalTokens,
40841
40920
  latestSummary: "",
40842
40921
  elapsedMs: liveElapsed(row, nowFn()),
40843
40922
  state: "incomplete",
@@ -40897,6 +40976,16 @@ function createWorkerActivityFeed(opts) {
40897
40976
  const running = runningRows(g);
40898
40977
  if (running.length === 0)
40899
40978
  continue;
40979
+ if (g.messageId != null && now - g.messageCreatedAtMs >= groupMessageLifetimeCapMs) {
40980
+ const age = Math.floor((now - g.messageCreatedAtMs) / 1000);
40981
+ const retiredId = g.messageId;
40982
+ log(`worker-feed: group-message lifetime cap rotate feed=${g.feedKey} ` + `msgId=${retiredId} \u2014 age ${age}s (>= ${Math.floor(groupMessageLifetimeCapMs / 1000)}s); ` + `rotating to a fresh message to re-establish the pin surface`);
40983
+ g.messageId = null;
40984
+ g.messageCreatedAtMs = 0;
40985
+ g.lastBody = null;
40986
+ syncPin(g);
40987
+ opts.bot.editMessageText(g.chatId, retiredId, WORKER_CARD_SUPERSEDED_BODY, sendOptsFor(g)).catch(() => {});
40988
+ }
40900
40989
  if (g.messageId == null) {
40901
40990
  const maxElapsed = Math.max(0, ...running.map((r) => liveElapsed(r, now)));
40902
40991
  if (maxElapsed < firstPaintMin)
@@ -40951,6 +41040,7 @@ function createWorkerActivityFeed(opts) {
40951
41040
  chatId,
40952
41041
  threadId,
40953
41042
  messageId: null,
41043
+ messageCreatedAtMs: 0,
40954
41044
  lastBody: null,
40955
41045
  lastEditAt: 0,
40956
41046
  cooldownUntil: 0,
@@ -40963,6 +41053,7 @@ function createWorkerActivityFeed(opts) {
40963
41053
  }
40964
41054
  if (!g.workers.has(agentId) && g.terminalPainted && !hasLiveWorker(g)) {
40965
41055
  g.messageId = null;
41056
+ g.messageCreatedAtMs = 0;
40966
41057
  g.lastBody = null;
40967
41058
  g.pendingFinalize.clear();
40968
41059
  g.terminalPainted = false;
@@ -41889,8 +41980,18 @@ function buildVaultGrantDeniedInbound(opts) {
41889
41980
  }
41890
41981
  };
41891
41982
  }
41983
+ var MAX_GRANT_REASON_CHARS = 300;
41984
+ function normalizeGrantReason(raw) {
41985
+ if (raw == null)
41986
+ return "";
41987
+ const collapsed = raw.replace(/\s+/g, " ").trim();
41988
+ if (collapsed.length === 0)
41989
+ return "";
41990
+ return collapsed.length > MAX_GRANT_REASON_CHARS ? collapsed.slice(0, MAX_GRANT_REASON_CHARS - 1) + "\u2026" : collapsed;
41991
+ }
41892
41992
  function buildVaultGrantApprovedCardText(opts) {
41893
- return `\u2705 Granted **${opts.agentEscaped}** ${opts.scope} access to ` + `\`${opts.key}\` for ${opts.days}d. ` + `(grant \`${opts.grantId}\`)` + (opts.footer ?? "");
41993
+ const reasonClause = opts.reasonEscaped != null && opts.reasonEscaped.length > 0 ? ` _Reason: ${opts.reasonEscaped}_` : "";
41994
+ return `\u2705 Granted **${opts.agentEscaped}** ${opts.scope} access to ` + `\`${opts.key}\` for ${opts.days}d. ` + `(grant \`${opts.grantId}\`)` + reasonClause + (opts.footer ?? "");
41894
41995
  }
41895
41996
  function buildVaultSaveCompletedInbound(opts) {
41896
41997
  const ts = opts.nowMs ?? Date.now();
@@ -43163,6 +43264,7 @@ function createCallbackQueryHandlers(deps) {
43163
43264
  pendingCardStore.remove(stageId);
43164
43265
  if (pending.card_message_id != null) {
43165
43266
  const days = Math.round(pending.ttl_seconds / 86400);
43267
+ const reasonNormalized = normalizeGrantReason(pending.reason);
43166
43268
  const footer = getVaultApprovalAuthMode() === "telegram-id" ? `
43167
43269
  _Approver verified by Telegram identity \u2014 broker auto-unlocked at startup._` : "";
43168
43270
  await ctx.api.editMessageText(pending.chat_id, pending.card_message_id, richMessage(buildVaultGrantApprovedCardText({
@@ -43171,6 +43273,7 @@ _Approver verified by Telegram identity \u2014 broker auto-unlocked at startup._
43171
43273
  key: pending.key,
43172
43274
  days,
43173
43275
  grantId: id,
43276
+ reasonEscaped: reasonNormalized.length > 0 ? escapeHtmlForTg2(reasonNormalized) : undefined,
43174
43277
  footer
43175
43278
  })), { reply_markup: { inline_keyboard: [] } }).catch(() => {});
43176
43279
  }
@@ -45035,16 +45138,34 @@ function clipNarrative2(s) {
45035
45138
  return s.split(`
45036
45139
  `)[0].trim().slice(0, STATUS_LINE_MAX);
45037
45140
  }
45038
- function renderActivityHeader2(emoji, label, description, elapsedMs, toolCount, state, model) {
45141
+ function renderActivityHeader2(emoji, label, description, elapsedMs, toolCount, state, model, totalTokens) {
45039
45142
  const toolWord = toolCount === 1 ? "tool" : "tools";
45040
45143
  const elapsed = formatFeedElapsed2(elapsedMs);
45041
45144
  const descPart = description.length > 0 ? ` \u00b7 _${escapeMarkdown(description)}_` : "";
45042
45145
  const line1 = `${emoji} **${escapeMarkdown(label)}**${descPart}`;
45146
+ const tokPart = tokenSegment2(totalTokens);
45043
45147
  const modelLabel = formatModelLabel(model);
45044
45148
  const modelPart = modelLabel != null ? ` \u00b7 ${escapeMarkdown(modelLabel)}` : "";
45045
- const line2 = state === "running" ? `_${elapsed} \u00b7 ${toolCount} ${toolWord}${modelPart}_` : `_${state} \u00b7 ${toolCount} ${toolWord} \u00b7 ${elapsed}${modelPart}_`;
45149
+ const line2 = state === "running" ? `_${elapsed} \u00b7 ${toolCount} ${toolWord}${tokPart}${modelPart}_` : `_${state} \u00b7 ${toolCount} ${toolWord}${tokPart} \u00b7 ${elapsed}${modelPart}_`;
45046
45150
  return [line1, line2];
45047
45151
  }
45152
+ function formatTokenCount2(n) {
45153
+ if (!Number.isFinite(n) || n <= 0)
45154
+ return "0";
45155
+ if (n < 1000)
45156
+ return String(Math.floor(n));
45157
+ if (n < 1e6) {
45158
+ const k = Number((n / 1000).toFixed(1));
45159
+ if (k < 1000)
45160
+ return `${k.toFixed(1)}k`;
45161
+ }
45162
+ return `${(n / 1e6).toFixed(1)}M`;
45163
+ }
45164
+ function tokenSegment2(totalTokens) {
45165
+ if (totalTokens == null || totalTokens <= 0)
45166
+ return "";
45167
+ return ` \u00b7 ${formatTokenCount2(totalTokens)} tok`;
45168
+ }
45048
45169
  function formatFeedElapsed2(ms) {
45049
45170
  const s = Math.floor(ms / 1000);
45050
45171
  if (s < 60)
@@ -45081,7 +45202,7 @@ function renderStatusCard2(opts) {
45081
45202
  const hasChildren = rawChildren.length > 0;
45082
45203
  const steps = rawSteps.map(escapeStepLine2);
45083
45204
  const children = rawChildren.map(escapeStepLine2);
45084
- const headerLines = header != null ? renderActivityHeader2(header.emoji, header.label, header.description ?? "", header.elapsedMs, header.toolCount, header.state, header.model) : [];
45205
+ const headerLines = header != null ? renderActivityHeader2(header.emoji, header.label, header.description ?? "", header.elapsedMs, header.toolCount, header.state, header.model, header.totalTokens) : [];
45085
45206
  const out = [...headerLines];
45086
45207
  if (hasChildren) {
45087
45208
  const shownParent = steps.slice(-STATUS_ROLLING_LINES);
@@ -45178,7 +45299,8 @@ function renderActivityFeed2(lines, final = false, liveSuffix = "", stepCount, h
45178
45299
  elapsedMs: header.elapsedMs,
45179
45300
  toolCount: header.toolCount,
45180
45301
  state: header.state,
45181
- model: header.model
45302
+ model: header.model,
45303
+ totalTokens: header.totalTokens
45182
45304
  } : undefined,
45183
45305
  steps: lines,
45184
45306
  final,
@@ -45197,7 +45319,8 @@ function renderActivityFeedWithNested2(lines, childLines, final = false, liveSuf
45197
45319
  elapsedMs: header.elapsedMs,
45198
45320
  toolCount: header.toolCount,
45199
45321
  state: header.state,
45200
- model: header.model
45322
+ model: header.model,
45323
+ totalTokens: header.totalTokens
45201
45324
  } : undefined,
45202
45325
  steps: lines,
45203
45326
  childSteps: children,
@@ -62872,6 +62995,597 @@ function resolveAnswerLaneConfig(input) {
62872
62995
  };
62873
62996
  }
62874
62997
 
62998
+ // session-tail.ts
62999
+ import { homedir as homedir7 } from "os";
63000
+ import { basename as basename5, join as join21 } from "path";
63001
+
63002
+ // operator-events.ts
63003
+ init_format();
63004
+
63005
+ // raw-error-scrub.ts
63006
+ function stripRawErrorBytes(raw) {
63007
+ if (typeof raw !== "string" || raw.length === 0)
63008
+ return "";
63009
+ let s = raw;
63010
+ s = s.replace(/API Error:?\s*\d*\s*/gi, " ");
63011
+ s = s.replace(/\bb'[^']*'/g, " ");
63012
+ s = s.replace(/\bb"[^"]*"/g, " ");
63013
+ s = s.replace(/[\u00b7\-\s]*\{[\s\S]*?["']type["']\s*:\s*["']error["'][\s\S]*$/i, " ");
63014
+ s = s.replace(/^\s*\{[\s\S]*\}\s*$/g, " ");
63015
+ s = s.replace(/\s+/g, " ").replace(/[\u00b7:\-\s]+$/g, "").replace(/^[\u00b7:\-\s]+/g, "").trim();
63016
+ return s;
63017
+ }
63018
+ function extractRequestId(raw) {
63019
+ if (typeof raw !== "string" || raw.length === 0)
63020
+ return;
63021
+ const m = raw.match(/["']request[_-]?id["']\s*:\s*["']([A-Za-z0-9._-]+)["']/i) ?? raw.match(/\brequest[_-]?id[=:]\s*([A-Za-z0-9._-]+)/i);
63022
+ return m ? m[1] : undefined;
63023
+ }
63024
+
63025
+ // operator-events.ts
63026
+ function classifyClaudeError(raw) {
63027
+ try {
63028
+ return classifyInner(raw);
63029
+ } catch {
63030
+ return "unknown-4xx";
63031
+ }
63032
+ }
63033
+ function classifyInner(raw) {
63034
+ if (raw == null)
63035
+ return "unknown-4xx";
63036
+ const obj = typeof raw === "object" ? raw : {};
63037
+ const errorType = extractString(obj, "error_type") ?? extractString(obj, "type") ?? extractString(getNestedObj(obj, "error"), "type") ?? "";
63038
+ const errorCode = extractString(obj, "code") ?? extractString(getNestedObj(obj, "error"), "code") ?? "";
63039
+ const message = extractString(obj, "message") ?? extractString(getNestedObj(obj, "error"), "message") ?? (typeof raw === "string" ? raw : "") ?? "";
63040
+ const status = extractNumber(obj, "status") ?? extractNumber(obj, "statusCode") ?? extractNumber(obj, "status_code") ?? null;
63041
+ const sdkCode = extractString(obj, "error_code") ?? "";
63042
+ if (errorType === "authentication_error" || errorCode === "authentication_error" || sdkCode === "authentication_error" || message.toLowerCase().includes("authentication_error")) {
63043
+ const msg = message.toLowerCase();
63044
+ if (msg.includes("expired") || msg.includes("refresh")) {
63045
+ return "credentials-expired";
63046
+ }
63047
+ return "credentials-invalid";
63048
+ }
63049
+ if (errorType === "invalid_api_key" || errorCode === "invalid_api_key" || sdkCode === "invalid_api_key" || message.toLowerCase().includes("invalid_api_key") || message.toLowerCase().includes("invalid api key")) {
63050
+ return "credentials-invalid";
63051
+ }
63052
+ if (errorType === "credit_balance_too_low" || errorCode === "credit_balance_too_low" || sdkCode === "credit_balance_too_low" || message.toLowerCase().includes("credit_balance_too_low") || message.toLowerCase().includes("credit balance")) {
63053
+ return "credit-exhausted";
63054
+ }
63055
+ if (errorType === "rate_limit_error" || errorCode === "rate_limit_error" || sdkCode === "rate_limit_error" || message.toLowerCase().includes("rate_limit_error") || message.toLowerCase().includes("rate limit")) {
63056
+ return "rate-limited";
63057
+ }
63058
+ if (errorType === "overloaded_error" || errorCode === "overloaded_error" || sdkCode === "overloaded_error" || message.toLowerCase().includes("overloaded_error") || message.toLowerCase().includes("overloaded")) {
63059
+ return "rate-limited";
63060
+ }
63061
+ if (errorType === "agent-crashed" || errorCode === "agent-crashed") {
63062
+ return "agent-crashed";
63063
+ }
63064
+ if (errorType === "agent-restarted-unexpectedly" || errorCode === "agent-restarted-unexpectedly") {
63065
+ return "agent-restarted-unexpectedly";
63066
+ }
63067
+ if (status != null) {
63068
+ if (status >= 400 && status < 500)
63069
+ return "unknown-4xx";
63070
+ if (status >= 500 && status < 600)
63071
+ return "unknown-5xx";
63072
+ }
63073
+ return "unknown-4xx";
63074
+ }
63075
+ function extractString(obj, key) {
63076
+ const v = obj[key];
63077
+ return typeof v === "string" && v.length > 0 ? v : null;
63078
+ }
63079
+ function extractNumber(obj, key) {
63080
+ const v = obj[key];
63081
+ return typeof v === "number" ? v : null;
63082
+ }
63083
+ function getNestedObj(obj, key) {
63084
+ const v = obj[key];
63085
+ return typeof v === "object" && v != null ? v : {};
63086
+ }
63087
+ var DEFAULT_OPERATOR_EVENT_COOLDOWN_MS = 5 * 60000;
63088
+ var cooldownMap = new Map;
63089
+
63090
+ // model-unavailable.ts
63091
+ init_quota_check();
63092
+ init_card_format();
63093
+ var transientUpstreamSignals = [
63094
+ "not your usage limit",
63095
+ "not your account",
63096
+ "not your account's",
63097
+ "temporarily limiting requests",
63098
+ "temporarily rate",
63099
+ "server is temporarily",
63100
+ "would exceed your account\u2019s rate limit",
63101
+ "would exceed your account's rate limit"
63102
+ ];
63103
+ function isTransientUpstreamSignal(text4) {
63104
+ if (typeof text4 !== "string" || text4.length === 0)
63105
+ return false;
63106
+ const sample = text4.length > 16384 ? text4.slice(0, 16384) : text4;
63107
+ const lower = sample.toLowerCase();
63108
+ return transientUpstreamSignals.some((s) => lower.includes(s));
63109
+ }
63110
+ var litellmProxyLocal429Signals = [
63111
+ "deployment over user-defined ratelimit",
63112
+ "model rate limit exceeded. tpm limit",
63113
+ "model rate limit exceeded. rpm limit",
63114
+ "deployment over defined rpm limit",
63115
+ "no deployments available for selected model",
63116
+ "litellm rate limit handler",
63117
+ "crossed tpm / rpm",
63118
+ "max parallel request limit reached"
63119
+ ];
63120
+ var litellmV3LimiterSignalPair = ["rate limit exceeded for ", "limit type:"];
63121
+ function isLitellmProxyLocal429(text4) {
63122
+ if (typeof text4 !== "string" || text4.length === 0)
63123
+ return false;
63124
+ const sample = text4.length > 16384 ? text4.slice(0, 16384) : text4;
63125
+ const lower = sample.toLowerCase();
63126
+ if (litellmProxyLocal429Signals.some((s) => lower.includes(s)))
63127
+ return true;
63128
+ return litellmV3LimiterSignalPair.every((s) => lower.includes(s));
63129
+ }
63130
+ function parseLitellmLimitDetail(text4, parseTimeNow = new Date) {
63131
+ const empty2 = { limitType: null, limit: null, currentUsage: null, resetAtMs: null };
63132
+ if (typeof text4 !== "string" || text4.length === 0)
63133
+ return empty2;
63134
+ const sample = text4.length > 16384 ? text4.slice(0, 16384) : text4;
63135
+ const lower = sample.toLowerCase();
63136
+ let limitType = null;
63137
+ let limit = null;
63138
+ const eqLimit = lower.match(/\b([tr]pm)[ _]limit[=:]\s*(\d+)/);
63139
+ if (eqLimit) {
63140
+ limitType = eqLimit[1];
63141
+ limit = Number(eqLimit[2]);
63142
+ }
63143
+ if (limitType == null) {
63144
+ const v3Type = lower.match(/limit type:\s*(tokens|requests|max_parallel_requests)/);
63145
+ if (v3Type)
63146
+ limitType = v3Type[1];
63147
+ }
63148
+ if (limit == null) {
63149
+ const v3Limit = lower.match(/current limit:\s*(\d+)/);
63150
+ if (v3Limit)
63151
+ limit = Number(v3Limit[1]);
63152
+ }
63153
+ let currentUsage = null;
63154
+ const usage = lower.match(/current usage=(\d+)/);
63155
+ if (usage)
63156
+ currentUsage = Number(usage[1]);
63157
+ let resetAtMs = null;
63158
+ const resetsAt = sample.match(/limit resets at:\s*(\d{4}-\d{2}-\d{2}) (\d{2}:\d{2}:\d{2}) UTC/i);
63159
+ if (resetsAt) {
63160
+ const d = new Date(`${resetsAt[1]}T${resetsAt[2]}Z`);
63161
+ if (!Number.isNaN(d.getTime()))
63162
+ resetAtMs = d.getTime();
63163
+ }
63164
+ if (resetAtMs == null) {
63165
+ const tryAgain = lower.match(/try again in\s+(\d+(?:\.\d+)?)\s*seconds/);
63166
+ if (tryAgain) {
63167
+ const secs = Number(tryAgain[1]);
63168
+ if (Number.isFinite(secs) && secs > 0 && secs < 7 * 24 * 3600) {
63169
+ resetAtMs = parseTimeNow.getTime() + Math.round(secs * 1000);
63170
+ }
63171
+ }
63172
+ }
63173
+ return {
63174
+ limitType,
63175
+ limit: limit != null && Number.isFinite(limit) ? limit : null,
63176
+ currentUsage: currentUsage != null && Number.isFinite(currentUsage) ? currentUsage : null,
63177
+ resetAtMs
63178
+ };
63179
+ }
63180
+ function detectModelUnavailable(stderr) {
63181
+ if (typeof stderr !== "string" || stderr.length === 0)
63182
+ return null;
63183
+ const sample = stderr.length > 16384 ? stderr.slice(0, 16384) : stderr;
63184
+ const lower = sample.toLowerCase();
63185
+ if (transientUpstreamSignals.some((s) => lower.includes(s))) {
63186
+ const resetAt = parseResetTime(sample);
63187
+ return resetAt !== undefined ? { kind: "overload", resetAt, raw: stderr } : { kind: "overload", raw: stderr };
63188
+ }
63189
+ if (isLitellmProxyLocal429(sample)) {
63190
+ const resetAt = parseResetTime(sample);
63191
+ return resetAt !== undefined ? { kind: "overload", resetAt, raw: stderr } : { kind: "overload", raw: stderr };
63192
+ }
63193
+ const quotaSignals = [
63194
+ "out of extra usage",
63195
+ "extra usage",
63196
+ "credit_balance_too_low",
63197
+ "credit balance too low",
63198
+ "usage limit",
63199
+ "usage_limit",
63200
+ "quota exhausted",
63201
+ "quota_exhausted",
63202
+ "plan limit",
63203
+ "subscription limit",
63204
+ "hit your limit",
63205
+ "hit the limit",
63206
+ "session limit",
63207
+ "session cap"
63208
+ ];
63209
+ if (quotaSignals.some((s) => lower.includes(s))) {
63210
+ const resetAt = parseResetTime(sample);
63211
+ return resetAt !== undefined ? { kind: "quota_exhausted", resetAt, raw: stderr } : { kind: "quota_exhausted", raw: stderr };
63212
+ }
63213
+ const overloadSignals = [
63214
+ "overloaded_error",
63215
+ "overloaded",
63216
+ "rate_limit_error",
63217
+ "rate limit",
63218
+ "rate-limited",
63219
+ "http 429",
63220
+ '"status":429',
63221
+ "status: 429",
63222
+ " 429 ",
63223
+ "503 service",
63224
+ "service unavailable",
63225
+ '"status":529',
63226
+ "http 529",
63227
+ " 529 "
63228
+ ];
63229
+ if (overloadSignals.some((s) => lower.includes(s))) {
63230
+ const resetAt = parseResetTime(sample);
63231
+ return resetAt !== undefined ? { kind: "overload", resetAt, raw: stderr } : { kind: "overload", raw: stderr };
63232
+ }
63233
+ const networkSignals = [
63234
+ "econnrefused",
63235
+ "econnreset",
63236
+ "etimedout",
63237
+ "enotfound",
63238
+ "eai_again",
63239
+ "fetch failed",
63240
+ "network error",
63241
+ "socket hang up",
63242
+ "request timed out",
63243
+ "connection refused",
63244
+ "getaddrinfo"
63245
+ ];
63246
+ if (networkSignals.some((s) => lower.includes(s))) {
63247
+ return { kind: "network", raw: stderr };
63248
+ }
63249
+ return null;
63250
+ }
63251
+ function parseResetTime(text4, parseTimeNow = new Date) {
63252
+ const lower = text4.toLowerCase();
63253
+ const retryAfter = lower.match(/retry[\s-]*after[:\s]+(\d+)\s*(seconds?|s\b|minutes?|m\b|hours?|h\b)?/);
63254
+ if (retryAfter) {
63255
+ const n = Number(retryAfter[1]);
63256
+ if (Number.isFinite(n) && n > 0 && n < 7 * 24 * 3600) {
63257
+ const unit = (retryAfter[2] ?? "seconds").toLowerCase();
63258
+ const ms = unit.startsWith("h") ? n * 3600000 : unit.startsWith("m") ? n * 60000 : n * 1000;
63259
+ return new Date(parseTimeNow.getTime() + ms);
63260
+ }
63261
+ }
63262
+ const relReset = lower.match(/resets?\s+in\s+([0-9hms\s]+)/);
63263
+ if (relReset) {
63264
+ const ms = parseRelativeDuration(relReset[1]);
63265
+ if (ms != null)
63266
+ return new Date(parseTimeNow.getTime() + ms);
63267
+ }
63268
+ const iso = text4.match(/\b\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:?\d{2})?\b/);
63269
+ if (iso) {
63270
+ const d = new Date(iso[0]);
63271
+ if (!Number.isNaN(d.getTime()))
63272
+ return d;
63273
+ }
63274
+ const calReset = text4.match(/resets?\s+(?:at\s+)?([A-Z][a-z]{2,8}\s+\d{1,2}(?:,?\s*(?:\d{1,2}(?::\d{2})?\s*(?:am|pm|AM|PM)?))?)/);
63275
+ if (calReset) {
63276
+ const candidate = `${calReset[1]} ${parseTimeNow.getUTCFullYear()}`;
63277
+ const d = new Date(candidate);
63278
+ if (!Number.isNaN(d.getTime()))
63279
+ return d;
63280
+ }
63281
+ const timeOnly = text4.match(/resets?\s+(?:at\s+)?(?!(?:jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)[a-z]*\b)(\d{1,2})(?::(\d{2}))?\s*(am|pm)?\s*(?:\(([^)]+)\))?/i);
63282
+ if (timeOnly) {
63283
+ const d = resolveNextWallClock(Number(timeOnly[1]), timeOnly[2] ? Number(timeOnly[2]) : 0, timeOnly[3]?.toLowerCase(), timeOnly[4]?.trim(), parseTimeNow);
63284
+ if (d != null)
63285
+ return d;
63286
+ }
63287
+ return;
63288
+ }
63289
+ function resolveNextWallClock(hour12or24, minute, ampm, tz, nowDate) {
63290
+ let hour = hour12or24;
63291
+ if (ampm === "pm" && hour < 12)
63292
+ hour += 12;
63293
+ if (ampm === "am" && hour === 12)
63294
+ hour = 0;
63295
+ if (!Number.isFinite(hour) || hour > 23 || hour < 0)
63296
+ return;
63297
+ if (!Number.isFinite(minute) || minute > 59 || minute < 0)
63298
+ return;
63299
+ const nowMs2 = nowDate.getTime();
63300
+ const base = new Date(nowMs2);
63301
+ for (let dayOffset = 0;dayOffset <= 2; dayOffset++) {
63302
+ const dateParts = tzDateParts(new Date(nowMs2 + dayOffset * 86400000), tz);
63303
+ if (dateParts == null)
63304
+ return;
63305
+ const epoch = wallClockToEpoch(dateParts.year, dateParts.month, dateParts.day, hour, minute, tz);
63306
+ if (epoch != null && epoch > nowMs2)
63307
+ return new Date(epoch);
63308
+ }
63309
+ return;
63310
+ }
63311
+ function tzDateParts(d, tz) {
63312
+ if (!tz) {
63313
+ return { year: d.getUTCFullYear(), month: d.getUTCMonth(), day: d.getUTCDate() };
63314
+ }
63315
+ try {
63316
+ const fmt = new Intl.DateTimeFormat("en-US", {
63317
+ timeZone: tz,
63318
+ year: "numeric",
63319
+ month: "2-digit",
63320
+ day: "2-digit"
63321
+ });
63322
+ const parts = Object.fromEntries(fmt.formatToParts(d).filter((p) => p.type !== "literal").map((p) => [p.type, p.value]));
63323
+ return {
63324
+ year: Number(parts.year),
63325
+ month: Number(parts.month) - 1,
63326
+ day: Number(parts.day)
63327
+ };
63328
+ } catch {
63329
+ return null;
63330
+ }
63331
+ }
63332
+ function wallClockToEpoch(year, month, day, hour, minute, tz) {
63333
+ const asUtc = Date.UTC(year, month, day, hour, minute, 0);
63334
+ if (!tz)
63335
+ return asUtc;
63336
+ try {
63337
+ const fmt = new Intl.DateTimeFormat("en-US", {
63338
+ timeZone: tz,
63339
+ year: "numeric",
63340
+ month: "2-digit",
63341
+ day: "2-digit",
63342
+ hour: "2-digit",
63343
+ minute: "2-digit",
63344
+ second: "2-digit",
63345
+ hour12: false
63346
+ });
63347
+ const parts = Object.fromEntries(fmt.formatToParts(new Date(asUtc)).filter((p) => p.type !== "literal").map((p) => [p.type, p.value]));
63348
+ const shown = Date.UTC(Number(parts.year), Number(parts.month) - 1, Number(parts.day), Number(parts.hour) % 24, Number(parts.minute), Number(parts.second));
63349
+ const offset = shown - asUtc;
63350
+ return asUtc - offset;
63351
+ } catch {
63352
+ return null;
63353
+ }
63354
+ }
63355
+ function parseRelativeDuration(s) {
63356
+ let total = 0;
63357
+ let matched = false;
63358
+ const re = /(\d+)\s*(h|hours?|m|minutes?|s|seconds?)/g;
63359
+ let m;
63360
+ while ((m = re.exec(s)) != null) {
63361
+ matched = true;
63362
+ const n = Number(m[1]);
63363
+ const unit = m[2].toLowerCase();
63364
+ if (unit.startsWith("h"))
63365
+ total += n * 3600000;
63366
+ else if (unit.startsWith("m"))
63367
+ total += n * 60000;
63368
+ else
63369
+ total += n * 1000;
63370
+ }
63371
+ return matched && total > 0 ? total : null;
63372
+ }
63373
+
63374
+ // session-tail.ts
63375
+ function sanitizeCwdToProjectName(cwd) {
63376
+ return cwd.replace(/[^a-zA-Z0-9]/g, "-");
63377
+ }
63378
+ function getProjectsDirForCwd(cwd = process.cwd(), claudeHome = process.env.CLAUDE_CONFIG_DIR ?? join21(homedir7(), ".claude")) {
63379
+ return join21(claudeHome, "projects", sanitizeCwdToProjectName(cwd));
63380
+ }
63381
+ function parseChannelMeta(content3) {
63382
+ const grab = (key) => {
63383
+ const m = content3.match(new RegExp(`(?:^|[\\s"'])${key}="([^"]+)"`));
63384
+ return m ? m[1] : null;
63385
+ };
63386
+ return {
63387
+ chatId: grab("chat_id"),
63388
+ messageId: grab("message_id"),
63389
+ threadId: grab("message_thread_id")
63390
+ };
63391
+ }
63392
+ var MAX_JSONL_LINE_BYTES = 2 * 1024 * 1024;
63393
+ var MAX_ERROR_TEXT_CHARS = 500;
63394
+ function extractToolResultErrorText(content3) {
63395
+ if (typeof content3 === "string") {
63396
+ return content3.slice(0, MAX_ERROR_TEXT_CHARS);
63397
+ }
63398
+ if (Array.isArray(content3)) {
63399
+ const parts = [];
63400
+ for (const block of content3) {
63401
+ if (typeof block === "object" && block != null) {
63402
+ const b = block;
63403
+ if (b.type === "text" && typeof b.text === "string") {
63404
+ parts.push(b.text);
63405
+ }
63406
+ }
63407
+ }
63408
+ return parts.join(`
63409
+ `).slice(0, MAX_ERROR_TEXT_CHARS);
63410
+ }
63411
+ return "";
63412
+ }
63413
+ function projectAssistantTextBlocks(content3, make) {
63414
+ const out = new Map;
63415
+ let lastToolUseIdx = -1;
63416
+ content3.forEach((c, i) => {
63417
+ if (c.type === "tool_use")
63418
+ lastToolUseIdx = i;
63419
+ });
63420
+ content3.forEach((c, i) => {
63421
+ if (c.type !== "text")
63422
+ return;
63423
+ const text4 = c.text ?? "";
63424
+ if (text4.trim().length === 0)
63425
+ return;
63426
+ out.set(i, make(text4, i, i > lastToolUseIdx));
63427
+ });
63428
+ return out;
63429
+ }
63430
+ function sumUsageTokens(usage) {
63431
+ if (usage == null || typeof usage !== "object")
63432
+ return 0;
63433
+ const u = usage;
63434
+ const n = (v) => typeof v === "number" && Number.isFinite(v) ? v : 0;
63435
+ return n(u.input_tokens) + n(u.output_tokens) + n(u.cache_creation_input_tokens);
63436
+ }
63437
+ function projectTrailingAnswerFromTranscript(transcriptText) {
63438
+ const buf = [];
63439
+ let lastMeaningful = null;
63440
+ for (const rawLine of transcriptText.split(`
63441
+ `)) {
63442
+ const line = rawLine.trim();
63443
+ if (!line)
63444
+ continue;
63445
+ if (isRealUserTurnBoundary(line)) {
63446
+ buf.length = 0;
63447
+ lastMeaningful = null;
63448
+ continue;
63449
+ }
63450
+ for (const ev of projectTranscriptLine(line)) {
63451
+ if (ev.kind === "enqueue") {
63452
+ buf.length = 0;
63453
+ lastMeaningful = null;
63454
+ } else if (ev.kind === "tool_use") {
63455
+ buf.length = 0;
63456
+ lastMeaningful = "tool_use";
63457
+ } else if (ev.kind === "text") {
63458
+ const t = ev.text ?? "";
63459
+ if (t.trim().length > 0) {
63460
+ buf.push(t);
63461
+ lastMeaningful = "text";
63462
+ }
63463
+ }
63464
+ }
63465
+ }
63466
+ const text4 = buf.join("").trim();
63467
+ return { text: text4, trailingIsText: lastMeaningful === "text" && text4.length > 0 };
63468
+ }
63469
+ function isRealUserTurnBoundary(line) {
63470
+ let obj;
63471
+ try {
63472
+ obj = JSON.parse(line);
63473
+ } catch {
63474
+ return false;
63475
+ }
63476
+ if (obj.type !== "user")
63477
+ return false;
63478
+ const message = obj.message;
63479
+ const content3 = message?.content;
63480
+ if (typeof content3 === "string")
63481
+ return content3.trim().length > 0;
63482
+ if (Array.isArray(content3)) {
63483
+ for (const c of content3) {
63484
+ if (typeof c === "object" && c != null && c.type === "text") {
63485
+ const t = String(c.text ?? "");
63486
+ if (t.trim().length > 0)
63487
+ return true;
63488
+ }
63489
+ }
63490
+ }
63491
+ return false;
63492
+ }
63493
+ function projectTranscriptLine(line) {
63494
+ if (line.length > MAX_JSONL_LINE_BYTES)
63495
+ return [];
63496
+ let obj;
63497
+ try {
63498
+ obj = JSON.parse(line);
63499
+ } catch {
63500
+ return [];
63501
+ }
63502
+ const type = obj.type;
63503
+ if (!type)
63504
+ return [];
63505
+ if (type === "queue-operation") {
63506
+ const op = obj.operation;
63507
+ if (op === "enqueue") {
63508
+ const content3 = obj.content ?? "";
63509
+ const { chatId, messageId, threadId } = parseChannelMeta(content3);
63510
+ return [{ kind: "enqueue", chatId, messageId, threadId, rawContent: content3 }];
63511
+ }
63512
+ if (op === "dequeue") {
63513
+ return [{ kind: "dequeue" }];
63514
+ }
63515
+ return [];
63516
+ }
63517
+ if (type === "assistant") {
63518
+ const message = obj.message;
63519
+ const content3 = message?.content;
63520
+ if (!Array.isArray(content3))
63521
+ return [];
63522
+ if (obj.isApiErrorMessage === true) {
63523
+ const mainModel2 = message?.model;
63524
+ return typeof mainModel2 === "string" && !isModelSentinel(mainModel2) ? [{ kind: "model", model: mainModel2 }] : [];
63525
+ }
63526
+ const events = [];
63527
+ const mainModel = message?.model;
63528
+ if (typeof mainModel === "string" && !isModelSentinel(mainModel)) {
63529
+ events.push({ kind: "model", model: mainModel });
63530
+ }
63531
+ const mainUsageTotal = sumUsageTokens(message?.usage);
63532
+ if (mainUsageTotal > 0) {
63533
+ const mainMsgId = message?.id;
63534
+ events.push({
63535
+ kind: "usage",
63536
+ messageId: typeof mainMsgId === "string" ? mainMsgId : null,
63537
+ totalTokens: mainUsageTotal
63538
+ });
63539
+ }
63540
+ const textEvents = projectAssistantTextBlocks(content3, (text4, blockIndex, lastInMessage) => ({ kind: "text", text: text4, blockIndex, lastInMessage }));
63541
+ content3.forEach((c, i) => {
63542
+ const ct = c.type;
63543
+ if (ct === "thinking") {
63544
+ events.push({ kind: "thinking" });
63545
+ } else if (ct === "tool_use") {
63546
+ const input = c.input;
63547
+ events.push({
63548
+ kind: "tool_use",
63549
+ toolName: c.name ?? "",
63550
+ toolUseId: c.id ?? null,
63551
+ input: input && typeof input === "object" ? input : undefined
63552
+ });
63553
+ } else if (ct === "text") {
63554
+ const ev = textEvents.get(i);
63555
+ if (ev != null)
63556
+ events.push(ev);
63557
+ }
63558
+ });
63559
+ return events;
63560
+ }
63561
+ if (type === "user") {
63562
+ const message = obj.message;
63563
+ const content3 = message?.content;
63564
+ if (!Array.isArray(content3))
63565
+ return [];
63566
+ const events = [];
63567
+ for (const c of content3) {
63568
+ if (c.type === "tool_result") {
63569
+ const isError2 = c.is_error === true ? true : undefined;
63570
+ events.push({
63571
+ kind: "tool_result",
63572
+ toolUseId: c.tool_use_id ?? "",
63573
+ toolName: null,
63574
+ isError: isError2,
63575
+ errorText: isError2 ? extractToolResultErrorText(c.content) : undefined
63576
+ });
63577
+ }
63578
+ }
63579
+ return events;
63580
+ }
63581
+ if (type === "system" && obj.subtype === "turn_duration") {
63582
+ return [
63583
+ { kind: "turn_end", durationMs: obj.durationMs ?? 0 }
63584
+ ];
63585
+ }
63586
+ return [];
63587
+ }
63588
+
62875
63589
  // pty-tail.ts
62876
63590
  var import_headless = __toESM(require_xterm_headless(), 1);
62877
63591
  var PTY_DEBUG = process.env.SWITCHROOM_PTY_DEBUG === "1";
@@ -62956,7 +63670,7 @@ async function gatewayStartupRetry(fn, opts = {}) {
62956
63670
 
62957
63671
  // gateway/quarantine.ts
62958
63672
  import { mkdirSync as mkdirSync15, writeFileSync as writeFileSync15 } from "node:fs";
62959
- import { join as join20 } from "node:path";
63673
+ import { join as join22 } from "node:path";
62960
63674
  var QUARANTINE_FILENAME = "quarantine.json";
62961
63675
  function writeQuarantineMarker(telegramStateDir, reason, detail, nowFn = Date.now) {
62962
63676
  mkdirSync15(telegramStateDir, { recursive: true, mode: 448 });
@@ -62966,7 +63680,7 @@ function writeQuarantineMarker(telegramStateDir, reason, detail, nowFn = Date.no
62966
63680
  ts: nowFn(),
62967
63681
  detail
62968
63682
  };
62969
- writeFileSync15(join20(telegramStateDir, QUARANTINE_FILENAME), JSON.stringify(marker) + `
63683
+ writeFileSync15(join22(telegramStateDir, QUARANTINE_FILENAME), JSON.stringify(marker) + `
62970
63684
  `, "utf-8");
62971
63685
  }
62972
63686
 
@@ -63985,9 +64699,9 @@ function defaultAddAccount(label, credentials, opts) {
63985
64699
  // ../src/auth/broker/client.ts
63986
64700
  init_protocol2();
63987
64701
  import * as net3 from "node:net";
63988
- import { homedir as homedir7 } from "node:os";
64702
+ import { homedir as homedir8 } from "node:os";
63989
64703
  import { randomUUID as randomUUID4 } from "node:crypto";
63990
- import { join as join22 } from "node:path";
64704
+ import { join as join24 } from "node:path";
63991
64705
  var DEFAULT_TIMEOUT_MS3 = 5000;
63992
64706
  function reviveDate2(v) {
63993
64707
  if (v == null)
@@ -63997,8 +64711,8 @@ function reviveDate2(v) {
63997
64711
  const d = new Date(v);
63998
64712
  return Number.isNaN(d.getTime()) ? null : d;
63999
64713
  }
64000
- function operatorSocketPath2(home2 = homedir7()) {
64001
- return join22(home2, ".switchroom", "state", "auth-broker-operator", "sock");
64714
+ function operatorSocketPath2(home2 = homedir8()) {
64715
+ return join24(home2, ".switchroom", "state", "auth-broker-operator", "sock");
64002
64716
  }
64003
64717
  function resolveAuthBrokerSocketPath2(opts) {
64004
64718
  if (opts?.socket)
@@ -64312,13 +65026,13 @@ class AuthBrokerClient2 {
64312
65026
  init_loader();
64313
65027
  init_resolver();
64314
65028
  init_vault();
64315
- import { existsSync as existsSync16 } from "node:fs";
65029
+ import { existsSync as existsSync17 } from "node:fs";
64316
65030
  var DEFAULT_VOICE_API_KEY_REF = "vault:openai/api-key";
64317
65031
  function tryDirectVaultRead(ref, config, passphrase) {
64318
65032
  if (!passphrase)
64319
65033
  return null;
64320
65034
  const vaultPath = resolvePath(config.vault?.path ?? "~/.switchroom/vault.enc");
64321
- if (!existsSync16(vaultPath))
65035
+ if (!existsSync17(vaultPath))
64322
65036
  return null;
64323
65037
  try {
64324
65038
  const secrets = openVault(passphrase, vaultPath);
@@ -64374,14 +65088,14 @@ async function materializeVoiceKey(opts = {}, logger2 = (line) => process.stderr
64374
65088
  init_loader();
64375
65089
  init_resolver();
64376
65090
  init_vault();
64377
- import { existsSync as existsSync17 } from "node:fs";
65091
+ import { existsSync as existsSync18 } from "node:fs";
64378
65092
  var VOICE_SIDECAR_TOKEN_KEY = "voice/sidecar-token";
64379
65093
  var DEFAULT_VOICE_SIDECAR_TOKEN_REF = `vault:${VOICE_SIDECAR_TOKEN_KEY}`;
64380
65094
  function tryDirectVaultRead2(ref, config, passphrase) {
64381
65095
  if (!passphrase)
64382
65096
  return null;
64383
65097
  const vaultPath = resolvePath(config.vault?.path ?? "~/.switchroom/vault.enc");
64384
- if (!existsSync17(vaultPath))
65098
+ if (!existsSync18(vaultPath))
64385
65099
  return null;
64386
65100
  try {
64387
65101
  const secrets = openVault(passphrase, vaultPath);
@@ -64434,17 +65148,17 @@ async function materializeSidecarToken(opts = {}, logger2 = (line) => process.st
64434
65148
  }
64435
65149
 
64436
65150
  // ../src/setup/host-capabilities.ts
64437
- import { existsSync as existsSync18, mkdirSync as mkdirSync18, readFileSync as readFileSync18, writeFileSync as writeFileSync16 } from "node:fs";
65151
+ import { existsSync as existsSync19, mkdirSync as mkdirSync18, readFileSync as readFileSync19, writeFileSync as writeFileSync16 } from "node:fs";
64438
65152
  init_paths();
64439
65153
  function hostCapabilitiesPath() {
64440
65154
  return resolveStatePath("host-capabilities.json");
64441
65155
  }
64442
65156
  function loadHostCapabilities() {
64443
65157
  const path2 = hostCapabilitiesPath();
64444
- if (!existsSync18(path2))
65158
+ if (!existsSync19(path2))
64445
65159
  return null;
64446
65160
  try {
64447
- const parsed = JSON.parse(readFileSync18(path2, "utf-8"));
65161
+ const parsed = JSON.parse(readFileSync19(path2, "utf-8"));
64448
65162
  if (parsed && typeof parsed === "object" && "voice" in parsed && typeof parsed.voice === "object") {
64449
65163
  return parsed;
64450
65164
  }
@@ -64552,16 +65266,16 @@ function resolveExhaustUntil(resetAtMs, now = Date.now()) {
64552
65266
 
64553
65267
  // gateway/auth-add-flow.ts
64554
65268
  import { execFileSync as execFileSync3 } from "node:child_process";
64555
- import { existsSync as existsSync20, mkdirSync as mkdirSync20, readFileSync as readFileSync20, readdirSync as readdirSync4, rmSync as rmSync3, statSync as statSync7, writeFileSync as writeFileSync18 } from "node:fs";
64556
- import { homedir as homedir8 } from "node:os";
64557
- import { join as join23 } from "node:path";
65269
+ import { existsSync as existsSync21, mkdirSync as mkdirSync20, readFileSync as readFileSync21, readdirSync as readdirSync4, rmSync as rmSync3, statSync as statSync7, writeFileSync as writeFileSync18 } from "node:fs";
65270
+ import { homedir as homedir9 } from "node:os";
65271
+ import { join as join25 } from "node:path";
64558
65272
  import { randomBytes as randomBytes5 } from "node:crypto";
64559
65273
 
64560
65274
  // ../src/auth/manager.ts
64561
65275
  import {
64562
- readFileSync as readFileSync19,
65276
+ readFileSync as readFileSync20,
64563
65277
  readdirSync as readdirSync3,
64564
- existsSync as existsSync19,
65278
+ existsSync as existsSync20,
64565
65279
  writeFileSync as writeFileSync17,
64566
65280
  mkdirSync as mkdirSync19,
64567
65281
  mkdtempSync as mkdtempSync2,
@@ -64588,9 +65302,9 @@ function parseSetupTokenUrl(output) {
64588
65302
  }
64589
65303
  function readTokenFromCredentialsFile(credentialsFilePath) {
64590
65304
  try {
64591
- if (!existsSync19(credentialsFilePath))
65305
+ if (!existsSync20(credentialsFilePath))
64592
65306
  return null;
64593
- const raw = readFileSync19(credentialsFilePath, "utf-8");
65307
+ const raw = readFileSync20(credentialsFilePath, "utf-8");
64594
65308
  const parsed = JSON.parse(raw);
64595
65309
  const token = parsed?.claudeAiOauth?.accessToken;
64596
65310
  if (typeof token !== "string")
@@ -64648,9 +65362,9 @@ function makeAuthAddTmuxOps(tmuxBin = "tmux") {
64648
65362
  };
64649
65363
  }
64650
65364
  var pendingAuthAddFlows = new Map;
64651
- function pickScratchDir(label, home2 = homedir8()) {
65365
+ function pickScratchDir(label, home2 = homedir9()) {
64652
65366
  const suffix = randomBytes5(8).toString("hex");
64653
- return join23(home2, ".switchroom", "accounts", ".in-progress", `${label}-${suffix}`);
65367
+ return join25(home2, ".switchroom", "accounts", ".in-progress", `${label}-${suffix}`);
64654
65368
  }
64655
65369
  function cleanScratchDir(scratchDir) {
64656
65370
  try {
@@ -64659,8 +65373,8 @@ function cleanScratchDir(scratchDir) {
64659
65373
  }
64660
65374
  var AUTH_TMUX_SESSION_FILE = ".auth-tmux-session";
64661
65375
  function sweepOrphanSessions(home2, tmux, nowMs2 = Date.now()) {
64662
- const inProgressDir = join23(home2, ".switchroom", "accounts", ".in-progress");
64663
- if (!existsSync20(inProgressDir))
65376
+ const inProgressDir = join25(home2, ".switchroom", "accounts", ".in-progress");
65377
+ if (!existsSync21(inProgressDir))
64664
65378
  return;
64665
65379
  let entries;
64666
65380
  try {
@@ -64670,16 +65384,16 @@ function sweepOrphanSessions(home2, tmux, nowMs2 = Date.now()) {
64670
65384
  }
64671
65385
  const tenMinMs = 10 * 60000;
64672
65386
  for (const entry of entries) {
64673
- const dir = join23(inProgressDir, entry);
64674
- const sessionFile = join23(dir, AUTH_TMUX_SESSION_FILE);
64675
- if (!existsSync20(sessionFile))
65387
+ const dir = join25(inProgressDir, entry);
65388
+ const sessionFile = join25(dir, AUTH_TMUX_SESSION_FILE);
65389
+ if (!existsSync21(sessionFile))
64676
65390
  continue;
64677
65391
  let fileContents;
64678
65392
  let fileMtime;
64679
65393
  try {
64680
65394
  const stat = statSync7(sessionFile);
64681
65395
  fileMtime = stat.mtimeMs;
64682
- fileContents = readFileSync20(sessionFile, "utf8").trim();
65396
+ fileContents = readFileSync21(sessionFile, "utf8").trim();
64683
65397
  } catch {
64684
65398
  continue;
64685
65399
  }
@@ -64699,7 +65413,7 @@ async function startAccountAuthSession(label, opts = {}) {
64699
65413
  if (process.env.SWITCHROOM_TMUX_SUPERVISOR !== "1" && !opts.tmuxOps) {
64700
65414
  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).");
64701
65415
  }
64702
- const home2 = opts.home ?? homedir8();
65416
+ const home2 = opts.home ?? homedir9();
64703
65417
  const urlTimeoutMs = opts.urlTimeoutMs ?? 12000;
64704
65418
  const agentName3 = opts.agentName ?? process.env.SWITCHROOM_AGENT_NAME ?? "gateway";
64705
65419
  const tmux = opts.tmuxOps ?? makeAuthAddTmuxOps(opts.tmuxBin);
@@ -64711,7 +65425,7 @@ async function startAccountAuthSession(label, opts = {}) {
64711
65425
  const tmuxSocket = `switchroom-${agentName3}`;
64712
65426
  const tmuxSession = `auth-add-${label}-${hexSuffix}`.slice(0, 64);
64713
65427
  try {
64714
- writeFileSync18(join23(scratchDir, AUTH_TMUX_SESSION_FILE), `${tmuxSocket}
65428
+ writeFileSync18(join25(scratchDir, AUTH_TMUX_SESSION_FILE), `${tmuxSocket}
64715
65429
  ${tmuxSession}`, "utf8");
64716
65430
  } catch {}
64717
65431
  const sessionEnv = {
@@ -64760,7 +65474,7 @@ async function submitAccountAuthCode(flow3, code2, opts = {}) {
64760
65474
  const pollIntervalMs = opts.pollIntervalMs ?? 250;
64761
65475
  const pollTimeoutMs = opts.pollTimeoutMs ?? 300000;
64762
65476
  const tmux = opts.tmuxOps ?? makeAuthAddTmuxOps();
64763
- const credentialsPath = join23(flow3.scratchDir, ".credentials.json");
65477
+ const credentialsPath = join25(flow3.scratchDir, ".credentials.json");
64764
65478
  try {
64765
65479
  tmux.send(flow3.tmuxSocket, flow3.tmuxSession, code2);
64766
65480
  } catch (err) {
@@ -64770,11 +65484,11 @@ async function submitAccountAuthCode(flow3, code2, opts = {}) {
64770
65484
  const deadline = Date.now() + pollTimeoutMs;
64771
65485
  while (Date.now() < deadline) {
64772
65486
  await new Promise((r) => setTimeout(r, pollIntervalMs));
64773
- if (existsSync20(credentialsPath)) {
65487
+ if (existsSync21(credentialsPath)) {
64774
65488
  const token = readTokenFromCredentialsFile(credentialsPath);
64775
65489
  if (token) {
64776
65490
  try {
64777
- const raw = readFileSync20(credentialsPath, "utf-8");
65491
+ const raw = readFileSync21(credentialsPath, "utf-8");
64778
65492
  const parsed = JSON.parse(raw);
64779
65493
  if (parsed.claudeAiOauth?.accessToken) {
64780
65494
  tmux.killSession(flow3.tmuxSocket, flow3.tmuxSession);
@@ -64784,7 +65498,7 @@ async function submitAccountAuthCode(flow3, code2, opts = {}) {
64784
65498
  }
64785
65499
  }
64786
65500
  if (!tmux.hasSession(flow3.tmuxSocket, flow3.tmuxSession)) {
64787
- if (!existsSync20(credentialsPath)) {
65501
+ if (!existsSync21(credentialsPath)) {
64788
65502
  cleanScratchDir(flow3.scratchDir);
64789
65503
  throw new Error("claude setup-token exited without writing credentials \u2014 the code may be invalid or expired");
64790
65504
  }
@@ -65236,28 +65950,6 @@ function autoClassifyMidTurnInbound(i) {
65236
65950
 
65237
65951
  // operator-events.ts
65238
65952
  init_format();
65239
-
65240
- // raw-error-scrub.ts
65241
- function stripRawErrorBytes(raw) {
65242
- if (typeof raw !== "string" || raw.length === 0)
65243
- return "";
65244
- let s = raw;
65245
- s = s.replace(/API Error:?\s*\d*\s*/gi, " ");
65246
- s = s.replace(/\bb'[^']*'/g, " ");
65247
- s = s.replace(/\bb"[^"]*"/g, " ");
65248
- s = s.replace(/[\u00b7\-\s]*\{[\s\S]*?["']type["']\s*:\s*["']error["'][\s\S]*$/i, " ");
65249
- s = s.replace(/^\s*\{[\s\S]*\}\s*$/g, " ");
65250
- s = s.replace(/\s+/g, " ").replace(/[\u00b7:\-\s]+$/g, "").replace(/^[\u00b7:\-\s]+/g, "").trim();
65251
- return s;
65252
- }
65253
- function extractRequestId(raw) {
65254
- if (typeof raw !== "string" || raw.length === 0)
65255
- return;
65256
- const m = raw.match(/["']request[_-]?id["']\s*:\s*["']([A-Za-z0-9._-]+)["']/i) ?? raw.match(/\brequest[_-]?id[=:]\s*([A-Za-z0-9._-]+)/i);
65257
- return m ? m[1] : undefined;
65258
- }
65259
-
65260
- // operator-events.ts
65261
65953
  function renderOperatorEvent(ev) {
65262
65954
  const agent = escapeMarkdown(ev.agent);
65263
65955
  const detail = escapeMarkdown(stripRawErrorBytes(ev.detail));
@@ -65445,15 +66137,15 @@ function renderOperatorEvent(ev) {
65445
66137
  };
65446
66138
  }
65447
66139
  }
65448
- var DEFAULT_OPERATOR_EVENT_COOLDOWN_MS = 5 * 60000;
65449
- var cooldownMap = new Map;
65450
- function shouldEmitOperatorEvent(agent, kind, now = Date.now(), cooldownMs = DEFAULT_OPERATOR_EVENT_COOLDOWN_MS) {
66140
+ var DEFAULT_OPERATOR_EVENT_COOLDOWN_MS2 = 5 * 60000;
66141
+ var cooldownMap2 = new Map;
66142
+ function shouldEmitOperatorEvent(agent, kind, now = Date.now(), cooldownMs = DEFAULT_OPERATOR_EVENT_COOLDOWN_MS2) {
65451
66143
  const key = `${agent}:${kind}`;
65452
- const last = cooldownMap.get(key);
66144
+ const last = cooldownMap2.get(key);
65453
66145
  if (last != null && now - last < cooldownMs) {
65454
66146
  return false;
65455
66147
  }
65456
- cooldownMap.set(key, now);
66148
+ cooldownMap2.set(key, now);
65457
66149
  return true;
65458
66150
  }
65459
66151
 
@@ -65464,290 +66156,6 @@ function recordOperatorEvent(event, now = Date.now()) {
65464
66156
  store.set(event.agent, { event, storedAt: now });
65465
66157
  }
65466
66158
 
65467
- // model-unavailable.ts
65468
- init_quota_check();
65469
- init_card_format();
65470
- var transientUpstreamSignals = [
65471
- "not your usage limit",
65472
- "not your account",
65473
- "not your account's",
65474
- "temporarily limiting requests",
65475
- "temporarily rate",
65476
- "server is temporarily",
65477
- "would exceed your account\u2019s rate limit",
65478
- "would exceed your account's rate limit"
65479
- ];
65480
- function isTransientUpstreamSignal(text4) {
65481
- if (typeof text4 !== "string" || text4.length === 0)
65482
- return false;
65483
- const sample = text4.length > 16384 ? text4.slice(0, 16384) : text4;
65484
- const lower = sample.toLowerCase();
65485
- return transientUpstreamSignals.some((s) => lower.includes(s));
65486
- }
65487
- var litellmProxyLocal429Signals = [
65488
- "deployment over user-defined ratelimit",
65489
- "model rate limit exceeded. tpm limit",
65490
- "model rate limit exceeded. rpm limit",
65491
- "deployment over defined rpm limit",
65492
- "no deployments available for selected model",
65493
- "litellm rate limit handler",
65494
- "crossed tpm / rpm",
65495
- "max parallel request limit reached"
65496
- ];
65497
- var litellmV3LimiterSignalPair = ["rate limit exceeded for ", "limit type:"];
65498
- function isLitellmProxyLocal429(text4) {
65499
- if (typeof text4 !== "string" || text4.length === 0)
65500
- return false;
65501
- const sample = text4.length > 16384 ? text4.slice(0, 16384) : text4;
65502
- const lower = sample.toLowerCase();
65503
- if (litellmProxyLocal429Signals.some((s) => lower.includes(s)))
65504
- return true;
65505
- return litellmV3LimiterSignalPair.every((s) => lower.includes(s));
65506
- }
65507
- function parseLitellmLimitDetail(text4, parseTimeNow = new Date) {
65508
- const empty2 = { limitType: null, limit: null, currentUsage: null, resetAtMs: null };
65509
- if (typeof text4 !== "string" || text4.length === 0)
65510
- return empty2;
65511
- const sample = text4.length > 16384 ? text4.slice(0, 16384) : text4;
65512
- const lower = sample.toLowerCase();
65513
- let limitType = null;
65514
- let limit = null;
65515
- const eqLimit = lower.match(/\b([tr]pm)[ _]limit[=:]\s*(\d+)/);
65516
- if (eqLimit) {
65517
- limitType = eqLimit[1];
65518
- limit = Number(eqLimit[2]);
65519
- }
65520
- if (limitType == null) {
65521
- const v3Type = lower.match(/limit type:\s*(tokens|requests|max_parallel_requests)/);
65522
- if (v3Type)
65523
- limitType = v3Type[1];
65524
- }
65525
- if (limit == null) {
65526
- const v3Limit = lower.match(/current limit:\s*(\d+)/);
65527
- if (v3Limit)
65528
- limit = Number(v3Limit[1]);
65529
- }
65530
- let currentUsage = null;
65531
- const usage = lower.match(/current usage=(\d+)/);
65532
- if (usage)
65533
- currentUsage = Number(usage[1]);
65534
- let resetAtMs = null;
65535
- const resetsAt = sample.match(/limit resets at:\s*(\d{4}-\d{2}-\d{2}) (\d{2}:\d{2}:\d{2}) UTC/i);
65536
- if (resetsAt) {
65537
- const d = new Date(`${resetsAt[1]}T${resetsAt[2]}Z`);
65538
- if (!Number.isNaN(d.getTime()))
65539
- resetAtMs = d.getTime();
65540
- }
65541
- if (resetAtMs == null) {
65542
- const tryAgain = lower.match(/try again in\s+(\d+(?:\.\d+)?)\s*seconds/);
65543
- if (tryAgain) {
65544
- const secs = Number(tryAgain[1]);
65545
- if (Number.isFinite(secs) && secs > 0 && secs < 7 * 24 * 3600) {
65546
- resetAtMs = parseTimeNow.getTime() + Math.round(secs * 1000);
65547
- }
65548
- }
65549
- }
65550
- return {
65551
- limitType,
65552
- limit: limit != null && Number.isFinite(limit) ? limit : null,
65553
- currentUsage: currentUsage != null && Number.isFinite(currentUsage) ? currentUsage : null,
65554
- resetAtMs
65555
- };
65556
- }
65557
- function detectModelUnavailable(stderr) {
65558
- if (typeof stderr !== "string" || stderr.length === 0)
65559
- return null;
65560
- const sample = stderr.length > 16384 ? stderr.slice(0, 16384) : stderr;
65561
- const lower = sample.toLowerCase();
65562
- if (transientUpstreamSignals.some((s) => lower.includes(s))) {
65563
- const resetAt = parseResetTime(sample);
65564
- return resetAt !== undefined ? { kind: "overload", resetAt, raw: stderr } : { kind: "overload", raw: stderr };
65565
- }
65566
- if (isLitellmProxyLocal429(sample)) {
65567
- const resetAt = parseResetTime(sample);
65568
- return resetAt !== undefined ? { kind: "overload", resetAt, raw: stderr } : { kind: "overload", raw: stderr };
65569
- }
65570
- const quotaSignals = [
65571
- "out of extra usage",
65572
- "extra usage",
65573
- "credit_balance_too_low",
65574
- "credit balance too low",
65575
- "usage limit",
65576
- "usage_limit",
65577
- "quota exhausted",
65578
- "quota_exhausted",
65579
- "plan limit",
65580
- "subscription limit",
65581
- "hit your limit",
65582
- "hit the limit",
65583
- "session limit",
65584
- "session cap"
65585
- ];
65586
- if (quotaSignals.some((s) => lower.includes(s))) {
65587
- const resetAt = parseResetTime(sample);
65588
- return resetAt !== undefined ? { kind: "quota_exhausted", resetAt, raw: stderr } : { kind: "quota_exhausted", raw: stderr };
65589
- }
65590
- const overloadSignals = [
65591
- "overloaded_error",
65592
- "overloaded",
65593
- "rate_limit_error",
65594
- "rate limit",
65595
- "rate-limited",
65596
- "http 429",
65597
- '"status":429',
65598
- "status: 429",
65599
- " 429 ",
65600
- "503 service",
65601
- "service unavailable",
65602
- '"status":529',
65603
- "http 529",
65604
- " 529 "
65605
- ];
65606
- if (overloadSignals.some((s) => lower.includes(s))) {
65607
- const resetAt = parseResetTime(sample);
65608
- return resetAt !== undefined ? { kind: "overload", resetAt, raw: stderr } : { kind: "overload", raw: stderr };
65609
- }
65610
- const networkSignals = [
65611
- "econnrefused",
65612
- "econnreset",
65613
- "etimedout",
65614
- "enotfound",
65615
- "eai_again",
65616
- "fetch failed",
65617
- "network error",
65618
- "socket hang up",
65619
- "request timed out",
65620
- "connection refused",
65621
- "getaddrinfo"
65622
- ];
65623
- if (networkSignals.some((s) => lower.includes(s))) {
65624
- return { kind: "network", raw: stderr };
65625
- }
65626
- return null;
65627
- }
65628
- function parseResetTime(text4, parseTimeNow = new Date) {
65629
- const lower = text4.toLowerCase();
65630
- const retryAfter = lower.match(/retry[\s-]*after[:\s]+(\d+)\s*(seconds?|s\b|minutes?|m\b|hours?|h\b)?/);
65631
- if (retryAfter) {
65632
- const n = Number(retryAfter[1]);
65633
- if (Number.isFinite(n) && n > 0 && n < 7 * 24 * 3600) {
65634
- const unit = (retryAfter[2] ?? "seconds").toLowerCase();
65635
- const ms = unit.startsWith("h") ? n * 3600000 : unit.startsWith("m") ? n * 60000 : n * 1000;
65636
- return new Date(parseTimeNow.getTime() + ms);
65637
- }
65638
- }
65639
- const relReset = lower.match(/resets?\s+in\s+([0-9hms\s]+)/);
65640
- if (relReset) {
65641
- const ms = parseRelativeDuration(relReset[1]);
65642
- if (ms != null)
65643
- return new Date(parseTimeNow.getTime() + ms);
65644
- }
65645
- const iso = text4.match(/\b\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:?\d{2})?\b/);
65646
- if (iso) {
65647
- const d = new Date(iso[0]);
65648
- if (!Number.isNaN(d.getTime()))
65649
- return d;
65650
- }
65651
- const calReset = text4.match(/resets?\s+(?:at\s+)?([A-Z][a-z]{2,8}\s+\d{1,2}(?:,?\s*(?:\d{1,2}(?::\d{2})?\s*(?:am|pm|AM|PM)?))?)/);
65652
- if (calReset) {
65653
- const candidate = `${calReset[1]} ${parseTimeNow.getUTCFullYear()}`;
65654
- const d = new Date(candidate);
65655
- if (!Number.isNaN(d.getTime()))
65656
- return d;
65657
- }
65658
- const timeOnly = text4.match(/resets?\s+(?:at\s+)?(?!(?:jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)[a-z]*\b)(\d{1,2})(?::(\d{2}))?\s*(am|pm)?\s*(?:\(([^)]+)\))?/i);
65659
- if (timeOnly) {
65660
- const d = resolveNextWallClock(Number(timeOnly[1]), timeOnly[2] ? Number(timeOnly[2]) : 0, timeOnly[3]?.toLowerCase(), timeOnly[4]?.trim(), parseTimeNow);
65661
- if (d != null)
65662
- return d;
65663
- }
65664
- return;
65665
- }
65666
- function resolveNextWallClock(hour12or24, minute, ampm, tz, nowDate) {
65667
- let hour = hour12or24;
65668
- if (ampm === "pm" && hour < 12)
65669
- hour += 12;
65670
- if (ampm === "am" && hour === 12)
65671
- hour = 0;
65672
- if (!Number.isFinite(hour) || hour > 23 || hour < 0)
65673
- return;
65674
- if (!Number.isFinite(minute) || minute > 59 || minute < 0)
65675
- return;
65676
- const nowMs2 = nowDate.getTime();
65677
- const base = new Date(nowMs2);
65678
- for (let dayOffset = 0;dayOffset <= 2; dayOffset++) {
65679
- const dateParts = tzDateParts(new Date(nowMs2 + dayOffset * 86400000), tz);
65680
- if (dateParts == null)
65681
- return;
65682
- const epoch = wallClockToEpoch(dateParts.year, dateParts.month, dateParts.day, hour, minute, tz);
65683
- if (epoch != null && epoch > nowMs2)
65684
- return new Date(epoch);
65685
- }
65686
- return;
65687
- }
65688
- function tzDateParts(d, tz) {
65689
- if (!tz) {
65690
- return { year: d.getUTCFullYear(), month: d.getUTCMonth(), day: d.getUTCDate() };
65691
- }
65692
- try {
65693
- const fmt = new Intl.DateTimeFormat("en-US", {
65694
- timeZone: tz,
65695
- year: "numeric",
65696
- month: "2-digit",
65697
- day: "2-digit"
65698
- });
65699
- const parts = Object.fromEntries(fmt.formatToParts(d).filter((p) => p.type !== "literal").map((p) => [p.type, p.value]));
65700
- return {
65701
- year: Number(parts.year),
65702
- month: Number(parts.month) - 1,
65703
- day: Number(parts.day)
65704
- };
65705
- } catch {
65706
- return null;
65707
- }
65708
- }
65709
- function wallClockToEpoch(year, month, day, hour, minute, tz) {
65710
- const asUtc = Date.UTC(year, month, day, hour, minute, 0);
65711
- if (!tz)
65712
- return asUtc;
65713
- try {
65714
- const fmt = new Intl.DateTimeFormat("en-US", {
65715
- timeZone: tz,
65716
- year: "numeric",
65717
- month: "2-digit",
65718
- day: "2-digit",
65719
- hour: "2-digit",
65720
- minute: "2-digit",
65721
- second: "2-digit",
65722
- hour12: false
65723
- });
65724
- const parts = Object.fromEntries(fmt.formatToParts(new Date(asUtc)).filter((p) => p.type !== "literal").map((p) => [p.type, p.value]));
65725
- const shown = Date.UTC(Number(parts.year), Number(parts.month) - 1, Number(parts.day), Number(parts.hour) % 24, Number(parts.minute), Number(parts.second));
65726
- const offset = shown - asUtc;
65727
- return asUtc - offset;
65728
- } catch {
65729
- return null;
65730
- }
65731
- }
65732
- function parseRelativeDuration(s) {
65733
- let total = 0;
65734
- let matched = false;
65735
- const re = /(\d+)\s*(h|hours?|m|minutes?|s|seconds?)/g;
65736
- let m;
65737
- while ((m = re.exec(s)) != null) {
65738
- matched = true;
65739
- const n = Number(m[1]);
65740
- const unit = m[2].toLowerCase();
65741
- if (unit.startsWith("h"))
65742
- total += n * 3600000;
65743
- else if (unit.startsWith("m"))
65744
- total += n * 60000;
65745
- else
65746
- total += n * 1000;
65747
- }
65748
- return matched && total > 0 ? total : null;
65749
- }
65750
-
65751
66159
  // throttle-tier.ts
65752
66160
  init_card_format();
65753
66161
  init_quota_check();
@@ -65807,72 +66215,6 @@ function renderThrottleEscalationNotice(opts) {
65807
66215
  ${tail}`;
65808
66216
  }
65809
66217
 
65810
- // operator-events.ts
65811
- init_format();
65812
- function classifyClaudeError(raw) {
65813
- try {
65814
- return classifyInner(raw);
65815
- } catch {
65816
- return "unknown-4xx";
65817
- }
65818
- }
65819
- function classifyInner(raw) {
65820
- if (raw == null)
65821
- return "unknown-4xx";
65822
- const obj = typeof raw === "object" ? raw : {};
65823
- const errorType = extractString(obj, "error_type") ?? extractString(obj, "type") ?? extractString(getNestedObj(obj, "error"), "type") ?? "";
65824
- const errorCode = extractString(obj, "code") ?? extractString(getNestedObj(obj, "error"), "code") ?? "";
65825
- const message = extractString(obj, "message") ?? extractString(getNestedObj(obj, "error"), "message") ?? (typeof raw === "string" ? raw : "") ?? "";
65826
- const status = extractNumber(obj, "status") ?? extractNumber(obj, "statusCode") ?? extractNumber(obj, "status_code") ?? null;
65827
- const sdkCode = extractString(obj, "error_code") ?? "";
65828
- if (errorType === "authentication_error" || errorCode === "authentication_error" || sdkCode === "authentication_error" || message.toLowerCase().includes("authentication_error")) {
65829
- const msg = message.toLowerCase();
65830
- if (msg.includes("expired") || msg.includes("refresh")) {
65831
- return "credentials-expired";
65832
- }
65833
- return "credentials-invalid";
65834
- }
65835
- if (errorType === "invalid_api_key" || errorCode === "invalid_api_key" || sdkCode === "invalid_api_key" || message.toLowerCase().includes("invalid_api_key") || message.toLowerCase().includes("invalid api key")) {
65836
- return "credentials-invalid";
65837
- }
65838
- if (errorType === "credit_balance_too_low" || errorCode === "credit_balance_too_low" || sdkCode === "credit_balance_too_low" || message.toLowerCase().includes("credit_balance_too_low") || message.toLowerCase().includes("credit balance")) {
65839
- return "credit-exhausted";
65840
- }
65841
- if (errorType === "rate_limit_error" || errorCode === "rate_limit_error" || sdkCode === "rate_limit_error" || message.toLowerCase().includes("rate_limit_error") || message.toLowerCase().includes("rate limit")) {
65842
- return "rate-limited";
65843
- }
65844
- if (errorType === "overloaded_error" || errorCode === "overloaded_error" || sdkCode === "overloaded_error" || message.toLowerCase().includes("overloaded_error") || message.toLowerCase().includes("overloaded")) {
65845
- return "rate-limited";
65846
- }
65847
- if (errorType === "agent-crashed" || errorCode === "agent-crashed") {
65848
- return "agent-crashed";
65849
- }
65850
- if (errorType === "agent-restarted-unexpectedly" || errorCode === "agent-restarted-unexpectedly") {
65851
- return "agent-restarted-unexpectedly";
65852
- }
65853
- if (status != null) {
65854
- if (status >= 400 && status < 500)
65855
- return "unknown-4xx";
65856
- if (status >= 500 && status < 600)
65857
- return "unknown-5xx";
65858
- }
65859
- return "unknown-4xx";
65860
- }
65861
- function extractString(obj, key) {
65862
- const v = obj[key];
65863
- return typeof v === "string" && v.length > 0 ? v : null;
65864
- }
65865
- function extractNumber(obj, key) {
65866
- const v = obj[key];
65867
- return typeof v === "number" ? v : null;
65868
- }
65869
- function getNestedObj(obj, key) {
65870
- const v = obj[key];
65871
- return typeof v === "object" && v != null ? v : {};
65872
- }
65873
- var DEFAULT_OPERATOR_EVENT_COOLDOWN_MS2 = 5 * 60000;
65874
- var cooldownMap2 = new Map;
65875
-
65876
66218
  // llm-error-present.ts
65877
66219
  function extractModel(raw) {
65878
66220
  const m = raw.match(/["']?model["']?\s*[=:]\s*["']?((?:claude|sr)[A-Za-z0-9._-]+)/i);
@@ -67373,6 +67715,35 @@ function richMessage2(markdown) {
67373
67715
  return { markdown };
67374
67716
  }
67375
67717
 
67718
+ // gateway/redelivery-decision.ts
67719
+ var REDELIVERY_PREFIX = "Recovered from an interrupted turn:";
67720
+ function decideRedeliverCapture(input) {
67721
+ if (input.willBeResumed)
67722
+ return { capture: false, skipReason: "will-be-resumed" };
67723
+ if (!input.hasSessionId)
67724
+ return { capture: false, skipReason: "no-session-id" };
67725
+ return { capture: true };
67726
+ }
67727
+ function frameRedelivery(capturedText) {
67728
+ return `${REDELIVERY_PREFIX}
67729
+
67730
+ ${capturedText.trim()}`;
67731
+ }
67732
+ function decideRedeliver(input) {
67733
+ const text4 = input.capturedText.trim();
67734
+ if (text4.length === 0)
67735
+ return { redeliver: false, skipReason: "empty-text" };
67736
+ if (!input.trailingIsText)
67737
+ return { redeliver: false, skipReason: "trailing-not-text" };
67738
+ if (input.alreadyRedelivered)
67739
+ return { redeliver: false, skipReason: "already-redelivered" };
67740
+ if (input.hasDeliveredText)
67741
+ return { redeliver: false, skipReason: "already-delivered" };
67742
+ if (input.ageMs > input.maxAgeMs)
67743
+ return { redeliver: false, skipReason: "stale" };
67744
+ return { redeliver: true, framedText: frameRedelivery(text4) };
67745
+ }
67746
+
67376
67747
  // text-voice-scrub.ts
67377
67748
  var NULL3 = "\x00";
67378
67749
  var FENCE_PH2 = `${NULL3}VS_FENCE`;
@@ -68360,10 +68731,10 @@ function resolveAgentDirFromEnv() {
68360
68731
 
68361
68732
  // active-reactions.ts
68362
68733
  import { readFileSync as readFileSync22, writeFileSync as writeFileSync19, renameSync as renameSync7, existsSync as existsSync23, unlinkSync as unlinkSync11 } from "node:fs";
68363
- import { join as join26 } from "node:path";
68734
+ import { join as join27 } from "node:path";
68364
68735
  var ACTIVE_REACTIONS_FILENAME = ".active-reactions.json";
68365
68736
  function reactionsPath(agentDir) {
68366
- return join26(agentDir, ACTIVE_REACTIONS_FILENAME);
68737
+ return join27(agentDir, ACTIVE_REACTIONS_FILENAME);
68367
68738
  }
68368
68739
  function readActiveReactions(agentDir) {
68369
68740
  const p = reactionsPath(agentDir);
@@ -68428,10 +68799,10 @@ function clearActiveReactions(agentDir) {
68428
68799
 
68429
68800
  // active-reactions.ts
68430
68801
  import { readFileSync as readFileSync23, writeFileSync as writeFileSync20, renameSync as renameSync8, existsSync as existsSync24, unlinkSync as unlinkSync12 } from "node:fs";
68431
- import { join as join27 } from "node:path";
68802
+ import { join as join28 } from "node:path";
68432
68803
  var ACTIVE_REACTIONS_FILENAME2 = ".active-reactions.json";
68433
68804
  function reactionsPath2(agentDir) {
68434
- return join27(agentDir, ACTIVE_REACTIONS_FILENAME2);
68805
+ return join28(agentDir, ACTIVE_REACTIONS_FILENAME2);
68435
68806
  }
68436
68807
  function readActiveReactions2(agentDir) {
68437
68808
  const p = reactionsPath2(agentDir);
@@ -69349,12 +69720,12 @@ async function approvalRecord(args, opts) {
69349
69720
 
69350
69721
  // quota-check.ts
69351
69722
  import { readFileSync as readFileSync24, existsSync as existsSync25 } from "fs";
69352
- import { join as join28 } from "path";
69723
+ import { join as join29 } from "path";
69353
69724
  var OAUTH_BETA2 = "oauth-2025-04-20";
69354
69725
  var DEFAULT_USER_AGENT2 = "claude-cli/1.0.0 (external, cli)";
69355
69726
  var DEFAULT_PROBE_MODEL2 = "claude-haiku-4-5-20251001";
69356
69727
  function readOauthToken2(claudeConfigDir) {
69357
- const tokenFile = join28(claudeConfigDir, ".oauth-token");
69728
+ const tokenFile = join29(claudeConfigDir, ".oauth-token");
69358
69729
  if (!existsSync25(tokenFile))
69359
69730
  return null;
69360
69731
  try {
@@ -69754,18 +70125,32 @@ async function injectSlashCommand(agentName3, command, opts = {}) {
69754
70125
  const socket = opts.socketName ?? defaultSocketName(agentName3);
69755
70126
  const session = opts.sessionName ?? agentName3;
69756
70127
  const settleMs = opts.settleMs ?? 2000;
69757
- const timeoutMs = opts.timeoutMs ?? 5000;
70128
+ const signalMode = !!(opts.successPattern || opts.errorPattern);
70129
+ const timeoutMs = opts.timeoutMs ?? (signalMode ? 8000 : 5000);
69758
70130
  return withPaneLock(`${socket}:${session}`, () => injectSlashCommandWith(makeTmuxRunner(tmuxBin), {
69759
70131
  socket,
69760
70132
  session,
69761
70133
  command: command.trim(),
69762
70134
  settleMs,
69763
70135
  timeoutMs,
69764
- precondition: opts.precondition
70136
+ precondition: opts.precondition,
70137
+ successPattern: opts.successPattern,
70138
+ errorPattern: opts.errorPattern,
70139
+ settleBeforeSendMs: opts.settleBeforeSendMs
69765
70140
  }));
69766
70141
  }
69767
70142
  async function injectSlashCommandWith(runner, args) {
69768
- const { socket, session, command, settleMs, timeoutMs, precondition } = args;
70143
+ const {
70144
+ socket,
70145
+ session,
70146
+ command,
70147
+ settleMs,
70148
+ timeoutMs,
70149
+ precondition,
70150
+ successPattern,
70151
+ errorPattern,
70152
+ settleBeforeSendMs
70153
+ } = args;
69769
70154
  let bareVerb;
69770
70155
  try {
69771
70156
  bareVerb = validateInjectCommand(command);
@@ -69806,6 +70191,17 @@ async function injectSlashCommandWith(runner, args) {
69806
70191
  errorMessage: "inject precondition returned false at write time; send aborted " + "(no keys sent)."
69807
70192
  };
69808
70193
  }
70194
+ if (settleBeforeSendMs && settleBeforeSendMs > 0) {
70195
+ const settleStart = Date.now();
70196
+ let prevSettle = runner.capture(socket, session) ?? "";
70197
+ while (Date.now() - settleStart < settleBeforeSendMs) {
70198
+ await sleep(POLL_INTERVAL_MS2);
70199
+ const cur = runner.capture(socket, session) ?? "";
70200
+ if (cur === prevSettle)
70201
+ break;
70202
+ prevSettle = cur;
70203
+ }
70204
+ }
69809
70205
  const before = runner.capture(socket, session) ?? "";
69810
70206
  try {
69811
70207
  runner.send(socket, session, ["send-keys", "-l", command]);
@@ -69824,9 +70220,23 @@ async function injectSlashCommandWith(runner, args) {
69824
70220
  const start = Date.now();
69825
70221
  let last = before;
69826
70222
  let stableSince = null;
70223
+ const signalMode = !!(successPattern || errorPattern);
69827
70224
  while (Date.now() - start < timeoutMs) {
69828
70225
  await sleep(POLL_INTERVAL_MS2);
69829
70226
  const cur = runner.capture(socket, session) ?? "";
70227
+ if (signalMode) {
70228
+ last = cur;
70229
+ if (cur !== before) {
70230
+ const { output: region } = diffPane(before, cur, command);
70231
+ const regionLines = region.split(`
70232
+ `).map((l) => l.trim());
70233
+ if (errorPattern && regionLines.some((l) => errorPattern.test(l)))
70234
+ break;
70235
+ if (successPattern && regionLines.some((l) => successPattern.test(l)))
70236
+ break;
70237
+ }
70238
+ continue;
70239
+ }
69830
70240
  if (cur === last && cur !== before) {
69831
70241
  if (stableSince === null) {
69832
70242
  stableSince = Date.now();
@@ -70261,7 +70671,11 @@ async function handleModelCommand(parsed, deps) {
70261
70671
  const verbHtml = `\`/model ${deps.escapeHtml(model)}\``;
70262
70672
  let result;
70263
70673
  try {
70264
- result = await deps.inject(deps.getAgentName(), `/model ${model}`);
70674
+ result = await deps.inject(deps.getAgentName(), `/model ${model}`, {
70675
+ successPattern: MODEL_SWITCH_CONFIRMATION_PREFIX,
70676
+ errorPattern: MODEL_SWITCH_ERROR_RE,
70677
+ settleBeforeSendMs: 1500
70678
+ });
70265
70679
  } catch (err) {
70266
70680
  const msg = err instanceof Error ? err.message : String(err);
70267
70681
  return {
@@ -70269,21 +70683,21 @@ async function handleModelCommand(parsed, deps) {
70269
70683
  html: true
70270
70684
  };
70271
70685
  }
70272
- if (result.outcome === "ok") {
70273
- const errLine = modelSwitchErrorLine(result.output);
70274
- if (errLine) {
70275
- return {
70276
- text: [
70277
- `\u274c ${verbHtml} \u2014 the switch did not take:`,
70278
- deps.preBlock(errLine),
70279
- "Check `/model` for valid model names."
70280
- ].join(`
70281
- `),
70282
- html: true
70283
- };
70284
- }
70285
- const confirmation = modelSwitchConfirmationLine(result.output);
70686
+ if (result.outcome === "ok" || result.outcome === "ok_no_output") {
70687
+ const confirmation = result.outcome === "ok" ? modelSwitchConfirmationLine(result.output) : null;
70286
70688
  if (confirmation) {
70689
+ if (isKeptModelConfirmation(confirmation)) {
70690
+ return {
70691
+ text: [
70692
+ `${verbHtml}`,
70693
+ deps.preBlock(confirmation),
70694
+ ...result.truncated ? ["_truncated_"] : [],
70695
+ PERSIST_NOTE
70696
+ ].join(`
70697
+ `),
70698
+ html: true
70699
+ };
70700
+ }
70287
70701
  const confirmed = sessionModelFromConfirmation(confirmation) ?? model;
70288
70702
  return {
70289
70703
  text: [
@@ -70294,26 +70708,31 @@ async function handleModelCommand(parsed, deps) {
70294
70708
  ].join(`
70295
70709
  `),
70296
70710
  html: true,
70297
- ...isKeptModelConfirmation(confirmation) ? {} : { selectedModel: confirmed }
70711
+ selectedModel: confirmed
70298
70712
  };
70299
70713
  }
70300
- return {
70301
- text: [
70302
- `${verbHtml} \u2014 sent, but couldn't confirm the switch \u2014 check \`/status\`.`,
70303
- PERSIST_NOTE
70304
- ].join(`
70714
+ const errLine = result.outcome === "ok" ? modelSwitchErrorLine(result.output) : null;
70715
+ if (errLine) {
70716
+ return {
70717
+ text: [
70718
+ `\u274c ${verbHtml} \u2014 the switch did not take:`,
70719
+ deps.preBlock(errLine),
70720
+ "Check `/model` for a valid, available model."
70721
+ ].join(`
70305
70722
  `),
70306
- html: true
70307
- };
70308
- }
70309
- if (result.outcome === "ok_no_output") {
70723
+ html: true
70724
+ };
70725
+ }
70726
+ const optimisticLabel = optimisticModelRecordLabel(model);
70310
70727
  return {
70311
70728
  text: [
70312
- `${verbHtml} \u2014 sent, but no response captured. The agent may be mid-turn; check \`/inject /status\` to confirm the active model.`,
70729
+ `${verbHtml} \u2014 sent, but couldn't read a confirmation line. \`/status\` will show the live model once it's confirmed.`,
70313
70730
  PERSIST_NOTE
70314
70731
  ].join(`
70315
70732
  `),
70316
- html: true
70733
+ html: true,
70734
+ selectedModel: optimisticLabel,
70735
+ optimistic: true
70317
70736
  };
70318
70737
  }
70319
70738
  if (result.errorCode === "session_missing") {
@@ -70376,6 +70795,15 @@ function expandSrAlias(arg) {
70376
70795
  function srFriendlyLabel(srName) {
70377
70796
  return SR_MODEL_LABELS[srName] ?? srName.replace(/^sr-/, "").replace(/-/g, " ");
70378
70797
  }
70798
+ function optimisticModelRecordLabel(token) {
70799
+ if (isSrModel(token))
70800
+ return token;
70801
+ const lower = token.toLowerCase();
70802
+ if (MODEL_ALIASES.includes(lower)) {
70803
+ return lower.charAt(0).toUpperCase() + lower.slice(1);
70804
+ }
70805
+ return token;
70806
+ }
70379
70807
  function classifyDiscoveredOptions(options) {
70380
70808
  return {
70381
70809
  claude: options.filter((o) => !o.label.startsWith("sr-") && !o.label.includes("/") && (/^[A-Z]/.test(o.label) || o.label.startsWith("claude-"))),
@@ -70548,7 +70976,11 @@ async function handleModelMenuCallback(data, deps) {
70548
70976
  }
70549
70977
  let aliasResult;
70550
70978
  try {
70551
- aliasResult = await deps.inject(deps.getAgentName(), `/model ${alias}`);
70979
+ aliasResult = await deps.inject(deps.getAgentName(), `/model ${alias}`, {
70980
+ successPattern: MODEL_SWITCH_CONFIRMATION_PREFIX,
70981
+ errorPattern: MODEL_SWITCH_ERROR_RE,
70982
+ settleBeforeSendMs: 1500
70983
+ });
70552
70984
  } catch (err) {
70553
70985
  const msg = err instanceof Error ? err.message : String(err);
70554
70986
  return {
@@ -70556,16 +70988,32 @@ async function handleModelMenuCallback(data, deps) {
70556
70988
  reply: await menuWithBanner(deps, `\u274c Switch to **${deps.escapeHtml(alias)}** failed: ${deps.escapeHtml(msg)}`)
70557
70989
  };
70558
70990
  }
70559
- if (aliasResult.outcome === "ok") {
70560
- const confirmation = modelSwitchConfirmationLine(aliasResult.output) ?? `Switched to ${alias} (session)`;
70561
- const kept = isKeptModelConfirmation(confirmation);
70991
+ if (aliasResult.outcome === "ok" || aliasResult.outcome === "ok_no_output") {
70992
+ const confirmation = aliasResult.outcome === "ok" ? modelSwitchConfirmationLine(aliasResult.output) : null;
70993
+ if (confirmation) {
70994
+ const kept = isKeptModelConfirmation(confirmation);
70995
+ return {
70996
+ answer: confirmation,
70997
+ reply: await menuWithBannerStatic(deps, `\u2705 ${deps.escapeHtml(confirmation)}`),
70998
+ ...kept ? {} : {
70999
+ selectedModel: sessionModelFromConfirmation(confirmation) ?? optimisticModelRecordLabel(alias),
71000
+ selectedModelToken: alias
71001
+ }
71002
+ };
71003
+ }
71004
+ const aliasErr = aliasResult.outcome === "ok" ? modelSwitchErrorLine(aliasResult.output) : null;
71005
+ if (aliasErr) {
71006
+ return {
71007
+ answer: "Switch failed",
71008
+ reply: await menuWithBanner(deps, `\u274c Switch to **${deps.escapeHtml(alias)}** did not take: ${deps.escapeHtml(aliasErr)}`)
71009
+ };
71010
+ }
71011
+ const optimisticLabel = optimisticModelRecordLabel(alias);
70562
71012
  return {
70563
- answer: confirmation,
70564
- reply: await menuWithBannerStatic(deps, `\u2705 ${deps.escapeHtml(confirmation)}`),
70565
- ...kept ? {} : {
70566
- selectedModel: sessionModelFromConfirmation(confirmation) ?? alias,
70567
- selectedModelToken: alias
70568
- }
71013
+ answer: `Sent /model ${alias} \u2014 check /status`,
71014
+ reply: await menuWithBannerStatic(deps, `Sent \`/model ${deps.escapeHtml(alias)}\` \u2014 couldn\u2019t read a confirmation line. \`/status\` will show the live model once it\u2019s confirmed.`),
71015
+ selectedModel: optimisticLabel,
71016
+ selectedModelToken: alias
70569
71017
  };
70570
71018
  }
70571
71019
  return {
@@ -70662,7 +71110,7 @@ function modelSwitchConfirmationLine(output) {
70662
71110
  `).map((l) => l.trim()).find((l) => MODEL_SWITCH_CONFIRMATION_PREFIX.test(l));
70663
71111
  return line && line.length > 0 ? line : null;
70664
71112
  }
70665
- var MODEL_SWITCH_ERROR_RE = /^\s*[\u23fa\u25cf\u2022>\u23bf-]?\s*(?:Error:\s*)?(?:Model(?:\s+'[^']+')?\s+not found|Invalid model|Unknown model|No such model)\b/i;
71113
+ var MODEL_SWITCH_ERROR_RE = /^\s*[\u23fa\u25cf\u2022>\u23bf-]?\s*(?:Error:\s*)?(?:Model(?:\s+'[^']+')?\s+not found|Invalid model|Unknown model|No such model|(?:[\w'\u2019.\-]+\s+){0,4}(?:(?:is |are )?(?:not available|unavailable|not enabled|not supported)|access denied|requires\b[^\n]{0,40}\b(?:subscription|plan)|no access)\b)/i;
70666
71114
  function modelSwitchErrorLine(output) {
70667
71115
  const line = output.split(`
70668
71116
  `).map((l) => l.trim()).find((l) => MODEL_SWITCH_ERROR_RE.test(l));
@@ -70711,7 +71159,7 @@ async function menuWithBannerStatic(deps, banner) {
70711
71159
 
70712
71160
  // gateway/session-model-file.ts
70713
71161
  import { readFileSync as readFileSync25, writeFileSync as writeFileSync21, renameSync as renameSync9, rmSync as rmSync4 } from "node:fs";
70714
- import { join as join29 } from "node:path";
71162
+ import { join as join30 } from "node:path";
70715
71163
 
70716
71164
  // gateway/model-command.ts
70717
71165
  var MODEL_ARG_RE2 = /^[A-Za-z0-9][A-Za-z0-9._/\[\]-]{0,99}$/;
@@ -70740,18 +71188,18 @@ function writeSessionModelFile(agentDir, model, configuredDefaultAtWrite) {
70740
71188
  if (!isValidModelArg2(model)) {
70741
71189
  throw new Error(`refusing to persist non-canonical session model token: ${JSON.stringify(model)}`);
70742
71190
  }
70743
- atomicWrite(join29(agentDir, SESSION_MODEL_FILE), serializeSessionModel({ model, configuredDefaultAtWrite, ts: Date.now() }));
71191
+ atomicWrite(join30(agentDir, SESSION_MODEL_FILE), serializeSessionModel({ model, configuredDefaultAtWrite, ts: Date.now() }));
70744
71192
  }
70745
71193
  function readSessionModelFileRaw(agentDir) {
70746
71194
  try {
70747
- return readFileSync25(join29(agentDir, SESSION_MODEL_FILE), "utf8");
71195
+ return readFileSync25(join30(agentDir, SESSION_MODEL_FILE), "utf8");
70748
71196
  } catch {
70749
71197
  return null;
70750
71198
  }
70751
71199
  }
70752
71200
  function clearSessionModelFile(agentDir) {
70753
71201
  try {
70754
- rmSync4(join29(agentDir, SESSION_MODEL_FILE), { force: true });
71202
+ rmSync4(join30(agentDir, SESSION_MODEL_FILE), { force: true });
70755
71203
  } catch {}
70756
71204
  }
70757
71205
  function restoreSessionModelFileRaw(agentDir, raw) {
@@ -70760,12 +71208,12 @@ function restoreSessionModelFileRaw(agentDir, raw) {
70760
71208
  return;
70761
71209
  }
70762
71210
  try {
70763
- atomicWrite(join29(agentDir, SESSION_MODEL_FILE), raw);
71211
+ atomicWrite(join30(agentDir, SESSION_MODEL_FILE), raw);
70764
71212
  } catch {}
70765
71213
  }
70766
71214
  function readConfiguredDefaultModel(agentDir) {
70767
71215
  try {
70768
- const v = readFileSync25(join29(agentDir, CONFIGURED_DEFAULT_MODEL_FILE), "utf8").trim();
71216
+ const v = readFileSync25(join30(agentDir, CONFIGURED_DEFAULT_MODEL_FILE), "utf8").trim();
70769
71217
  return v.length > 0 ? v : null;
70770
71218
  } catch {
70771
71219
  return null;
@@ -70777,12 +71225,12 @@ function writeSessionEffortFile(agentDir, level, configuredDefaultAtWrite) {
70777
71225
  if (!EFFORT_LEVEL_RE.test(level)) {
70778
71226
  throw new Error(`refusing to persist non-allowlisted effort level: ${JSON.stringify(level)}`);
70779
71227
  }
70780
- atomicWrite(join29(agentDir, SESSION_EFFORT_FILE), `${JSON.stringify({ level, configuredDefaultAtWrite: configuredDefaultAtWrite ?? "", ts: Date.now() })}
71228
+ atomicWrite(join30(agentDir, SESSION_EFFORT_FILE), `${JSON.stringify({ level, configuredDefaultAtWrite: configuredDefaultAtWrite ?? "", ts: Date.now() })}
70781
71229
  `);
70782
71230
  }
70783
71231
  function clearSessionEffortFile(agentDir) {
70784
71232
  try {
70785
- rmSync4(join29(agentDir, SESSION_EFFORT_FILE), { force: true });
71233
+ rmSync4(join30(agentDir, SESSION_EFFORT_FILE), { force: true });
70786
71234
  } catch {}
70787
71235
  }
70788
71236
  var PREMIUM_RECOVERY_FILE = ".premium-recovery";
@@ -70805,13 +71253,13 @@ function writePremiumRecoveryFile(agentDir, premiumModel, chats) {
70805
71253
  if (clean.length === 0) {
70806
71254
  throw new Error("refusing to persist premium-recovery marker with no chats to notify");
70807
71255
  }
70808
- atomicWrite(join29(agentDir, PREMIUM_RECOVERY_FILE), `${JSON.stringify({ premiumModel, chats: clean, ts: Date.now() })}
71256
+ atomicWrite(join30(agentDir, PREMIUM_RECOVERY_FILE), `${JSON.stringify({ premiumModel, chats: clean, ts: Date.now() })}
70809
71257
  `);
70810
71258
  }
70811
71259
  function readPremiumRecoveryFile(agentDir) {
70812
71260
  let raw;
70813
71261
  try {
70814
- raw = readFileSync25(join29(agentDir, PREMIUM_RECOVERY_FILE), "utf8");
71262
+ raw = readFileSync25(join30(agentDir, PREMIUM_RECOVERY_FILE), "utf8");
70815
71263
  } catch {
70816
71264
  return null;
70817
71265
  }
@@ -70824,7 +71272,7 @@ function readPremiumRecoveryFile(agentDir) {
70824
71272
  }
70825
71273
  function clearPremiumRecoveryFile(agentDir) {
70826
71274
  try {
70827
- rmSync4(join29(agentDir, PREMIUM_RECOVERY_FILE), { force: true });
71275
+ rmSync4(join30(agentDir, PREMIUM_RECOVERY_FILE), { force: true });
70828
71276
  } catch {}
70829
71277
  }
70830
71278
 
@@ -71016,7 +71464,7 @@ function makeIo(agentName3, opts) {
71016
71464
  socket: opts.socketName ?? `switchroom-${agentName3}`,
71017
71465
  session: opts.sessionName ?? agentName3,
71018
71466
  stepMs: opts.stepMs ?? 600,
71019
- timeoutMs: opts.timeoutMs ?? 1e4,
71467
+ timeoutMs: opts.timeoutMs ?? 12000,
71020
71468
  sleep: opts._sleep ?? realSleep,
71021
71469
  log: opts._log ?? ((line) => process.stderr.write(`${line}
71022
71470
  `)),
@@ -71039,7 +71487,7 @@ function sendLiteral(io, text4) {
71039
71487
  function sendKey(io, key) {
71040
71488
  io.runner.send(io.socket, io.session, ["send-keys", key]);
71041
71489
  }
71042
- async function openPicker(io) {
71490
+ async function openPicker(io, deadlineMs) {
71043
71491
  sendLiteral(io, "/model");
71044
71492
  sendKey(io, "Enter");
71045
71493
  for (;; ) {
@@ -71048,10 +71496,24 @@ async function openPicker(io) {
71048
71496
  const parsed = parseModelPicker(pane);
71049
71497
  if (parsed?.footerSeen)
71050
71498
  return parsed;
71051
- if (expired(io))
71499
+ if (deadlineMs != null && Date.now() >= deadlineMs || expired(io)) {
71052
71500
  return parsed;
71501
+ }
71053
71502
  }
71054
71503
  }
71504
+ async function openPickerWithRetry(io) {
71505
+ const remaining = io.timeoutMs - (Date.now() - io.startedAt);
71506
+ const firstDeadline = Date.now() + Math.max(io.stepMs * 2, Math.floor(remaining / 2));
71507
+ const parsed = await openPicker(io, firstDeadline);
71508
+ if (parsed?.footerSeen)
71509
+ return parsed;
71510
+ if (expired(io))
71511
+ return parsed;
71512
+ await dismissPicker(io);
71513
+ if (expired(io))
71514
+ return parsed;
71515
+ return openPicker(io);
71516
+ }
71055
71517
  async function dismissPicker(io) {
71056
71518
  for (let attempt = 0;attempt < 2; attempt++) {
71057
71519
  try {
@@ -71081,7 +71543,7 @@ async function discoverModels(agentName3, opts = {}) {
71081
71543
  let parsed = null;
71082
71544
  let dismissed = true;
71083
71545
  try {
71084
- parsed = await openPicker(io);
71546
+ parsed = await openPickerWithRetry(io);
71085
71547
  } finally {
71086
71548
  dismissed = await dismissOrWarn(io, "discover");
71087
71549
  }
@@ -71106,7 +71568,7 @@ async function selectModel(agentName3, targetLabel, opts = {}) {
71106
71568
  io.startedAt = Date.now();
71107
71569
  let selected = false;
71108
71570
  try {
71109
- const parsed = await openPicker(io);
71571
+ const parsed = await openPickerWithRetry(io);
71110
71572
  if (!parsed || !parsed.footerSeen) {
71111
71573
  return { ok: false, reason: "picker did not render \u2014 agent may be mid-turn" };
71112
71574
  }
@@ -71163,7 +71625,7 @@ function extractConfirmation(pane) {
71163
71625
  }
71164
71626
 
71165
71627
  // ../src/agents/scaffold.ts
71166
- import { join as join32, resolve as resolve6 } from "node:path";
71628
+ import { join as join33, resolve as resolve6 } from "node:path";
71167
71629
  init_atomic();
71168
71630
 
71169
71631
  // ../src/agents/agent-uid.ts
@@ -71187,8 +71649,8 @@ var CONTAINER_DEFAULT_UTC_ZONES = new Set([
71187
71649
  ]);
71188
71650
 
71189
71651
  // ../src/cli/agent-config.ts
71190
- import { join as join30 } from "node:path";
71191
- import { homedir as homedir9 } from "node:os";
71652
+ import { join as join31 } from "node:path";
71653
+ import { homedir as homedir10 } from "node:os";
71192
71654
 
71193
71655
  // ../src/cli/helpers.ts
71194
71656
  init_loader();
@@ -71208,12 +71670,12 @@ var WEBKITE_VAULT_KEYS = new Set([
71208
71670
  init_overlay_loader();
71209
71671
 
71210
71672
  // ../src/cli/agent-config.ts
71211
- var AUDIT_ROOT = join30(homedir9(), ".switchroom", "audit");
71673
+ var AUDIT_ROOT = join31(homedir10(), ".switchroom", "audit");
71212
71674
 
71213
71675
  // ../src/agents/profiles.ts
71214
71676
  var import_handlebars = __toESM(require_lib(), 1);
71215
71677
  import { readFileSync as readFileSync26, writeFileSync as writeFileSync22, existsSync as existsSync26, readdirSync as readdirSync5, statSync as statSync8, copyFileSync, mkdirSync as mkdirSync22, realpathSync as realpathSync2 } from "node:fs";
71216
- import { resolve as resolve5, join as join31, sep as pathSep } from "node:path";
71678
+ import { resolve as resolve5, join as join32, sep as pathSep } from "node:path";
71217
71679
  var PROFILES_ROOT = resolve5(import.meta.dirname, "../../profiles");
71218
71680
  import_handlebars.default.registerHelper("json", (value) => {
71219
71681
  return new import_handlebars.default.SafeString(JSON.stringify(value, null, 2));
@@ -71224,7 +71686,7 @@ import_handlebars.default.registerHelper("isNumber", (value) => {
71224
71686
  var SHARED_FRAGMENTS_DIR = resolve5(PROFILES_ROOT, "_shared");
71225
71687
  var SHARED_FRAGMENTS = ["vault-protocol", "agent-self-service", "execution-discipline", "reply-discipline", "dev-protocol"];
71226
71688
  for (const name of SHARED_FRAGMENTS) {
71227
- const fragPath = join31(SHARED_FRAGMENTS_DIR, `${name}.md.hbs`);
71689
+ const fragPath = join32(SHARED_FRAGMENTS_DIR, `${name}.md.hbs`);
71228
71690
  if (existsSync26(fragPath)) {
71229
71691
  import_handlebars.default.registerPartial(name, readFileSync26(fragPath, "utf-8"));
71230
71692
  }
@@ -71936,7 +72398,7 @@ init_overlay_loader();
71936
72398
  init_merge();
71937
72399
  var import_yaml4 = __toESM(require_dist(), 1);
71938
72400
  import { readFileSync as readFileSync27, existsSync as existsSync27 } from "node:fs";
71939
- import { homedir as homedir10 } from "node:os";
72401
+ import { homedir as homedir11 } from "node:os";
71940
72402
  import { resolve as resolve7 } from "node:path";
71941
72403
 
71942
72404
  class ConfigError2 extends Error {
@@ -71993,7 +72455,7 @@ function coerceLegacyGoogleWorkspaceKeys2(parsed, filePath) {
71993
72455
  }
71994
72456
  function findConfigFile2(startDir) {
71995
72457
  const envPath = process.env.SWITCHROOM_CONFIG;
71996
- const home2 = homedir10();
72458
+ const home2 = homedir11();
71997
72459
  const userDir = resolve7(home2, ".switchroom");
71998
72460
  const searchPaths = [
71999
72461
  envPath ? resolve7(envPath) : null,
@@ -72632,7 +73094,7 @@ function numField(obj, key) {
72632
73094
 
72633
73095
  // gateway/context-occupancy.ts
72634
73096
  import { mkdirSync as mkdirSync23, writeFileSync as writeFileSync23 } from "node:fs";
72635
- import { join as join33 } from "node:path";
73097
+ import { join as join34 } from "node:path";
72636
73098
  var CONTEXT_OCCUPANCY_FILENAME = "context-occupancy.json";
72637
73099
  var TIGHT_FRACTION = 0.8;
72638
73100
  function buildContextOccupancy(occupancy, cap, now) {
@@ -72655,7 +73117,7 @@ function buildContextOccupancy(occupancy, cap, now) {
72655
73117
  }
72656
73118
  function writeContextOccupancySnapshot(stateDir, snapshot, deps) {
72657
73119
  try {
72658
- const path2 = join33(stateDir, CONTEXT_OCCUPANCY_FILENAME);
73120
+ const path2 = join34(stateDir, CONTEXT_OCCUPANCY_FILENAME);
72659
73121
  (deps?.mkdir ?? ((p, o) => mkdirSync23(p, o)))(stateDir, { recursive: true });
72660
73122
  (deps?.writeFile ?? ((p, d) => writeFileSync23(p, d)))(path2, JSON.stringify(snapshot, null, 2) + `
72661
73123
  `);
@@ -73150,12 +73612,12 @@ function startWebhookIngestServer(opts) {
73150
73612
 
73151
73613
  // ../src/web/webhook-gateway-record.ts
73152
73614
  import { appendFileSync as appendFileSync5, mkdirSync as mkdirSync26 } from "fs";
73153
- import { join as join36 } from "path";
73154
- import { homedir as homedir12 } from "os";
73615
+ import { join as join37 } from "path";
73616
+ import { homedir as homedir13 } from "os";
73155
73617
 
73156
73618
  // ../src/web/webhook-handler.ts
73157
73619
  import { appendFileSync as appendFileSync4, existsSync as existsSync31, mkdirSync as mkdirSync24, readFileSync as readFileSync29, writeFileSync as writeFileSync24 } from "fs";
73158
- import { join as join34 } from "path";
73620
+ import { join as join35 } from "path";
73159
73621
  var DEDUP_MAX = 1000;
73160
73622
  var DEDUP_TTL_MS = 24 * 60 * 60 * 1000;
73161
73623
  function loadDedupFile(path2) {
@@ -73184,8 +73646,8 @@ var agentDedupCache = new Map;
73184
73646
  function createFileDedupStore(resolveAgentDir) {
73185
73647
  return {
73186
73648
  check(agent, deliveryId, now) {
73187
- const telegramDir = join34(resolveAgentDir(agent), "telegram");
73188
- const filePath = join34(telegramDir, "webhook-dedup.json");
73649
+ const telegramDir = join35(resolveAgentDir(agent), "telegram");
73650
+ const filePath = join35(telegramDir, "webhook-dedup.json");
73189
73651
  if (!agentDedupCache.has(agent)) {
73190
73652
  agentDedupCache.set(agent, loadDedupFile(filePath));
73191
73653
  }
@@ -73207,8 +73669,8 @@ var throttleIssueWindow = new Map;
73207
73669
 
73208
73670
  // ../src/web/webhook-dispatch.ts
73209
73671
  import { existsSync as existsSync32, mkdirSync as mkdirSync25, readFileSync as readFileSync30, writeFileSync as writeFileSync25 } from "fs";
73210
- import { join as join35 } from "path";
73211
- import { homedir as homedir11 } from "os";
73672
+ import { join as join36 } from "path";
73673
+ import { homedir as homedir12 } from "os";
73212
73674
 
73213
73675
  // ../src/agent-scheduler/ipc-client.ts
73214
73676
  import { createConnection as createConnection2 } from "node:net";
@@ -73527,8 +73989,8 @@ function createFileCooldownStore(resolveAgentDir) {
73527
73989
  isCoolingDown(agent, key, cooldownMs, now) {
73528
73990
  if (cooldownMs <= 0)
73529
73991
  return false;
73530
- const telegramDir = join35(resolveAgentDir(agent), "telegram");
73531
- const filePath = join35(telegramDir, "webhook-cooldown.json");
73992
+ const telegramDir = join36(resolveAgentDir(agent), "telegram");
73993
+ const filePath = join36(telegramDir, "webhook-cooldown.json");
73532
73994
  if (!cache.has(agent)) {
73533
73995
  cache.set(agent, loadCooldownFile(filePath));
73534
73996
  }
@@ -73585,9 +74047,9 @@ async function defaultInject(socketPath, agentName3, inbound) {
73585
74047
  }
73586
74048
  function injectWebhookInbound(agent, prompt, ctx, deps = {}) {
73587
74049
  const log = deps.log ?? ((s) => process.stderr.write(s));
73588
- const resolveAgentDir = deps.resolveAgentDir ?? ((a) => join35(homedir11(), ".switchroom", "agents", a));
74050
+ const resolveAgentDir = deps.resolveAgentDir ?? ((a) => join36(homedir12(), ".switchroom", "agents", a));
73589
74051
  const now = (deps.now ?? Date.now)();
73590
- const socketPath = join35(resolveAgentDir(agent), "telegram", "gateway.sock");
74052
+ const socketPath = join36(resolveAgentDir(agent), "telegram", "gateway.sock");
73591
74053
  const inbound = {
73592
74054
  type: "inbound",
73593
74055
  chatId: ctx.chatId,
@@ -73659,7 +74121,7 @@ function evaluateDispatch(args, deps = {}) {
73659
74121
  const log = deps.log ?? ((s) => process.stderr.write(s));
73660
74122
  const now = (deps.now ?? Date.now)();
73661
74123
  const nowDate = deps.nowDate ?? (() => new Date(now));
73662
- const resolveAgentDir = deps.resolveAgentDir ?? ((a) => join35(homedir11(), ".switchroom", "agents", a));
74124
+ const resolveAgentDir = deps.resolveAgentDir ?? ((a) => join36(homedir12(), ".switchroom", "agents", a));
73663
74125
  const cooldownStore = deps.cooldownStore ?? createFileCooldownStore(resolveAgentDir);
73664
74126
  if (!DISPATCH_SOURCES.includes(args.source))
73665
74127
  return 0;
@@ -73737,10 +74199,10 @@ var CONSOLIDATION_COMPLETED_EVENT = "consolidation.completed";
73737
74199
  function recordWebhookEvent(rec, deps = {}) {
73738
74200
  const log = deps.log ?? ((s) => process.stderr.write(s));
73739
74201
  const now = rec.ts || (deps.now ?? Date.now)();
73740
- const resolveAgentDir = deps.resolveAgentDir ?? ((a) => join36(homedir12(), ".switchroom", "agents", a));
74202
+ const resolveAgentDir = deps.resolveAgentDir ?? ((a) => join37(homedir13(), ".switchroom", "agents", a));
73741
74203
  const dedupStore = deps.dedupStore ?? createFileDedupStore(resolveAgentDir);
73742
74204
  const agent = rec.agent;
73743
- const telegramDir = join36(resolveAgentDir(agent), "telegram");
74205
+ const telegramDir = join37(resolveAgentDir(agent), "telegram");
73744
74206
  if (rec.source === "github" && rec.delivery_id) {
73745
74207
  const originalTs = dedupStore.check(agent, rec.delivery_id, now);
73746
74208
  if (originalTs !== undefined) {
@@ -73749,7 +74211,7 @@ function recordWebhookEvent(rec, deps = {}) {
73749
74211
  return { status: "deduped", ts: originalTs };
73750
74212
  }
73751
74213
  }
73752
- const logPath = join36(telegramDir, "webhook-events.jsonl");
74214
+ const logPath = join37(telegramDir, "webhook-events.jsonl");
73753
74215
  try {
73754
74216
  mkdirSync26(telegramDir, { recursive: true });
73755
74217
  const record = {
@@ -77197,17 +77659,17 @@ import {
77197
77659
  readFileSync as readFileSync31,
77198
77660
  writeSync as writeSync5
77199
77661
  } from "node:fs";
77200
- import { join as join37 } from "node:path";
77662
+ import { join as join38 } from "node:path";
77201
77663
  import { randomUUID as randomUUID5 } from "node:crypto";
77202
77664
  var PROPOSALS_FILE2 = "skill-proposals.jsonl";
77203
77665
  var REJECTED_FILE2 = "skill-proposals-rejected.jsonl";
77204
77666
  var REJECTION_TTL_MS2 = 90 * 24 * 60 * 60 * 1000;
77205
77667
  var PROPOSAL_SIM_THRESHOLD = 0.5;
77206
77668
  function proposalsPath2(stateDir) {
77207
- return join37(stateDir, PROPOSALS_FILE2);
77669
+ return join38(stateDir, PROPOSALS_FILE2);
77208
77670
  }
77209
77671
  function rejectedPath2(stateDir) {
77210
- return join37(stateDir, REJECTED_FILE2);
77672
+ return join38(stateDir, REJECTED_FILE2);
77211
77673
  }
77212
77674
  function ensureDir3(stateDir) {
77213
77675
  if (!existsSync33(stateDir)) {
@@ -78702,17 +79164,17 @@ import {
78702
79164
  readdirSync as readdirSync6,
78703
79165
  readFileSync as readFileSync37
78704
79166
  } from "fs";
78705
- import { join as join39 } from "path";
79167
+ import { join as join40 } from "path";
78706
79168
 
78707
79169
  // session-tail.ts
78708
- function sanitizeCwdToProjectName(cwd) {
79170
+ function sanitizeCwdToProjectName2(cwd) {
78709
79171
  return cwd.replace(/[^a-zA-Z0-9]/g, "-");
78710
79172
  }
78711
- var MAX_JSONL_LINE_BYTES = 2 * 1024 * 1024;
78712
- var MAX_ERROR_TEXT_CHARS = 500;
78713
- function extractToolResultErrorText(content3) {
79173
+ var MAX_JSONL_LINE_BYTES2 = 2 * 1024 * 1024;
79174
+ var MAX_ERROR_TEXT_CHARS2 = 500;
79175
+ function extractToolResultErrorText2(content3) {
78714
79176
  if (typeof content3 === "string") {
78715
- return content3.slice(0, MAX_ERROR_TEXT_CHARS);
79177
+ return content3.slice(0, MAX_ERROR_TEXT_CHARS2);
78716
79178
  }
78717
79179
  if (Array.isArray(content3)) {
78718
79180
  const parts = [];
@@ -78725,11 +79187,11 @@ function extractToolResultErrorText(content3) {
78725
79187
  }
78726
79188
  }
78727
79189
  return parts.join(`
78728
- `).slice(0, MAX_ERROR_TEXT_CHARS);
79190
+ `).slice(0, MAX_ERROR_TEXT_CHARS2);
78729
79191
  }
78730
79192
  return "";
78731
79193
  }
78732
- function projectAssistantTextBlocks(content3, make) {
79194
+ function projectAssistantTextBlocks2(content3, make) {
78733
79195
  const out = new Map;
78734
79196
  let lastToolUseIdx = -1;
78735
79197
  content3.forEach((c, i) => {
@@ -78746,6 +79208,13 @@ function projectAssistantTextBlocks(content3, make) {
78746
79208
  });
78747
79209
  return out;
78748
79210
  }
79211
+ function sumUsageTokens2(usage) {
79212
+ if (usage == null || typeof usage !== "object")
79213
+ return 0;
79214
+ const u = usage;
79215
+ const n = (v) => typeof v === "number" && Number.isFinite(v) ? v : 0;
79216
+ return n(u.input_tokens) + n(u.output_tokens) + n(u.cache_creation_input_tokens);
79217
+ }
78749
79218
  function assistantLineCarriesAnswerSurface(content3) {
78750
79219
  if (!Array.isArray(content3))
78751
79220
  return false;
@@ -78805,7 +79274,7 @@ function projectSubagentLine(line, agentId, state4) {
78805
79274
  agentId,
78806
79275
  toolUseId: cc.tool_use_id ?? "",
78807
79276
  isError: isError2,
78808
- errorText: isError2 ? extractToolResultErrorText(cc.content) : undefined
79277
+ errorText: isError2 ? extractToolResultErrorText2(cc.content) : undefined
78809
79278
  });
78810
79279
  }
78811
79280
  }
@@ -78821,7 +79290,17 @@ function projectSubagentLine(line, agentId, state4) {
78821
79290
  if (typeof subModel === "string" && !isModelSentinel(subModel)) {
78822
79291
  events.push({ kind: "sub_agent_model", agentId, model: subModel });
78823
79292
  }
78824
- const textEvents = projectAssistantTextBlocks(content3, (text4, blockIndex, lastInMessage) => ({
79293
+ const subUsageTotal = sumUsageTokens2(message?.usage);
79294
+ if (subUsageTotal > 0) {
79295
+ const subMsgId = message?.id;
79296
+ events.push({
79297
+ kind: "sub_agent_usage",
79298
+ agentId,
79299
+ messageId: typeof subMsgId === "string" ? subMsgId : null,
79300
+ totalTokens: subUsageTotal
79301
+ });
79302
+ }
79303
+ const textEvents = projectAssistantTextBlocks2(content3, (text4, blockIndex, lastInMessage) => ({
78825
79304
  kind: "sub_agent_text",
78826
79305
  agentId,
78827
79306
  text: text4,
@@ -78945,7 +79424,7 @@ function sanitiseToolArg(name, raw) {
78945
79424
  case "NotebookEdit": {
78946
79425
  const fp = raw.file_path;
78947
79426
  if (typeof fp === "string" && fp.length > 0)
78948
- out = basename5(fp);
79427
+ out = basename6(fp);
78949
79428
  break;
78950
79429
  }
78951
79430
  case "Bash": {
@@ -78981,7 +79460,7 @@ function sanitiseToolArg(name, raw) {
78981
79460
  out = out.slice(0, SANITISE_MAX_LEN - 1) + "\u2026";
78982
79461
  return out;
78983
79462
  }
78984
- function basename5(p) {
79463
+ function basename6(p) {
78985
79464
  const idx = p.lastIndexOf("/");
78986
79465
  return idx === -1 ? p : p.slice(idx + 1);
78987
79466
  }
@@ -79185,10 +79664,10 @@ import {
79185
79664
  utimesSync,
79186
79665
  writeFileSync as writeFileSync29
79187
79666
  } from "node:fs";
79188
- import { join as join38 } from "node:path";
79667
+ import { join as join39 } from "node:path";
79189
79668
  var TURN_ACTIVE_MARKER_FILE = "turn-active.json";
79190
79669
  function touchTurnActiveMarker(stateDir) {
79191
- const path2 = join38(stateDir, TURN_ACTIVE_MARKER_FILE);
79670
+ const path2 = join39(stateDir, TURN_ACTIVE_MARKER_FILE);
79192
79671
  if (!existsSync34(path2))
79193
79672
  return;
79194
79673
  const now = new Date;
@@ -79355,6 +79834,7 @@ function readSubTail(entry, tail, now, onDescriptionUpdate, fs2, log, db2, paren
79355
79834
  },
79356
79835
  lastTool: entry.lastTool,
79357
79836
  toolCount: entry.toolCount,
79837
+ totalTokens: entry.totalTokens,
79358
79838
  model: entry.currentModel,
79359
79839
  skeleton: true
79360
79840
  });
@@ -79436,6 +79916,7 @@ function readSubTail(entry, tail, now, onDescriptionUpdate, fs2, log, db2, paren
79436
79916
  },
79437
79917
  lastTool: entry.lastTool,
79438
79918
  toolCount: entry.toolCount,
79919
+ totalTokens: entry.totalTokens,
79439
79920
  model: entry.currentModel
79440
79921
  });
79441
79922
  return true;
@@ -79546,6 +80027,15 @@ function readSubTail(entry, tail, now, onDescriptionUpdate, fs2, log, db2, paren
79546
80027
  }
79547
80028
  continue;
79548
80029
  }
80030
+ if (ev.kind === "sub_agent_usage") {
80031
+ if (ev.messageId == null) {
80032
+ entry.totalTokens += ev.totalTokens;
80033
+ } else if (!entry.seenUsageMessageIds.has(ev.messageId)) {
80034
+ entry.seenUsageMessageIds.add(ev.messageId);
80035
+ entry.totalTokens += ev.totalTokens;
80036
+ }
80037
+ continue;
80038
+ }
79549
80039
  if (ev.kind === "sub_agent_tool_use") {
79550
80040
  const narrativeJustFired = resolvePendingSubNarrative(ev.toolName, ev.input);
79551
80041
  if (REPLY_TOOLS2.has(ev.toolName) && typeof ev.input?.text === "string") {
@@ -79574,6 +80064,7 @@ function readSubTail(entry, tail, now, onDescriptionUpdate, fs2, log, db2, paren
79574
80064
  },
79575
80065
  lastTool: entry.lastTool,
79576
80066
  toolCount: entry.toolCount,
80067
+ totalTokens: entry.totalTokens,
79577
80068
  progressLine: toolLine,
79578
80069
  model: entry.currentModel
79579
80070
  });
@@ -79648,7 +80139,7 @@ function readSubTail(entry, tail, now, onDescriptionUpdate, fs2, log, db2, paren
79648
80139
  }
79649
80140
  function startSubagentWatcher(config) {
79650
80141
  const agentDir = config.agentDir;
79651
- const expectedProjectSlug = config.agentCwd != null ? sanitizeCwdToProjectName(config.agentCwd) : null;
80142
+ const expectedProjectSlug = config.agentCwd != null ? sanitizeCwdToProjectName2(config.agentCwd) : null;
79652
80143
  const extraWatchCwdsProvider = config.extraWatchCwdsProvider ?? null;
79653
80144
  const warnedForeignSlugs = new Set;
79654
80145
  const stallThresholdMs = config.stallThresholdMs ?? parseEnvMs("SWITCHROOM_SUBAGENT_STALL_MS") ?? DEFAULT_STALL_THRESHOLD_MS;
@@ -79713,6 +80204,8 @@ function startSubagentWatcher(config) {
79713
80204
  dispatchedAt: n,
79714
80205
  lastActivityAt: n,
79715
80206
  toolCount: 0,
80207
+ totalTokens: 0,
80208
+ seenUsageMessageIds: new Set,
79716
80209
  stallNotified: false,
79717
80210
  stalledAt: null,
79718
80211
  completionNotified: false,
@@ -79849,6 +80342,7 @@ function startSubagentWatcher(config) {
79849
80342
  state: entry.state,
79850
80343
  outcome: entry.errored ? "failed" : entry.historical ? "orphan" : "completed",
79851
80344
  toolCount: entry.toolCount,
80345
+ totalTokens: entry.totalTokens,
79852
80346
  durationMs: nowFn() - entry.dispatchedAt,
79853
80347
  description: entry.description,
79854
80348
  resultText: entry.errored ? entry.lastResultText || entry.errorDetail || "" : entry.lastResultText,
@@ -79869,6 +80363,7 @@ function startSubagentWatcher(config) {
79869
80363
  state: entry.state,
79870
80364
  outcome: "failed",
79871
80365
  toolCount: entry.toolCount,
80366
+ totalTokens: entry.totalTokens,
79872
80367
  durationMs: nowFn() - entry.dispatchedAt,
79873
80368
  description: entry.description,
79874
80369
  resultText: entry.lastResultText,
@@ -80125,8 +80620,8 @@ function startSubagentWatcher(config) {
80125
80620
  if (stopped)
80126
80621
  return;
80127
80622
  pruneVanishedDirWatchers();
80128
- const claudeHome = join39(agentDir, ".claude");
80129
- const projectsRoot = join39(claudeHome, "projects");
80623
+ const claudeHome = join40(agentDir, ".claude");
80624
+ const projectsRoot = join40(claudeHome, "projects");
80130
80625
  if (!fs2.existsSync(projectsRoot))
80131
80626
  return;
80132
80627
  let projectDirs;
@@ -80142,7 +80637,7 @@ function startSubagentWatcher(config) {
80142
80637
  if (extraWatchCwdsProvider != null) {
80143
80638
  try {
80144
80639
  for (const cwd of extraWatchCwdsProvider()) {
80145
- allowedSlugs.add(sanitizeCwdToProjectName(cwd));
80640
+ allowedSlugs.add(sanitizeCwdToProjectName2(cwd));
80146
80641
  }
80147
80642
  } catch (err) {
80148
80643
  providerOk = false;
@@ -80160,7 +80655,7 @@ function startSubagentWatcher(config) {
80160
80655
  continue;
80161
80656
  }
80162
80657
  warnedForeignSlugs.delete(pDir);
80163
- const projectPath = join39(projectsRoot, pDir);
80658
+ const projectPath = join40(projectsRoot, pDir);
80164
80659
  let sessionDirs;
80165
80660
  try {
80166
80661
  sessionDirs = fs2.readdirSync(projectPath);
@@ -80170,7 +80665,7 @@ function startSubagentWatcher(config) {
80170
80665
  for (const sDir of sessionDirs) {
80171
80666
  if (sDir.endsWith(".jsonl"))
80172
80667
  continue;
80173
- const subagentsPath = join39(projectPath, sDir, "subagents");
80668
+ const subagentsPath = join40(projectPath, sDir, "subagents");
80174
80669
  if (!fs2.existsSync(subagentsPath))
80175
80670
  continue;
80176
80671
  const watchAndScan = (dirPath) => {
@@ -80179,7 +80674,7 @@ function startSubagentWatcher(config) {
80179
80674
  const w = fs2.watch(dirPath, (_event, filename) => {
80180
80675
  if (!filename || !filename.toString().startsWith("agent-") || !filename.toString().endsWith(".jsonl"))
80181
80676
  return;
80182
- const filePath = join39(dirPath, filename.toString());
80677
+ const filePath = join40(dirPath, filename.toString());
80183
80678
  if (!knownFiles.has(filePath)) {
80184
80679
  scanSubagentsDir(dirPath);
80185
80680
  }
@@ -80193,7 +80688,7 @@ function startSubagentWatcher(config) {
80193
80688
  scanSubagentsDir(dirPath);
80194
80689
  };
80195
80690
  watchAndScan(subagentsPath);
80196
- const workflowsPath = join39(subagentsPath, "workflows");
80691
+ const workflowsPath = join40(subagentsPath, "workflows");
80197
80692
  if (fs2.existsSync(workflowsPath)) {
80198
80693
  let wfDirs;
80199
80694
  try {
@@ -80203,7 +80698,7 @@ function startSubagentWatcher(config) {
80203
80698
  }
80204
80699
  for (const wfDir of wfDirs) {
80205
80700
  try {
80206
- const wfPath = join39(workflowsPath, wfDir);
80701
+ const wfPath = join40(workflowsPath, wfDir);
80207
80702
  if (!fs2.statSync(wfPath).isDirectory())
80208
80703
  continue;
80209
80704
  watchAndScan(wfPath);
@@ -80223,7 +80718,7 @@ function startSubagentWatcher(config) {
80223
80718
  for (const e of entries) {
80224
80719
  if (!e.startsWith("agent-") || !e.endsWith(".jsonl"))
80225
80720
  continue;
80226
- const filePath = join39(subagentsPath, e);
80721
+ const filePath = join40(subagentsPath, e);
80227
80722
  if (knownFiles.has(filePath))
80228
80723
  continue;
80229
80724
  const agentId = e.slice("agent-".length, -".jsonl".length);
@@ -80335,13 +80830,13 @@ import {
80335
80830
  existsSync as existsSync36,
80336
80831
  renameSync as renameSync15
80337
80832
  } from "node:fs";
80338
- import { join as join40, resolve as resolve8 } from "node:path";
80339
- import { homedir as homedir13 } from "node:os";
80833
+ import { join as join41, resolve as resolve8 } from "node:path";
80834
+ import { homedir as homedir14 } from "node:os";
80340
80835
  function registryDir() {
80341
- return resolve8(process.env.SWITCHROOM_WORKTREE_DIR ?? join40(homedir13(), ".switchroom", "worktrees"));
80836
+ return resolve8(process.env.SWITCHROOM_WORKTREE_DIR ?? join41(homedir14(), ".switchroom", "worktrees"));
80342
80837
  }
80343
80838
  function recordPath(id) {
80344
- return join40(registryDir(), `${id}.json`);
80839
+ return join41(registryDir(), `${id}.json`);
80345
80840
  }
80346
80841
  function ensureDir4() {
80347
80842
  mkdirSync29(registryDir(), { recursive: true });
@@ -80392,12 +80887,12 @@ function recordExists(id) {
80392
80887
 
80393
80888
  // worktree-watch-cwds.ts
80394
80889
  import { realpathSync as realpathSync3 } from "node:fs";
80395
- import { basename as basename7 } from "node:path";
80890
+ import { basename as basename8 } from "node:path";
80396
80891
  var identityEscalated = false;
80397
80892
  function defaultDeriveName(agentDir) {
80398
80893
  if (!agentDir || agentDir.trim().length === 0)
80399
80894
  return "";
80400
- const leaf = basename7(agentDir).trim();
80895
+ const leaf = basename8(agentDir).trim();
80401
80896
  return leaf;
80402
80897
  }
80403
80898
  function resolveOwnerIdentity(self, agentDir, deriveName) {
@@ -80520,14 +81015,14 @@ init_boot_card();
80520
81015
 
80521
81016
  // gateway/update-announce.ts
80522
81017
  import { existsSync as existsSync41, mkdirSync as mkdirSync33, openSync as openSync9, closeSync as closeSync9, readFileSync as readFileSync44 } from "node:fs";
80523
- import { join as join45 } from "node:path";
80524
- import { homedir as homedir15 } from "node:os";
81018
+ import { join as join46 } from "node:path";
81019
+ import { homedir as homedir16 } from "node:os";
80525
81020
 
80526
81021
  // ../src/host-control/audit-reader.ts
80527
- import { homedir as homedir14 } from "node:os";
80528
- import { join as join44 } from "node:path";
80529
- function defaultAuditLogPath(home2 = homedir14()) {
80530
- return join44(home2, ".switchroom", "host-control-audit.log");
81022
+ import { homedir as homedir15 } from "node:os";
81023
+ import { join as join45 } from "node:path";
81024
+ function defaultAuditLogPath(home2 = homedir15()) {
81025
+ return join45(home2, ".switchroom", "host-control-audit.log");
80531
81026
  }
80532
81027
  function parseAuditLine(line) {
80533
81028
  const trimmed = line.trim();
@@ -80712,15 +81207,15 @@ function renderUpdateOutcomeLine(entry) {
80712
81207
  `);
80713
81208
  }
80714
81209
  function claimUpdateAnnouncement(requestId, opts = {}) {
80715
- const stateDir = opts.stateDir ?? process.env.TELEGRAM_STATE_DIR ?? join45(homedir15(), ".switchroom");
80716
- const dir = join45(stateDir, "update-announced");
81210
+ const stateDir = opts.stateDir ?? process.env.TELEGRAM_STATE_DIR ?? join46(homedir16(), ".switchroom");
81211
+ const dir = join46(stateDir, "update-announced");
80717
81212
  try {
80718
81213
  mkdirSync33(dir, { recursive: true });
80719
81214
  } catch {
80720
81215
  return false;
80721
81216
  }
80722
81217
  const safeId = requestId.replace(/[^A-Za-z0-9_.-]/g, "_").slice(0, 200);
80723
- const path2 = join45(dir, safeId);
81218
+ const path2 = join46(dir, safeId);
80724
81219
  try {
80725
81220
  const fd = openSync9(path2, "wx");
80726
81221
  closeSync9(fd);
@@ -80942,7 +81437,7 @@ function createIssuesCardHandle(opts) {
80942
81437
 
80943
81438
  // issues-watcher.ts
80944
81439
  import { existsSync as existsSync43, statSync as statSync12 } from "node:fs";
80945
- import { join as join47 } from "node:path";
81440
+ import { join as join48 } from "node:path";
80946
81441
 
80947
81442
  // ../src/issues/store.ts
80948
81443
  import {
@@ -80958,7 +81453,7 @@ import {
80958
81453
  writeFileSync as writeFileSync36,
80959
81454
  writeSync as writeSync6
80960
81455
  } from "node:fs";
80961
- import { join as join46 } from "node:path";
81456
+ import { join as join47 } from "node:path";
80962
81457
  import { randomBytes as randomBytes7 } from "node:crypto";
80963
81458
  import { execSync } from "node:child_process";
80964
81459
 
@@ -80977,7 +81472,7 @@ init_redact();
80977
81472
  var ISSUES_FILE = "issues.jsonl";
80978
81473
  var ISSUES_LOCK = "issues.lock";
80979
81474
  function readAll(stateDir) {
80980
- const path2 = join46(stateDir, ISSUES_FILE);
81475
+ const path2 = join47(stateDir, ISSUES_FILE);
80981
81476
  if (!existsSync42(path2))
80982
81477
  return [];
80983
81478
  let raw;
@@ -81014,7 +81509,7 @@ function list2(stateDir, opts = {}) {
81014
81509
  });
81015
81510
  }
81016
81511
  function resolve9(stateDir, fingerprint, nowFn = Date.now) {
81017
- if (!existsSync42(join46(stateDir, ISSUES_FILE)))
81512
+ if (!existsSync42(join47(stateDir, ISSUES_FILE)))
81018
81513
  return 0;
81019
81514
  return withLock(stateDir, () => {
81020
81515
  const all2 = readAll(stateDir);
@@ -81032,7 +81527,7 @@ function resolve9(stateDir, fingerprint, nowFn = Date.now) {
81032
81527
  });
81033
81528
  }
81034
81529
  function writeAll(stateDir, events) {
81035
- const path2 = join46(stateDir, ISSUES_FILE);
81530
+ const path2 = join47(stateDir, ISSUES_FILE);
81036
81531
  sweepOrphanTmpFiles(stateDir);
81037
81532
  const tmp = `${path2}.tmp-${process.pid}-${randomBytes7(4).toString("hex")}`;
81038
81533
  const body = events.length === 0 ? "" : events.map((e) => JSON.stringify(e)).join(`
@@ -81054,7 +81549,7 @@ function sweepOrphanTmpFiles(stateDir) {
81054
81549
  for (const entry of entries) {
81055
81550
  if (!entry.startsWith(TMP_PREFIX))
81056
81551
  continue;
81057
- const tmpPath2 = join46(stateDir, entry);
81552
+ const tmpPath2 = join47(stateDir, entry);
81058
81553
  try {
81059
81554
  const stat = statSync11(tmpPath2);
81060
81555
  if (stat.mtimeMs < cutoff) {
@@ -81066,7 +81561,7 @@ function sweepOrphanTmpFiles(stateDir) {
81066
81561
  var LOCK_RETRY_MS = 25;
81067
81562
  var LOCK_TIMEOUT_MS = 1e4;
81068
81563
  function withLock(stateDir, fn) {
81069
- const lockPath = join46(stateDir, ISSUES_LOCK);
81564
+ const lockPath = join47(stateDir, ISSUES_LOCK);
81070
81565
  const startedAt = Date.now();
81071
81566
  let fd = null;
81072
81567
  while (fd === null) {
@@ -81151,7 +81646,7 @@ function isIssueEvent(v) {
81151
81646
  // issues-watcher.ts
81152
81647
  var DEFAULT_POLL_INTERVAL_MS2 = 2000;
81153
81648
  function startIssuesWatcher(opts) {
81154
- const path2 = join47(opts.stateDir, ISSUES_FILE);
81649
+ const path2 = join48(opts.stateDir, ISSUES_FILE);
81155
81650
  const log = opts.log ?? (() => {});
81156
81651
  const intervalMs = opts.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS2;
81157
81652
  const setIntervalFn = opts.setInterval ?? setInterval;
@@ -81213,10 +81708,10 @@ function defaultReadEvents(stateDir) {
81213
81708
  }
81214
81709
  // permission-title.ts
81215
81710
  init_card_format();
81216
- import { basename as basename9 } from "node:path";
81711
+ import { basename as basename10 } from "node:path";
81217
81712
 
81218
81713
  // permission-rule.ts
81219
- import { basename as basename8 } from "node:path";
81714
+ import { basename as basename9 } from "node:path";
81220
81715
  var FILE_TOOLS = new Set([
81221
81716
  "Edit",
81222
81717
  "Write",
@@ -81294,7 +81789,7 @@ function skillBasenameFromPath(input) {
81294
81789
  if (!path2)
81295
81790
  return null;
81296
81791
  const trimmed = path2.replace(/\/SKILL\.md$/i, "").replace(/\/$/, "");
81297
- return basename8(trimmed) || null;
81792
+ return basename9(trimmed) || null;
81298
81793
  }
81299
81794
  function matchesAllowRule(rule, toolName, inputPreview) {
81300
81795
  if (!rule || !toolName)
@@ -81586,11 +82081,11 @@ function describeGrant(toolName, inputPreview, option) {
81586
82081
  return m ? `run ${m[1]} commands` : "run that command";
81587
82082
  }
81588
82083
  if (t === "Edit" || t === "MultiEdit" || t === "NotebookEdit")
81589
- return `edit ${basename9(arg)}`;
82084
+ return `edit ${basename10(arg)}`;
81590
82085
  if (t === "Write")
81591
- return `write ${basename9(arg)}`;
82086
+ return `write ${basename10(arg)}`;
81592
82087
  if (t === "Read")
81593
- return `read ${basename9(arg)}`;
82088
+ return `read ${basename10(arg)}`;
81594
82089
  return naturalAction(toolName, inputPreview);
81595
82090
  }
81596
82091
  switch (rule) {
@@ -81629,12 +82124,12 @@ function fileBase(input, rawPreview) {
81629
82124
  if (input) {
81630
82125
  const p = readString2(input, "file_path") ?? readString2(input, "notebook_path");
81631
82126
  if (p)
81632
- return basename9(p);
82127
+ return basename10(p);
81633
82128
  }
81634
82129
  if (rawPreview) {
81635
82130
  const p = extractFilePathFromRaw2(rawPreview);
81636
82131
  if (p)
81637
- return basename9(p);
82132
+ return basename10(p);
81638
82133
  }
81639
82134
  return null;
81640
82135
  }
@@ -81765,7 +82260,7 @@ function truncate6(text4, max) {
81765
82260
  }
81766
82261
 
81767
82262
  // permission-rule.ts
81768
- import { basename as basename10 } from "node:path";
82263
+ import { basename as basename11 } from "node:path";
81769
82264
  var FILE_TOOLS2 = new Set([
81770
82265
  "Edit",
81771
82266
  "Write",
@@ -81897,14 +82392,14 @@ function skillBasenameFromPath3(input) {
81897
82392
  if (!path2)
81898
82393
  return null;
81899
82394
  const trimmed = path2.replace(/\/SKILL\.md$/i, "").replace(/\/$/, "");
81900
- return basename10(trimmed) || null;
82395
+ return basename11(trimmed) || null;
81901
82396
  }
81902
82397
  function isRulePersisted(resolvedAllow, ruleRule) {
81903
82398
  return resolvedAllow.includes(ruleRule);
81904
82399
  }
81905
82400
 
81906
82401
  // scoped-approval.ts
81907
- import { basename as basename11 } from "node:path";
82402
+ import { basename as basename12 } from "node:path";
81908
82403
  var SCOPED_APPROVAL_DEFAULT_TTL_MS = 30 * 60 * 1000;
81909
82404
  function scopedApprovalTtlMs(env = process.env) {
81910
82405
  const raw = env.SWITCHROOM_SCOPED_APPROVAL_TTL_MS;
@@ -81929,7 +82424,7 @@ function resolveTimeBox(toolName, inputPreview, choices) {
81929
82424
  const fileMatch = FILE_RULE.exec(rule);
81930
82425
  if (fileMatch) {
81931
82426
  const verb = fileMatch[1] === "Read" ? "reads of" : "edits to";
81932
- return { rule, breadth: `${verb} ${basename11(fileMatch[2])}` };
82427
+ return { rule, breadth: `${verb} ${basename12(fileMatch[2])}` };
81933
82428
  }
81934
82429
  const bashMatch = BASH_FAMILY_RULE.exec(rule);
81935
82430
  if (bashMatch) {
@@ -82030,7 +82525,7 @@ function readBashCommand(inputPreview) {
82030
82525
 
82031
82526
  // gateway/scoped-grant-store.ts
82032
82527
  import { readFileSync as readFileSync47, writeFileSync as writeFileSync37 } from "node:fs";
82033
- import { join as join48 } from "node:path";
82528
+ import { join as join49 } from "node:path";
82034
82529
 
82035
82530
  // scoped-approval.ts
82036
82531
  var SCOPED_APPROVAL_DEFAULT_TTL_MS2 = 30 * 60 * 1000;
@@ -82072,7 +82567,7 @@ function scopedGrantPersistEnabled(env = process.env) {
82072
82567
  return env.SWITCHROOM_SCOPED_GRANT_PERSIST !== "0";
82073
82568
  }
82074
82569
  function createScopedGrantStore(stateDir, env = process.env) {
82075
- const filePath = join48(stateDir, "scoped-grants.json");
82570
+ const filePath = join49(stateDir, "scoped-grants.json");
82076
82571
  const enabled8 = scopedGrantPersistEnabled(env);
82077
82572
  function read() {
82078
82573
  try {
@@ -82446,7 +82941,7 @@ function isDiffPreApproved(agentName3, unifiedDiff, deps) {
82446
82941
  // credits-watch.ts
82447
82942
  init_card_format();
82448
82943
  import { readFileSync as readFileSync48, writeFileSync as writeFileSync38, existsSync as existsSync44, mkdirSync as mkdirSync35 } from "fs";
82449
- import { join as join49 } from "path";
82944
+ import { join as join50 } from "path";
82450
82945
  var STATE_FILE = "credits-watch.json";
82451
82946
  var DEFAULT_CREDIT_FATAL_REASONS = new Set;
82452
82947
  var KNOWN_CREDIT_REASONS = [
@@ -82468,7 +82963,7 @@ function emptyCreditState() {
82468
82963
  return { lastNotifiedReason: null, lastNotifiedAt: 0 };
82469
82964
  }
82470
82965
  function readClaudeJsonOverage(claudeConfigDir) {
82471
- const path2 = join49(claudeConfigDir, ".claude.json");
82966
+ const path2 = join50(claudeConfigDir, ".claude.json");
82472
82967
  if (!existsSync44(path2))
82473
82968
  return null;
82474
82969
  let raw;
@@ -82551,7 +83046,7 @@ function humanizeReason(reason) {
82551
83046
  }
82552
83047
  }
82553
83048
  function loadCreditState(stateDir) {
82554
- const path2 = join49(stateDir, STATE_FILE);
83049
+ const path2 = join50(stateDir, STATE_FILE);
82555
83050
  if (!existsSync44(path2))
82556
83051
  return emptyCreditState();
82557
83052
  try {
@@ -82568,7 +83063,7 @@ function loadCreditState(stateDir) {
82568
83063
  }
82569
83064
  function saveCreditState(stateDir, state4) {
82570
83065
  mkdirSync35(stateDir, { recursive: true });
82571
- const path2 = join49(stateDir, STATE_FILE);
83066
+ const path2 = join50(stateDir, STATE_FILE);
82572
83067
  writeFileSync38(path2, JSON.stringify(state4, null, 2) + `
82573
83068
  `, { mode: 384 });
82574
83069
  }
@@ -82577,7 +83072,7 @@ function saveCreditState(stateDir, state4) {
82577
83072
  init_auth_snapshot_format();
82578
83073
  init_card_format();
82579
83074
  import { readFileSync as readFileSync49, writeFileSync as writeFileSync39, existsSync as existsSync45, mkdirSync as mkdirSync36 } from "fs";
82580
- import { join as join50 } from "path";
83075
+ import { join as join51 } from "path";
82581
83076
  var STATE_FILE2 = "quota-watch.json";
82582
83077
  function emptyQuotaWatchState() {
82583
83078
  return {};
@@ -82815,7 +83310,7 @@ function buildRecoveryMessage(agentName3, snap) {
82815
83310
  `);
82816
83311
  }
82817
83312
  function loadQuotaWatchState(stateDir) {
82818
- const path2 = join50(stateDir, STATE_FILE2);
83313
+ const path2 = join51(stateDir, STATE_FILE2);
82819
83314
  if (!existsSync45(path2))
82820
83315
  return emptyQuotaWatchState();
82821
83316
  try {
@@ -82837,7 +83332,7 @@ function loadQuotaWatchState(stateDir) {
82837
83332
  }
82838
83333
  function saveQuotaWatchState(stateDir, state4) {
82839
83334
  mkdirSync36(stateDir, { recursive: true });
82840
- const path2 = join50(stateDir, STATE_FILE2);
83335
+ const path2 = join51(stateDir, STATE_FILE2);
82841
83336
  writeFileSync39(path2, JSON.stringify(state4, null, 2) + `
82842
83337
  `, { mode: 384 });
82843
83338
  }
@@ -82895,17 +83390,17 @@ import {
82895
83390
  utimesSync as utimesSync2,
82896
83391
  writeFileSync as writeFileSync40
82897
83392
  } from "node:fs";
82898
- import { join as join51 } from "node:path";
83393
+ import { join as join52 } from "node:path";
82899
83394
  var TURN_ACTIVE_MARKER_FILE2 = "turn-active.json";
82900
83395
  function writeTurnActiveMarker(stateDir, marker) {
82901
83396
  try {
82902
83397
  mkdirSync37(stateDir, { recursive: true });
82903
- writeFileSync40(join51(stateDir, TURN_ACTIVE_MARKER_FILE2), JSON.stringify(marker, null, 2) + `
83398
+ writeFileSync40(join52(stateDir, TURN_ACTIVE_MARKER_FILE2), JSON.stringify(marker, null, 2) + `
82904
83399
  `, { mode: 384 });
82905
83400
  } catch {}
82906
83401
  }
82907
83402
  function touchTurnActiveMarker2(stateDir) {
82908
- const path2 = join51(stateDir, TURN_ACTIVE_MARKER_FILE2);
83403
+ const path2 = join52(stateDir, TURN_ACTIVE_MARKER_FILE2);
82909
83404
  if (!existsSync46(path2))
82910
83405
  return;
82911
83406
  const now = new Date;
@@ -82920,11 +83415,11 @@ function touchTurnActiveMarker2(stateDir) {
82920
83415
  }
82921
83416
  function removeTurnActiveMarker(stateDir) {
82922
83417
  try {
82923
- unlinkSync21(join51(stateDir, TURN_ACTIVE_MARKER_FILE2));
83418
+ unlinkSync21(join52(stateDir, TURN_ACTIVE_MARKER_FILE2));
82924
83419
  } catch {}
82925
83420
  }
82926
83421
  function sweepStaleTurnActiveMarker(stateDir, opts) {
82927
- const path2 = join51(stateDir, TURN_ACTIVE_MARKER_FILE2);
83422
+ const path2 = join52(stateDir, TURN_ACTIVE_MARKER_FILE2);
82928
83423
  if (!existsSync46(path2))
82929
83424
  return false;
82930
83425
  const now = opts.now ?? Date.now();
@@ -82955,7 +83450,7 @@ function sweepStaleTurnActiveMarker(stateDir, opts) {
82955
83450
  }
82956
83451
  }
82957
83452
  function readTurnActiveMarkerAgeMs(stateDir, now) {
82958
- const path2 = join51(stateDir, TURN_ACTIVE_MARKER_FILE2);
83453
+ const path2 = join52(stateDir, TURN_ACTIVE_MARKER_FILE2);
82959
83454
  try {
82960
83455
  const st = statSync13(path2);
82961
83456
  return (now ?? Date.now()) - st.mtimeMs;
@@ -82965,10 +83460,10 @@ function readTurnActiveMarkerAgeMs(stateDir, now) {
82965
83460
  }
82966
83461
 
82967
83462
  // ../src/build-info.ts
82968
- var VERSION = "0.18.24";
82969
- var COMMIT_SHA = "f641363e";
82970
- var COMMIT_DATE = "2026-07-14T15:55:33+10:00";
82971
- var LATEST_PR = 3240;
83463
+ var VERSION = "0.18.25";
83464
+ var COMMIT_SHA = "01803cff";
83465
+ var COMMIT_DATE = "2026-07-15T16:26:26+10:00";
83466
+ var LATEST_PR = 3254;
82972
83467
  var COMMITS_AHEAD_OF_TAG = 0;
82973
83468
 
82974
83469
  // gateway/boot-version.ts
@@ -83045,12 +83540,12 @@ init_protocol();
83045
83540
  init_peercred();
83046
83541
  import * as net5 from "node:net";
83047
83542
  import * as fs2 from "node:fs";
83048
- import { homedir as homedir16 } from "node:os";
83049
- import { join as join52 } from "node:path";
83543
+ import { homedir as homedir17 } from "node:os";
83544
+ import { join as join53 } from "node:path";
83050
83545
  var DEFAULT_TIMEOUT_MS4 = 2000;
83051
83546
  var UNLOCK_TIMEOUT_MS = 30000;
83052
- var LEGACY_SOCKET_PATH2 = join52(homedir16(), ".switchroom", "vault-broker.sock");
83053
- var OPERATOR_SOCKET_PATH2 = join52(homedir16(), ".switchroom", "broker-operator", "sock");
83547
+ var LEGACY_SOCKET_PATH2 = join53(homedir17(), ".switchroom", "vault-broker.sock");
83548
+ var OPERATOR_SOCKET_PATH2 = join53(homedir17(), ".switchroom", "broker-operator", "sock");
83054
83549
  function defaultBrokerSocketPath2() {
83055
83550
  if (fs2.existsSync(OPERATOR_SOCKET_PATH2))
83056
83551
  return OPERATOR_SOCKET_PATH2;
@@ -83933,7 +84428,7 @@ function resolveVaultApprovalPosture(broker) {
83933
84428
 
83934
84429
  // registry/turns-schema.ts
83935
84430
  import { chmodSync as chmodSync10, mkdirSync as mkdirSync38 } from "fs";
83936
- import { join as join53 } from "path";
84431
+ import { join as join54 } from "path";
83937
84432
  var DatabaseClass2 = null;
83938
84433
  function loadDatabaseClass2() {
83939
84434
  if (DatabaseClass2 != null)
@@ -83984,12 +84479,16 @@ var PHASE2_MIGRATIONS = [
83984
84479
  var PHASE3_MIGRATIONS = [
83985
84480
  `ALTER TABLE turns ADD COLUMN resumed_at INTEGER`
83986
84481
  ];
84482
+ var PHASE4_MIGRATIONS = [
84483
+ `ALTER TABLE turns ADD COLUMN session_id TEXT`,
84484
+ `ALTER TABLE turns ADD COLUMN answer_redelivered_at INTEGER`
84485
+ ];
83987
84486
  function applySchema(db2) {
83988
84487
  db2.exec("PRAGMA journal_mode = WAL");
83989
84488
  db2.exec("PRAGMA synchronous = NORMAL");
83990
84489
  db2.exec("PRAGMA busy_timeout = 5000");
83991
84490
  db2.exec(SCHEMA_SQL);
83992
- for (const sql of [...PHASE1_MIGRATIONS, ...PHASE2_MIGRATIONS, ...PHASE3_MIGRATIONS]) {
84491
+ for (const sql of [...PHASE1_MIGRATIONS, ...PHASE2_MIGRATIONS, ...PHASE3_MIGRATIONS, ...PHASE4_MIGRATIONS]) {
83993
84492
  try {
83994
84493
  db2.exec(sql);
83995
84494
  } catch (err) {
@@ -84001,9 +84500,9 @@ function applySchema(db2) {
84001
84500
  }
84002
84501
  function openTurnsDb(agentDir) {
84003
84502
  const Database = loadDatabaseClass2();
84004
- const dir = join53(agentDir, "telegram");
84503
+ const dir = join54(agentDir, "telegram");
84005
84504
  mkdirSync38(dir, { recursive: true, mode: 448 });
84006
- const path2 = join53(dir, "registry.db");
84505
+ const path2 = join54(dir, "registry.db");
84007
84506
  const db2 = new Database(path2, { create: true });
84008
84507
  applySchema(db2);
84009
84508
  try {
@@ -84032,6 +84531,8 @@ function mapRow(row) {
84032
84531
  tool_call_count: row.tool_call_count,
84033
84532
  interrupt_reason: row.interrupt_reason,
84034
84533
  resumed_at: row.resumed_at,
84534
+ session_id: row.session_id ?? null,
84535
+ answer_redelivered_at: row.answer_redelivered_at ?? null,
84035
84536
  created_at: row.created_at,
84036
84537
  updated_at: row.updated_at
84037
84538
  };
@@ -84127,6 +84628,24 @@ function markTurnResumed(db2, turnKey2, now = Date.now()) {
84127
84628
  WHERE turn_key = ? AND resumed_at IS NULL
84128
84629
  `).run(now, now, turnKey2);
84129
84630
  }
84631
+ function stampTurnSessionId(db2, turnKey2, sessionId, now = Date.now()) {
84632
+ if (!sessionId)
84633
+ return;
84634
+ db2.prepare(`
84635
+ UPDATE turns
84636
+ SET session_id = ?,
84637
+ updated_at = ?
84638
+ WHERE turn_key = ? AND session_id IS NULL
84639
+ `).run(sessionId, now, turnKey2);
84640
+ }
84641
+ function markAnswerRedelivered(db2, turnKey2, now = Date.now()) {
84642
+ db2.prepare(`
84643
+ UPDATE turns
84644
+ SET answer_redelivered_at = ?,
84645
+ updated_at = ?
84646
+ WHERE turn_key = ? AND answer_redelivered_at IS NULL
84647
+ `).run(now, now, turnKey2);
84648
+ }
84130
84649
  function findLatestTurnIfInterrupted(db2) {
84131
84650
  const row = db2.prepare(`
84132
84651
  SELECT * FROM turns
@@ -84756,7 +85275,7 @@ installGlobalErrorHandlers();
84756
85275
  process.on("beforeExit", () => {
84757
85276
  shutdownAnalytics();
84758
85277
  });
84759
- var STATE_DIR = process.env.TELEGRAM_STATE_DIR ?? join54(homedir17(), ".claude", "channels", "telegram");
85278
+ var STATE_DIR = process.env.TELEGRAM_STATE_DIR ?? join55(homedir18(), ".claude", "channels", "telegram");
84760
85279
  var permCardStore = createPermissionCardStore(STATE_DIR);
84761
85280
  var BLOCKED_APPROVALS_DIR = process.env.SWITCHROOM_BLOCKED_APPROVALS_DIR ?? "/state/blocked-approvals";
84762
85281
  var AGENT_NAME = process.env.SWITCHROOM_AGENT_NAME ?? "agent";
@@ -84856,11 +85375,11 @@ function scheduleAlwaysAllowPersistDrain() {
84856
85375
  }, ALWAYS_ALLOW_DRAIN_INTERVAL_MS);
84857
85376
  timer3.unref?.();
84858
85377
  }
84859
- var ACCESS_FILE = join54(STATE_DIR, "access.json");
84860
- var APPROVED_DIR = join54(STATE_DIR, "approved");
84861
- var ENV_FILE = join54(STATE_DIR, ".env");
84862
- var INBOX_DIR = join54(STATE_DIR, "inbox");
84863
- var PEOPLE_FILE = join54(STATE_DIR, "people.json");
85378
+ var ACCESS_FILE = join55(STATE_DIR, "access.json");
85379
+ var APPROVED_DIR = join55(STATE_DIR, "approved");
85380
+ var ENV_FILE = join55(STATE_DIR, ".env");
85381
+ var INBOX_DIR = join55(STATE_DIR, "inbox");
85382
+ var PEOPLE_FILE = join55(STATE_DIR, "people.json");
84864
85383
  function triggerSelfRestart(targetAgent, reason, delayMs = 300) {
84865
85384
  const isDocker = process.env.SWITCHROOM_RUNTIME === "docker";
84866
85385
  const selfAgent = process.env.SWITCHROOM_AGENT_NAME;
@@ -85057,7 +85576,7 @@ function assertSendable(f) {
85057
85576
  } catch {
85058
85577
  throw new Error(`refusing to send file \u2014 cannot resolve real path: ${f}`);
85059
85578
  }
85060
- const inbox = join54(stateReal, "inbox");
85579
+ const inbox = join55(stateReal, "inbox");
85061
85580
  if (real.startsWith(stateReal + sep3) && !real.startsWith(inbox + sep3)) {
85062
85581
  throw new Error(`refusing to send channel state: ${f}`);
85063
85582
  }
@@ -85182,7 +85701,7 @@ var HISTORY_ENABLED = HISTORY_ACCESS.historyEnabled !== false;
85182
85701
  if (HISTORY_ENABLED) {
85183
85702
  try {
85184
85703
  initHistory(STATE_DIR, HISTORY_ACCESS.historyRetentionDays ?? 30);
85185
- process.stderr.write(`telegram gateway: history capture enabled at ${join54(STATE_DIR, "history.db")}
85704
+ process.stderr.write(`telegram gateway: history capture enabled at ${join55(STATE_DIR, "history.db")}
85186
85705
  `);
85187
85706
  } catch (err) {
85188
85707
  process.stderr.write(`telegram gateway: history init failed (${err.message}) \u2014 capture disabled
@@ -85191,6 +85710,7 @@ if (HISTORY_ENABLED) {
85191
85710
  }
85192
85711
  var turnsDb = null;
85193
85712
  var bootResumeInbound = null;
85713
+ var pendingRedelivery = null;
85194
85714
  var bridgeDeadPriorStreak = 0;
85195
85715
  try {
85196
85716
  const agentDir = STATE_DIR.endsWith("/telegram") ? STATE_DIR.slice(0, -"/telegram".length) : STATE_DIR;
@@ -85199,7 +85719,7 @@ try {
85199
85719
  let markerTurnKey = null;
85200
85720
  let markerAgeMs = null;
85201
85721
  try {
85202
- const markerPath = join54(STATE_DIR, TURN_ACTIVE_MARKER_FILE2);
85722
+ const markerPath = join55(STATE_DIR, TURN_ACTIVE_MARKER_FILE2);
85203
85723
  if (existsSync50(markerPath)) {
85204
85724
  const st = statSync16(markerPath);
85205
85725
  markerAgeMs = Date.now() - st.mtimeMs;
@@ -85224,10 +85744,10 @@ try {
85224
85744
  process.stderr.write(`telegram gateway: turn-registry boot-reaper stamped ${reaped} orphaned turn(s)${timeoutTurnKey ? ` (turnKey=${timeoutTurnKey} as 'timeout', markerAgeMs=${markerAgeMs})` : " as 'restart'"}
85225
85745
  `);
85226
85746
  } else {
85227
- process.stderr.write(`telegram gateway: turn-registry initialized at ${join54(agentDir, "telegram", "registry.db")}
85747
+ process.stderr.write(`telegram gateway: turn-registry initialized at ${join55(agentDir, "telegram", "registry.db")}
85228
85748
  `);
85229
85749
  }
85230
- const bridgeDeadMarker = consumeBridgeDeadEscalationMarker(join54(STATE_DIR, "bridge-dead-escalation.json"));
85750
+ const bridgeDeadMarker = consumeBridgeDeadEscalationMarker(join55(STATE_DIR, "bridge-dead-escalation.json"));
85231
85751
  if (bridgeDeadMarker != null) {
85232
85752
  bridgeDeadPriorStreak = bridgeDeadMarker.count ?? 1;
85233
85753
  process.stderr.write(`telegram gateway: boot: prior restart was a bridge-dead escalation (reason=${bridgeDeadMarker.reason}, consecutive=${bridgeDeadPriorStreak}${bridgeDeadMarker.crashTail ? `, crashTail=${bridgeDeadMarker.crashTail}` : ""})
@@ -85240,7 +85760,7 @@ try {
85240
85760
  const pending2 = findLatestTurnIfInterrupted(turnsDb);
85241
85761
  const selfAgent = process.env.SWITCHROOM_AGENT_NAME ?? "";
85242
85762
  if (pending2 != null && selfAgent) {
85243
- const bootResumeMarkerPath = process.env.SWITCHROOM_GATEWAY_CLEAN_SHUTDOWN_MARKER ?? join54(STATE_DIR, "clean-shutdown.json");
85763
+ const bootResumeMarkerPath = process.env.SWITCHROOM_GATEWAY_CLEAN_SHUTDOWN_MARKER ?? join55(STATE_DIR, "clean-shutdown.json");
85244
85764
  const bootResumeCleanMarker = readCleanShutdownMarker(bootResumeMarkerPath);
85245
85765
  const bootResumeForceAlways = process.env.SWITCHROOM_BOOT_RESUME_ALWAYS === "1";
85246
85766
  const bootResumeMode = parseBootResumeMode(process.env.SWITCHROOM_BOOT_RESUME);
@@ -85258,6 +85778,19 @@ try {
85258
85778
  ageMs: Math.max(0, Date.now() - pending2.started_at),
85259
85779
  maxAgeMs: RESUME_MAX_AGE_MS
85260
85780
  });
85781
+ const redeliverCapture = decideRedeliverCapture({
85782
+ willBeResumed: bootResumeKind === "resume",
85783
+ hasSessionId: Boolean(pending2.session_id)
85784
+ });
85785
+ if (redeliverCapture.capture) {
85786
+ pendingRedelivery = { turn: pending2, maxAgeMs: RESUME_MAX_AGE_MS };
85787
+ } else if (redeliverCapture.skipReason === "will-be-resumed") {
85788
+ process.stderr.write(`telegram gateway: crash-redelivery suppressed \u2014 interrupted turnKey=${pending2.turn_key} will be RESUMED (bootResumeKind=resume); the fresh re-answer supersedes the recovered draft (no double-send)
85789
+ `);
85790
+ } else {
85791
+ process.stderr.write(`telegram gateway: crash-redelivery skipped \u2014 interrupted turnKey=${pending2.turn_key} has no pinned session_id (pre-feature turn or no session event seen); cannot resolve exact transcript
85792
+ `);
85793
+ }
85261
85794
  let interruptedSubagents = [];
85262
85795
  try {
85263
85796
  interruptedSubagents = listNonTerminalSubagentsForTurn(turnsDb, pending2.turn_key).map((s) => ({ agentType: s.agent_type, description: s.description, status: s.status }));
@@ -85340,7 +85873,7 @@ try {
85340
85873
  `);
85341
85874
  }
85342
85875
  }
85343
- const pendingEnvPath = join54(agentDir, ".pending-turn.env");
85876
+ const pendingEnvPath = join55(agentDir, ".pending-turn.env");
85344
85877
  try {
85345
85878
  if (pending2 != null) {
85346
85879
  const lines = [
@@ -85396,6 +85929,10 @@ function resolveSubagentOriginChat(agentId) {
85396
85929
  var WORKER_FEED_FALLBACK_LOG_CAP = 256;
85397
85930
  var WORKER_FEED_STALE_TTL_MARGIN_MS = 300000;
85398
85931
  var WORKER_FEED_ABSOLUTE_ROW_LIFETIME_CAP_MULTIPLE = 4;
85932
+ var WORKER_FEED_GROUP_MESSAGE_LIFETIME_CAP_MS = (() => {
85933
+ const v = Number(process.env.SWITCHROOM_WORKER_FEED_GROUP_MESSAGE_LIFETIME_CAP_MS);
85934
+ return Number.isFinite(v) && v > 0 ? v : 3600000;
85935
+ })();
85399
85936
  var workerFeedOwnerDmFallbackLogged = new Set;
85400
85937
  function resolveWorkerFeedChat(agentId, fleetChatId) {
85401
85938
  const origin = resolveSubagentOriginChat(agentId);
@@ -85458,7 +85995,7 @@ function checkApprovals() {
85458
85995
  return;
85459
85996
  }
85460
85997
  for (const senderId of files) {
85461
- const file = join54(APPROVED_DIR, senderId);
85998
+ const file = join55(APPROVED_DIR, senderId);
85462
85999
  bot.api.sendMessage(senderId, "Paired! Say hi to Claude.").then(() => rmSync6(file, { force: true }), (err) => {
85463
86000
  process.stderr.write(`telegram gateway: failed to send approval confirm: ${err}
85464
86001
  `);
@@ -85563,7 +86100,7 @@ function noteAgentOutputAt(key, ts) {
85563
86100
  lastAgentOutputAt.delete(oldest);
85564
86101
  }
85565
86102
  }
85566
- var OBLIGATION_STORE_PATH = join54(STATE_DIR, "obligations.json");
86103
+ var OBLIGATION_STORE_PATH = join55(STATE_DIR, "obligations.json");
85567
86104
  var obligationStoreFs = {
85568
86105
  readFileSync: (p) => readFileSync54(p, "utf8"),
85569
86106
  writeFileSync: (p, d) => writeFileSync43(p, d),
@@ -85678,6 +86215,7 @@ var pendingRestarts = new Map;
85678
86215
  var pendingSessionCommand = createPendingSessionCommandSlots();
85679
86216
  var PENDING_CMD_DRAIN_CAP_MS = 60000;
85680
86217
  var lastSessionActiveFile = null;
86218
+ var lastSessionStampedTurnKey = null;
85681
86219
  var compactState = initialCompactState();
85682
86220
  var compactDispatching = false;
85683
86221
  var COMPACT_CARD_TIMEOUT_MS = 900000;
@@ -87891,7 +88429,7 @@ var statusPinState = new Map;
87891
88429
  var statusPinChatIds = new Map;
87892
88430
  var statusPinPinnedAt = new Map;
87893
88431
  var statusPinRightsCache = new PinRightsCache2;
87894
- var STATUS_PIN_STORE_PATH = join54(STATE_DIR, "status-pins.json");
88432
+ var STATUS_PIN_STORE_PATH = join55(STATE_DIR, "status-pins.json");
87895
88433
  var statusPinStoreFs = {
87896
88434
  readFileSync: (p) => readFileSync54(p, "utf8"),
87897
88435
  writeFileSync: (p, d) => writeFileSync43(p, d),
@@ -87899,7 +88437,7 @@ var statusPinStoreFs = {
87899
88437
  existsSync: (p) => existsSync50(p)
87900
88438
  };
87901
88439
  var statusPinPersistEnabled = !STATIC && PIN_STATUS_WHILE_WORKING;
87902
- var ACTIVITY_CARD_STORE_PATH = join54(STATE_DIR, "activity-cards-pending.json");
88440
+ var ACTIVITY_CARD_STORE_PATH = join55(STATE_DIR, "activity-cards-pending.json");
87903
88441
  var activityCardStoreFs = {
87904
88442
  readFileSync: (p) => readFileSync54(p, "utf8"),
87905
88443
  writeFileSync: (p, d) => writeFileSync43(p, d),
@@ -87907,7 +88445,7 @@ var activityCardStoreFs = {
87907
88445
  existsSync: (p) => existsSync50(p)
87908
88446
  };
87909
88447
  var activityCardPersistEnabled = !STATIC;
87910
- var QUEUED_CARD_STORE_PATH = join54(STATE_DIR, "queued-cards-pending.json");
88448
+ var QUEUED_CARD_STORE_PATH = join55(STATE_DIR, "queued-cards-pending.json");
87911
88449
  var queuedCardStoreFs = {
87912
88450
  readFileSync: (p) => readFileSync54(p, "utf8"),
87913
88451
  writeFileSync: (p, d) => writeFileSync43(p, d),
@@ -88261,11 +88799,11 @@ var getPinnedProgressCardMessageId = null;
88261
88799
  var completeProgressCardTurn = null;
88262
88800
  var subagentWatcher = null;
88263
88801
  var workerActivityFeed = null;
88264
- var SOCKET_PATH = process.env.SWITCHROOM_GATEWAY_SOCKET ?? join54(STATE_DIR, "gateway.sock");
88802
+ var SOCKET_PATH = process.env.SWITCHROOM_GATEWAY_SOCKET ?? join55(STATE_DIR, "gateway.sock");
88265
88803
  mkdirSync40(STATE_DIR, { recursive: true, mode: 448 });
88266
- var GATEWAY_PID_PATH = process.env.SWITCHROOM_GATEWAY_PID_FILE ?? join54(STATE_DIR, "gateway.pid.json");
88267
- var GATEWAY_SESSION_MARKER_PATH = process.env.SWITCHROOM_GATEWAY_SESSION_MARKER ?? join54(STATE_DIR, "gateway-session.json");
88268
- var GATEWAY_CLEAN_SHUTDOWN_MARKER_PATH = process.env.SWITCHROOM_GATEWAY_CLEAN_SHUTDOWN_MARKER ?? join54(STATE_DIR, "clean-shutdown.json");
88804
+ var GATEWAY_PID_PATH = process.env.SWITCHROOM_GATEWAY_PID_FILE ?? join55(STATE_DIR, "gateway.pid.json");
88805
+ var GATEWAY_SESSION_MARKER_PATH = process.env.SWITCHROOM_GATEWAY_SESSION_MARKER ?? join55(STATE_DIR, "gateway-session.json");
88806
+ var GATEWAY_CLEAN_SHUTDOWN_MARKER_PATH = process.env.SWITCHROOM_GATEWAY_CLEAN_SHUTDOWN_MARKER ?? join55(STATE_DIR, "clean-shutdown.json");
88269
88807
  var GATEWAY_STARTED_AT_MS = Date.now();
88270
88808
  var BOOT_CARD_ENABLED = process.env.SWITCHROOM_BOOT_CARD !== "false";
88271
88809
  var activeBootCard = null;
@@ -88294,7 +88832,7 @@ function ensureIssuesCard(chatId, threadId) {
88294
88832
  bot: botApi,
88295
88833
  log: (msg) => process.stderr.write(`telegram gateway: ${msg}
88296
88834
  `),
88297
- persistPath: join54(stateDir, "issues-card.json")
88835
+ persistPath: join55(stateDir, "issues-card.json")
88298
88836
  });
88299
88837
  activeIssuesWatcher = startIssuesWatcher({
88300
88838
  stateDir,
@@ -88625,7 +89163,7 @@ startTimer2({
88625
89163
  }
88626
89164
  });
88627
89165
  var inboundSpool = STATIC ? undefined : createInboundSpool({
88628
- path: join54(STATE_DIR, "inbound-spool.jsonl"),
89166
+ path: join55(STATE_DIR, "inbound-spool.jsonl"),
88629
89167
  fs: {
88630
89168
  appendFileSync: (p, d) => appendFileSync6(p, d),
88631
89169
  readFileSync: (p) => readFileSync54(p, "utf8"),
@@ -88760,6 +89298,88 @@ async function deliverCapturedProse(args) {
88760
89298
  }
88761
89299
  }
88762
89300
  }
89301
+ async function maybeRedeliverUndeliveredAnswer() {
89302
+ const candidate = pendingRedelivery;
89303
+ pendingRedelivery = null;
89304
+ if (candidate == null || turnsDb == null)
89305
+ return;
89306
+ const { turn, maxAgeMs } = candidate;
89307
+ const sessionId = turn.session_id;
89308
+ if (!sessionId)
89309
+ return;
89310
+ let transcriptText;
89311
+ try {
89312
+ const projectsDir = getProjectsDirForCwd();
89313
+ const path2 = join55(projectsDir, `${sessionId}.jsonl`);
89314
+ if (!existsSync50(path2)) {
89315
+ process.stderr.write(`telegram gateway: crash-redelivery \u2014 transcript not found for turnKey=${turn.turn_key} session=${sessionId} (${path2}); skipping
89316
+ `);
89317
+ return;
89318
+ }
89319
+ transcriptText = readFileSync54(path2, "utf8");
89320
+ } catch (err) {
89321
+ process.stderr.write(`telegram gateway: crash-redelivery \u2014 transcript read failed turnKey=${turn.turn_key}: ${err.message}
89322
+ `);
89323
+ return;
89324
+ }
89325
+ const projected = projectTrailingAnswerFromTranscript(transcriptText);
89326
+ const threadIdNum2 = turn.thread_id != null && turn.thread_id !== "" ? Number(turn.thread_id) : undefined;
89327
+ const threadIdForOracle = threadIdNum2 != null && Number.isFinite(threadIdNum2) ? threadIdNum2 : null;
89328
+ const decision = decideRedeliver({
89329
+ capturedText: projected.text,
89330
+ trailingIsText: projected.trailingIsText,
89331
+ hasDeliveredText: HISTORY_ENABLED ? hasOutboundWithText(turn.chat_id, projected.text, threadIdForOracle, turn.started_at) : false,
89332
+ alreadyRedelivered: turn.answer_redelivered_at != null,
89333
+ ageMs: Math.max(0, Date.now() - turn.started_at),
89334
+ maxAgeMs
89335
+ });
89336
+ if (!decision.redeliver || decision.framedText == null) {
89337
+ process.stderr.write(`telegram gateway: crash-redelivery skipped turnKey=${turn.turn_key} reason=${decision.skipReason ?? "unknown"}
89338
+ `);
89339
+ return;
89340
+ }
89341
+ const chatId = turn.chat_id;
89342
+ const out = redactOutboundText(decision.framedText, "crash_redelivery");
89343
+ const chunks = splitMarkdownChunks2(out, RICH_MESSAGE_MAX_CHARS2);
89344
+ const sentIds = [];
89345
+ try {
89346
+ let liveThreadId = threadIdNum2 != null && Number.isFinite(threadIdNum2) ? threadIdNum2 : undefined;
89347
+ for (const c of chunks) {
89348
+ const sent = await retryWithThreadFallback2(robustApiCall, (tid) => {
89349
+ const opts = {
89350
+ link_preview_options: { is_disabled: true },
89351
+ ...tid != null ? { message_thread_id: tid } : {}
89352
+ };
89353
+ return bot.api.sendRichMessage(chatId, richMessage2(c), opts);
89354
+ }, { threadId: liveThreadId, chat_id: chatId, verb: "crash-redelivery.sendMessage" });
89355
+ if (liveThreadId != null && sent.message_thread_id == null) {
89356
+ liveThreadId = undefined;
89357
+ }
89358
+ sentIds.push(sent.message_id);
89359
+ }
89360
+ if (HISTORY_ENABLED && sentIds.length > 0) {
89361
+ try {
89362
+ recordOutbound({
89363
+ chat_id: chatId,
89364
+ thread_id: threadIdForOracle,
89365
+ message_ids: sentIds,
89366
+ texts: chunks
89367
+ });
89368
+ } catch {}
89369
+ }
89370
+ try {
89371
+ markAnswerRedelivered(turnsDb, turn.turn_key);
89372
+ } catch (err) {
89373
+ process.stderr.write(`telegram gateway: crash-redelivery markAnswerRedelivered failed turnKey=${turn.turn_key}: ${err.message}
89374
+ `);
89375
+ }
89376
+ process.stderr.write(`telegram gateway: crash-redelivery \u2014 delivered recovered answer (${out.length} chars, ${chunks.length} chunk(s)) for turnKey=${turn.turn_key} chat=${chatId}
89377
+ `);
89378
+ } catch (err) {
89379
+ process.stderr.write(`telegram gateway: crash-redelivery send failed turnKey=${turn.turn_key}: ${err.message} ` + `\u2014 left un-stamped for a later retry
89380
+ `);
89381
+ }
89382
+ }
88763
89383
  function obligationSweep() {
88764
89384
  if (!OBLIGATION_LEDGER_ENABLED)
88765
89385
  return;
@@ -88919,8 +89539,8 @@ var bridgeDeadWatchdog = createBridgeDeadWatchdog({
88919
89539
  isSessionAlive: () => findAgentProcessInContainer2()?.comm === "claude",
88920
89540
  isShuttingDown: () => shuttingDown,
88921
89541
  escalate: (reason) => triggerSelfRestart(process.env.SWITCHROOM_AGENT_NAME ?? "", reason, 1500),
88922
- crashLogPath: join54(STATE_DIR, "bridge-crash.log"),
88923
- markerPath: join54(STATE_DIR, "bridge-dead-escalation.json"),
89542
+ crashLogPath: join55(STATE_DIR, "bridge-crash.log"),
89543
+ markerPath: join55(STATE_DIR, "bridge-dead-escalation.json"),
88924
89544
  log: (line) => process.stderr.write(`${line}
88925
89545
  `),
88926
89546
  priorStreak: bridgeDeadPriorStreak,
@@ -89047,8 +89667,8 @@ var ipcServer = createIpcServer({
89047
89667
  probeQuotaViaBroker: (t) => probeQuotaForBootCard(agentSlug, t),
89048
89668
  tmuxSupervisor: process.env.SWITCHROOM_TMUX_SUPERVISOR === "1",
89049
89669
  dockerMode: process.env.SWITCHROOM_RUNTIME === "docker",
89050
- configSnapshotPath: join54(resolvedAgentDirForCard, ".config-snapshot.json"),
89051
- bootCardStatePath: join54(resolvedAgentDirForCard, ".boot-card-msgid.json"),
89670
+ configSnapshotPath: join55(resolvedAgentDirForCard, ".config-snapshot.json"),
89671
+ bootCardStatePath: join55(resolvedAgentDirForCard, ".boot-card-msgid.json"),
89052
89672
  floodStatePath: FLOOD_STATE_PATH,
89053
89673
  ...updateOutcomeLine ? { updateOutcomeLine } : {}
89054
89674
  }, ackMsgId).then((handle) => {
@@ -89133,6 +89753,21 @@ var ipcServer = createIpcServer({
89133
89753
  return;
89134
89754
  if (msg.activeFile)
89135
89755
  lastSessionActiveFile = msg.activeFile;
89756
+ if (turnsDb != null && msg.activeFile != null) {
89757
+ const stampKey = currentTurn?.registryKey ?? null;
89758
+ if (stampKey != null && stampKey !== lastSessionStampedTurnKey) {
89759
+ const sessionId = basename13(msg.activeFile).replace(/\.jsonl$/, "");
89760
+ if (sessionId) {
89761
+ try {
89762
+ stampTurnSessionId(turnsDb, stampKey, sessionId);
89763
+ lastSessionStampedTurnKey = stampKey;
89764
+ } catch (err) {
89765
+ process.stderr.write(`telegram gateway: stampTurnSessionId failed turnKey=${stampKey}: ${err.message}
89766
+ `);
89767
+ }
89768
+ }
89769
+ }
89770
+ }
89136
89771
  const ev = msg.event;
89137
89772
  handleSessionEvent(ev);
89138
89773
  toolFlightTracker.onEvent(ev);
@@ -89715,7 +90350,7 @@ var ipcServer = createIpcServer({
89715
90350
  const receiverUid = receiverUidRaw ? Number(receiverUidRaw) : NaN;
89716
90351
  if (Number.isInteger(receiverUid))
89717
90352
  allowedUids.push(receiverUid);
89718
- const socketPath = join54(STATE_DIR, "webhook.sock");
90353
+ const socketPath = join55(STATE_DIR, "webhook.sock");
89719
90354
  const webhookInject = (agentName3, inbound) => {
89720
90355
  const msg = inbound;
89721
90356
  const delivered = ipcServer.sendToAgent(agentName3, msg);
@@ -89950,9 +90585,9 @@ function redactOutboundText(text5, site) {
89950
90585
  var VOICE_OUT_DEFAULT_CHUNK_CHARS = 600;
89951
90586
  var VOICE_OUT_HARD_CHUNK_CAP = 4096;
89952
90587
  var voiceOnDemandCache = new VoiceOnDemandCache({
89953
- persistPath: join54(STATE_DIR, "voice-ondemand.json")
90588
+ persistPath: join55(STATE_DIR, "voice-ondemand.json")
89954
90589
  });
89955
- var VOICE_CACHE_DIR = join54(STATE_DIR, "voice-cache");
90590
+ var VOICE_CACHE_DIR = join55(STATE_DIR, "voice-cache");
89956
90591
  var voicePreSynthQueue = new PreSynthQueue({
89957
90592
  runJob: async (job) => {
89958
90593
  const sidecarToken = await materializeSidecarToken();
@@ -90931,7 +91566,7 @@ async function executeSendGif(rawArgs) {
90931
91566
  };
90932
91567
  }
90933
91568
  async function publishToTelegraph(text5, shortName, authorName) {
90934
- const accountPath = join54(STATE_DIR, "telegraph-account.json");
91569
+ const accountPath = join55(STATE_DIR, "telegraph-account.json");
90935
91570
  let account = null;
90936
91571
  try {
90937
91572
  if (existsSync50(accountPath)) {
@@ -91854,7 +92489,8 @@ function composeTurnActivity(turn, final = false, liveSuffix = "") {
91854
92489
  elapsedMs: turn.startedAt > 0 ? Date.now() - turn.startedAt : 0,
91855
92490
  toolCount: turn.labeledToolCount,
91856
92491
  state: final ? "done" : "running",
91857
- model: turn.currentModel
92492
+ model: turn.currentModel,
92493
+ totalTokens: turn.totalTokens
91858
92494
  };
91859
92495
  return renderActivityFeedWithNested2(turn.mirrorLines, childLines, final, liveSuffix, stepCount, header);
91860
92496
  }
@@ -92315,6 +92951,8 @@ function handleSessionEvent(ev) {
92315
92951
  lastAssistantDone: false,
92316
92952
  toolCallCount: 0,
92317
92953
  labeledToolCount: 0,
92954
+ totalTokens: 0,
92955
+ seenUsageMessageIds: new Set,
92318
92956
  activityMessageId: null,
92319
92957
  activityInFlight: null,
92320
92958
  activityPendingRender: null,
@@ -92388,6 +93026,18 @@ function handleSessionEvent(ev) {
92388
93026
  sessionModelSource.noteTranscriptModel(ev.model);
92389
93027
  return;
92390
93028
  }
93029
+ case "usage": {
93030
+ const turn = currentTurn;
93031
+ if (turn == null)
93032
+ return;
93033
+ if (ev.messageId != null) {
93034
+ if (turn.seenUsageMessageIds.has(ev.messageId))
93035
+ return;
93036
+ turn.seenUsageMessageIds.add(ev.messageId);
93037
+ }
93038
+ turn.totalTokens += ev.totalTokens;
93039
+ return;
93040
+ }
92391
93041
  case "thinking": {
92392
93042
  const turn = currentTurn;
92393
93043
  if (turn == null)
@@ -94258,7 +94908,7 @@ function getMyAgentName() {
94258
94908
  const fromEnv = process.env.SWITCHROOM_AGENT_NAME;
94259
94909
  if (fromEnv && fromEnv.trim().length > 0)
94260
94910
  return fromEnv.trim();
94261
- return basename12(process.cwd());
94911
+ return basename13(process.cwd());
94262
94912
  }
94263
94913
  function isSelfTargetingCommand(name) {
94264
94914
  if (name === "all")
@@ -94271,7 +94921,7 @@ function restartMarkerPath() {
94271
94921
  const agentDir = resolveAgentDirFromEnv();
94272
94922
  if (!agentDir)
94273
94923
  return null;
94274
- return join54(agentDir, "restart-pending.json");
94924
+ return join55(agentDir, "restart-pending.json");
94275
94925
  }
94276
94926
  function writeRestartMarker(marker) {
94277
94927
  const p = restartMarkerPath();
@@ -94463,7 +95113,7 @@ function _resetDockerReachableCache() {
94463
95113
  }
94464
95114
  function spawnSwitchroomDetached(args, onFailure) {
94465
95115
  const fullArgs = SWITCHROOM_CONFIG ? ["--config", SWITCHROOM_CONFIG, ...args] : args;
94466
- const logPath = join54(STATE_DIR, "detached-spawn.log");
95116
+ const logPath = join55(STATE_DIR, "detached-spawn.log");
94467
95117
  let outFd = null;
94468
95118
  try {
94469
95119
  mkdirSync40(STATE_DIR, { recursive: true });
@@ -94861,7 +95511,7 @@ bot.use(async (ctx, next) => {
94861
95511
  });
94862
95512
  function readRecentDenialsForAgent(agentName3, windowMs, limit) {
94863
95513
  try {
94864
- const auditPath = join54(homedir17(), ".switchroom", "vault-audit.log");
95514
+ const auditPath = join55(homedir18(), ".switchroom", "vault-audit.log");
94865
95515
  if (!existsSync50(auditPath))
94866
95516
  return [];
94867
95517
  const raw = readFileSync54(auditPath, "utf8");
@@ -94915,7 +95565,7 @@ async function buildAgentMetadata(agentName3) {
94915
95565
  try {
94916
95566
  const agentDir = resolveAgentDirFromEnv();
94917
95567
  if (agentDir) {
94918
- const raw = readFileSync54(join54(agentDir, ".claude", ".claude.json"), "utf8");
95568
+ const raw = readFileSync54(join55(agentDir, ".claude", ".claude.json"), "utf8");
94919
95569
  claudeJson = JSON.parse(raw);
94920
95570
  }
94921
95571
  } catch {}
@@ -95111,7 +95761,7 @@ function buildModelDeps(restartCtx) {
95111
95761
  try {
95112
95762
  const agentDir = resolveAgentDirFromEnv();
95113
95763
  if (agentDir) {
95114
- const local = await fetchQuota2({ claudeConfigDir: join54(agentDir, ".claude") });
95764
+ const local = await fetchQuota2({ claudeConfigDir: join55(agentDir, ".claude") });
95115
95765
  if (local.ok)
95116
95766
  return formatQuotaLine2(local.data);
95117
95767
  }
@@ -95583,7 +96233,7 @@ bot.command("restart", async (ctx) => {
95583
96233
  function flushAgentHandoff(agentDir) {
95584
96234
  let removed = 0;
95585
96235
  for (const fname of [".handoff.md", ".handoff-topic"]) {
95586
- const p = join54(agentDir, fname);
96236
+ const p = join55(agentDir, fname);
95587
96237
  try {
95588
96238
  if (existsSync50(p)) {
95589
96239
  unlinkSync24(p);
@@ -95641,7 +96291,7 @@ async function handleNewCommand(ctx) {
95641
96291
  writeRestartMarker({ chat_id: chatId, thread_id: threadId ?? null, ack_message_id: ackId, ts: Date.now() });
95642
96292
  if (agentDir != null) {
95643
96293
  try {
95644
- writeFileSync43(join54(agentDir, ".force-fresh-session"), `${kind} at ${new Date().toISOString()}
96294
+ writeFileSync43(join55(agentDir, ".force-fresh-session"), `${kind} at ${new Date().toISOString()}
95645
96295
  `, "utf8");
95646
96296
  } catch (err) {
95647
96297
  process.stderr.write(`telegram gateway: failed to write force-fresh marker: ${err}
@@ -96011,7 +96661,7 @@ var lockoutOps = {
96011
96661
  writeFileSync: (p, data, opts) => writeFileSync43(p, data, opts),
96012
96662
  existsSync: (p) => existsSync50(p),
96013
96663
  mkdirSync: (p, opts) => mkdirSync40(p, opts),
96014
- joinPath: (...parts) => join54(...parts)
96664
+ joinPath: (...parts) => join55(...parts)
96015
96665
  };
96016
96666
  var FLEET_FALLBACK_DEDUP_MS = 30000;
96017
96667
  function isAuthBrokerSocketReachable() {
@@ -96271,7 +96921,7 @@ async function runCreditWatch() {
96271
96921
  if (!agentDir)
96272
96922
  return;
96273
96923
  const agentName3 = getMyAgentName();
96274
- const claudeConfigDir = join54(agentDir, ".claude");
96924
+ const claudeConfigDir = join55(agentDir, ".claude");
96275
96925
  const stateDir = STATE_DIR;
96276
96926
  const reason = readClaudeJsonOverage(claudeConfigDir);
96277
96927
  const prev = loadCreditState(stateDir);
@@ -97283,7 +97933,7 @@ bot.command("usage", async (ctx) => {
97283
97933
  await switchroomReply(ctx, "**/usage:** cannot resolve agent dir.", { html: true });
97284
97934
  return;
97285
97935
  }
97286
- const result = await fetchQuota2({ claudeConfigDir: join54(agentDir, ".claude") });
97936
+ const result = await fetchQuota2({ claudeConfigDir: join55(agentDir, ".claude") });
97287
97937
  if (!result.ok) {
97288
97938
  await switchroomReply(ctx, `**/usage:** ${escapeHtmlForTg2(result.reason)}`, { html: true });
97289
97939
  return;
@@ -99171,6 +99821,10 @@ var didOneTimeSetup = false;
99171
99821
  process.stderr.write(`telegram gateway: blocked-approval boot reconcile failed: ${err.message}
99172
99822
  `);
99173
99823
  }
99824
+ maybeRedeliverUndeliveredAnswer().catch((err) => {
99825
+ process.stderr.write(`telegram gateway: crash-redelivery boot send errored: ${err.message}
99826
+ `);
99827
+ });
99174
99828
  try {
99175
99829
  const bootAccess = loadAccess();
99176
99830
  const chatSet = new Set(bootAccess.allowFrom);
@@ -99268,7 +99922,7 @@ var didOneTimeSetup = false;
99268
99922
  return;
99269
99923
  }
99270
99924
  })();
99271
- const resolvedAgentDirForBootCard = agentDir ?? join54(homedir17(), ".switchroom", "agents", agentSlug);
99925
+ const resolvedAgentDirForBootCard = agentDir ?? join55(homedir18(), ".switchroom", "agents", agentSlug);
99272
99926
  const handle = await startBootCard(chatId, threadId, botApiForCard, {
99273
99927
  agentName: agentDisplayName,
99274
99928
  agentSlug,
@@ -99282,8 +99936,8 @@ var didOneTimeSetup = false;
99282
99936
  probeQuotaViaBroker: (t) => probeQuotaForBootCard(agentSlug, t),
99283
99937
  tmuxSupervisor: process.env.SWITCHROOM_TMUX_SUPERVISOR === "1",
99284
99938
  dockerMode: process.env.SWITCHROOM_RUNTIME === "docker",
99285
- configSnapshotPath: join54(resolvedAgentDirForBootCard, ".config-snapshot.json"),
99286
- bootCardStatePath: join54(resolvedAgentDirForBootCard, ".boot-card-msgid.json"),
99939
+ configSnapshotPath: join55(resolvedAgentDirForBootCard, ".config-snapshot.json"),
99940
+ bootCardStatePath: join55(resolvedAgentDirForBootCard, ".boot-card-msgid.json"),
99287
99941
  floodStatePath: FLOOD_STATE_PATH,
99288
99942
  ...updateOutcomeLine ? { updateOutcomeLine } : {}
99289
99943
  }, ackMsgId);
@@ -99314,7 +99968,7 @@ var didOneTimeSetup = false;
99314
99968
  try {
99315
99969
  const smAgentDir = resolveAgentDirFromEnv();
99316
99970
  if (smAgentDir) {
99317
- const activePath = join54(smAgentDir, ".active-session-model");
99971
+ const activePath = join55(smAgentDir, ".active-session-model");
99318
99972
  if (existsSync50(activePath)) {
99319
99973
  try {
99320
99974
  const launched = readFileSync54(activePath, "utf8").trim();
@@ -99326,7 +99980,7 @@ var didOneTimeSetup = false;
99326
99980
  sessionModelSource.setOverride(launched.length > 0 && launched !== configured ? launched : null);
99327
99981
  } catch {}
99328
99982
  }
99329
- const activeEffortPath = join54(smAgentDir, ".active-session-effort");
99983
+ const activeEffortPath = join55(smAgentDir, ".active-session-effort");
99330
99984
  if (existsSync50(activeEffortPath)) {
99331
99985
  try {
99332
99986
  const launchedEffort = readFileSync54(activeEffortPath, "utf8").trim();
@@ -99334,7 +99988,7 @@ var didOneTimeSetup = false;
99334
99988
  sessionEffortOverride = launchedEffort.length > 0 && launchedEffort !== configuredEffort ? launchedEffort : null;
99335
99989
  } catch {}
99336
99990
  }
99337
- const alertPath = join54(smAgentDir, ".session-model-alert");
99991
+ const alertPath = join55(smAgentDir, ".session-model-alert");
99338
99992
  if (existsSync50(alertPath)) {
99339
99993
  let alertText = null;
99340
99994
  try {
@@ -99463,6 +100117,7 @@ var didOneTimeSetup = false;
99463
100117
  maxRows: workerFeedMaxRows,
99464
100118
  staleWorkerTtlMs: resolveInflightTerminalCapMs() + WORKER_FEED_STALE_TTL_MARGIN_MS,
99465
100119
  absoluteRowLifetimeCapMs: resolveInflightTerminalCapMs() * WORKER_FEED_ABSOLUTE_ROW_LIFETIME_CAP_MULTIPLE,
100120
+ groupMessageLifetimeCapMs: WORKER_FEED_GROUP_MESSAGE_LIFETIME_CAP_MS,
99466
100121
  reconcilePin: ({ feedKey, chatId, messageId }) => {
99467
100122
  if (!PIN_STATUS_WHILE_WORKING)
99468
100123
  return;
@@ -99516,7 +100171,7 @@ var didOneTimeSetup = false;
99516
100171
  `);
99517
100172
  }
99518
100173
  },
99519
- onFinish: ({ agentId, outcome, description, resultText, toolCount, durationMs, background: entryBackground }) => {
100174
+ onFinish: ({ agentId, outcome, description, resultText, toolCount, totalTokens, durationMs, background: entryBackground }) => {
99520
100175
  deferredDoneReactions.promote();
99521
100176
  let fleetChatId = "";
99522
100177
  try {
@@ -99544,6 +100199,7 @@ var didOneTimeSetup = false;
99544
100199
  description: dispatch.feedDescription,
99545
100200
  lastTool: null,
99546
100201
  toolCount,
100202
+ totalTokens,
99547
100203
  latestSummary: resultText,
99548
100204
  elapsedMs: durationMs,
99549
100205
  state: outcome === "failed" ? "failed" : "done",
@@ -99591,6 +100247,7 @@ var didOneTimeSetup = false;
99591
100247
  description: dispatch.feedDescription,
99592
100248
  lastTool: null,
99593
100249
  toolCount,
100250
+ totalTokens,
99594
100251
  latestSummary: resultText,
99595
100252
  elapsedMs: durationMs,
99596
100253
  state: outcome === "failed" ? "failed" : "done",
@@ -99604,6 +100261,7 @@ var didOneTimeSetup = false;
99604
100261
  description: dispatch.feedDescription,
99605
100262
  lastTool: null,
99606
100263
  toolCount,
100264
+ totalTokens,
99607
100265
  latestSummary: resultText,
99608
100266
  elapsedMs: durationMs,
99609
100267
  state: outcome === "failed" ? "failed" : "done",
@@ -99644,7 +100302,7 @@ var didOneTimeSetup = false;
99644
100302
  process.stderr.write(`telegram gateway: subagent-handback queued agent=${agentId} outcome=${outcome} chat=${decision.chatId} resultChars=${resultText.length}
99645
100303
  `);
99646
100304
  },
99647
- onProgress: ({ agentId, description, latestSummary, elapsedMs, prevBucketIdx, setBucketIdx, lastTool, toolCount, progressLine, model, skeleton }) => {
100305
+ onProgress: ({ agentId, description, latestSummary, elapsedMs, prevBucketIdx, setBucketIdx, lastTool, toolCount, totalTokens, progressLine, model, skeleton }) => {
99648
100306
  let fleetChatId = "";
99649
100307
  try {
99650
100308
  const fleets = progressDriver?.peekAllFleets() ?? [];
@@ -99681,7 +100339,8 @@ var didOneTimeSetup = false;
99681
100339
  latestSummary: stepLine,
99682
100340
  elapsedMs,
99683
100341
  state: "running",
99684
- model: feedModel
100342
+ model: feedModel,
100343
+ totalTokens
99685
100344
  }, wk.threadId);
99686
100345
  return;
99687
100346
  }
@@ -99742,7 +100401,8 @@ var didOneTimeSetup = false;
99742
100401
  latestSummary: stepLine,
99743
100402
  elapsedMs,
99744
100403
  state: "running",
99745
- model: feedModel
100404
+ model: feedModel,
100405
+ totalTokens
99746
100406
  }, wk.threadId);
99747
100407
  return;
99748
100408
  }