switchroom 0.18.24 → 0.18.26

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 (54) hide show
  1. package/dist/cli/switchroom.js +59 -11
  2. package/dist/host-control/main.js +1 -1
  3. package/package.json +2 -2
  4. package/telegram-plugin/dist/bridge/bridge.js +26 -0
  5. package/telegram-plugin/dist/gateway/gateway.js +1827 -831
  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/render/code-segments.ts +210 -0
  15. package/telegram-plugin/render/dollar-math-guard.ts +126 -0
  16. package/telegram-plugin/render/emphasis-guard.ts +158 -0
  17. package/telegram-plugin/render/inline-pairs-guard.ts +171 -0
  18. package/telegram-plugin/render/line-start-guard.ts +167 -0
  19. package/telegram-plugin/render/rich-render.ts +7 -0
  20. package/telegram-plugin/rich-send.ts +48 -2
  21. package/telegram-plugin/session-tail.ts +185 -0
  22. package/telegram-plugin/subagent-watcher.ts +45 -0
  23. package/telegram-plugin/tests/crash-redelivery-resume-exclusion.test.ts +133 -0
  24. package/telegram-plugin/tests/crash-redelivery-wiring.test.ts +72 -0
  25. package/telegram-plugin/tests/history.test.ts +91 -0
  26. package/telegram-plugin/tests/model-command.test.ts +189 -12
  27. package/telegram-plugin/tests/redelivery-decision.test.ts +84 -0
  28. package/telegram-plugin/tests/registry-turns.test.ts +51 -0
  29. package/telegram-plugin/tests/render/dollar-math-guard.test.ts +162 -0
  30. package/telegram-plugin/tests/render/emphasis-guard.test.ts +205 -0
  31. package/telegram-plugin/tests/render/guard-composition.test.ts +138 -0
  32. package/telegram-plugin/tests/render/inline-pairs-guard.test.ts +171 -0
  33. package/telegram-plugin/tests/render/line-start-guard.test.ts +164 -0
  34. package/telegram-plugin/tests/session-model-source.test.ts +11 -0
  35. package/telegram-plugin/tests/session-tail.test.ts +145 -0
  36. package/telegram-plugin/tests/subagent-watcher.test.ts +50 -0
  37. package/telegram-plugin/tests/tool-activity-summary.test.ts +109 -0
  38. package/telegram-plugin/tests/trailing-answer-projector.test.ts +124 -0
  39. package/telegram-plugin/tests/vault-grant-inbound-builders.test.ts +125 -0
  40. package/telegram-plugin/tests/worker-feed-pin-persistence.test.ts +306 -0
  41. package/telegram-plugin/tool-activity-summary.ts +54 -3
  42. package/telegram-plugin/worker-activity-feed.ts +104 -0
  43. package/vendor/hindsight-memory/scripts/backfill_transcripts.py +762 -0
  44. package/vendor/hindsight-memory/scripts/drain_pending.py +13 -1
  45. package/vendor/hindsight-memory/scripts/lib/client.py +14 -4
  46. package/vendor/hindsight-memory/scripts/lib/config.py +8 -0
  47. package/vendor/hindsight-memory/scripts/lib/pacing.py +102 -0
  48. package/vendor/hindsight-memory/scripts/lib/watermark.py +213 -0
  49. package/vendor/hindsight-memory/scripts/reconcile_tail.py +344 -0
  50. package/vendor/hindsight-memory/scripts/retain.py +299 -143
  51. package/vendor/hindsight-memory/scripts/session_start.py +14 -0
  52. package/vendor/hindsight-memory/scripts/tests/test_backfill.py +362 -0
  53. package/vendor/hindsight-memory/scripts/tests/test_reconcile_durability.py +350 -0
  54. package/vendor/hindsight-memory/tests/test_hooks.py +8 -2
@@ -7525,9 +7525,329 @@ var init_approval_card = __esm(() => {
7525
7525
  import_grammy3 = __toESM(require_mod2(), 1);
7526
7526
  });
7527
7527
 
7528
+ // render/code-segments.ts
7529
+ function findClosingBackticks(text, from, runLen) {
7530
+ let i = from;
7531
+ while (i < text.length) {
7532
+ if (text[i] === "`") {
7533
+ let j = i;
7534
+ while (j < text.length && text[j] === "`")
7535
+ j++;
7536
+ if (j - i === runLen)
7537
+ return j;
7538
+ i = j;
7539
+ } else {
7540
+ i++;
7541
+ }
7542
+ }
7543
+ return -1;
7544
+ }
7545
+ function splitCodeSegments(text) {
7546
+ const out = [];
7547
+ let i = 0;
7548
+ let plainStart = 0;
7549
+ while (i < text.length) {
7550
+ if (text[i] === "`") {
7551
+ let j = i;
7552
+ while (j < text.length && text[j] === "`")
7553
+ j++;
7554
+ const runLen = j - i;
7555
+ const close = findClosingBackticks(text, j, runLen);
7556
+ if (close !== -1) {
7557
+ if (plainStart < i)
7558
+ out.push({ code: false, text: text.slice(plainStart, i) });
7559
+ out.push({ code: true, text: text.slice(i, close) });
7560
+ i = close;
7561
+ plainStart = close;
7562
+ continue;
7563
+ }
7564
+ }
7565
+ i++;
7566
+ }
7567
+ if (plainStart < text.length)
7568
+ out.push({ code: false, text: text.slice(plainStart) });
7569
+ return out;
7570
+ }
7571
+ function isTableDelimiterRow(line) {
7572
+ const t = line.trim();
7573
+ return t.length > 0 && /^[\s|:-]+$/.test(t) && t.includes("-") && t.includes("|");
7574
+ }
7575
+ function isTableCandidateLine(line) {
7576
+ return /^\s*\|/.test(line);
7577
+ }
7578
+ function findTableRanges(text) {
7579
+ const ranges = [];
7580
+ const lines = text.split(`
7581
+ `);
7582
+ let offset = 0;
7583
+ let runStart = -1;
7584
+ let runEnd = -1;
7585
+ let runHasDelim = false;
7586
+ let runLineCount = 0;
7587
+ const flush = () => {
7588
+ if (runStart !== -1 && runLineCount >= 2 && runHasDelim) {
7589
+ ranges.push([runStart, runEnd]);
7590
+ }
7591
+ runStart = -1;
7592
+ runEnd = -1;
7593
+ runHasDelim = false;
7594
+ runLineCount = 0;
7595
+ };
7596
+ for (let k = 0;k < lines.length; k++) {
7597
+ const line = lines[k];
7598
+ const lineLen = line.length + (k < lines.length - 1 ? 1 : 0);
7599
+ if (isTableCandidateLine(line)) {
7600
+ if (runStart === -1)
7601
+ runStart = offset;
7602
+ runEnd = offset + lineLen;
7603
+ runLineCount += 1;
7604
+ if (isTableDelimiterRow(line))
7605
+ runHasDelim = true;
7606
+ } else {
7607
+ flush();
7608
+ }
7609
+ offset += lineLen;
7610
+ }
7611
+ flush();
7612
+ return ranges;
7613
+ }
7614
+ function splitProseProtected(text) {
7615
+ const out = [];
7616
+ const tables = findTableRanges(text);
7617
+ let tIdx = 0;
7618
+ let i = 0;
7619
+ let plainStart = 0;
7620
+ const pushProtected = (from, to) => {
7621
+ if (plainStart < from)
7622
+ out.push({ code: false, text: text.slice(plainStart, from) });
7623
+ out.push({ code: true, text: text.slice(from, to) });
7624
+ plainStart = to;
7625
+ };
7626
+ while (i < text.length) {
7627
+ while (tIdx < tables.length && tables[tIdx][1] <= i)
7628
+ tIdx++;
7629
+ if (tIdx < tables.length && tables[tIdx][0] === i) {
7630
+ const [, end] = tables[tIdx];
7631
+ pushProtected(i, end);
7632
+ i = end;
7633
+ continue;
7634
+ }
7635
+ const ch = text[i];
7636
+ if (ch === "[") {
7637
+ const close = text.indexOf("]", i + 1);
7638
+ if (close !== -1 && text[close + 1] === "(") {
7639
+ const destClose = text.indexOf(")", close + 2);
7640
+ if (destClose !== -1) {
7641
+ pushProtected(close + 1, destClose + 1);
7642
+ i = destClose + 1;
7643
+ continue;
7644
+ }
7645
+ }
7646
+ i++;
7647
+ continue;
7648
+ }
7649
+ if ((ch === "h" || ch === "w") && (i === 0 || !/[A-Za-z0-9]/.test(text[i - 1]))) {
7650
+ const m = /^(?:https?:\/\/|www\.)[^\s<>()\[\]]+/.exec(text.slice(i));
7651
+ if (m) {
7652
+ const end = i + m[0].length;
7653
+ pushProtected(i, end);
7654
+ i = end;
7655
+ continue;
7656
+ }
7657
+ }
7658
+ i++;
7659
+ }
7660
+ if (plainStart < text.length)
7661
+ out.push({ code: false, text: text.slice(plainStart) });
7662
+ return out;
7663
+ }
7664
+ function splitProtectedSegments(text) {
7665
+ const out = [];
7666
+ for (const seg of splitCodeSegments(text)) {
7667
+ if (seg.code) {
7668
+ out.push(seg);
7669
+ continue;
7670
+ }
7671
+ for (const sub of splitProseProtected(seg.text))
7672
+ out.push(sub);
7673
+ }
7674
+ return out;
7675
+ }
7676
+
7677
+ // render/dollar-math-guard.ts
7678
+ function guardDollarMath(text) {
7679
+ if (!text.includes("$"))
7680
+ return text;
7681
+ const segments = splitProtectedSegments(text);
7682
+ let total = 0;
7683
+ let hasCurrencySignal = false;
7684
+ for (const seg of segments) {
7685
+ if (seg.code)
7686
+ continue;
7687
+ total += seg.text.match(ANY_DOLLAR)?.length ?? 0;
7688
+ if (!hasCurrencySignal && CURRENCY_SIGNAL.test(seg.text))
7689
+ hasCurrencySignal = true;
7690
+ }
7691
+ if (total < 2 || !hasCurrencySignal)
7692
+ return text;
7693
+ return segments.map((seg) => seg.code ? seg.text : seg.text.replace(UNESCAPED_DOLLAR, () => "\\$")).join("");
7694
+ }
7695
+ var ANY_DOLLAR, CURRENCY_SIGNAL, UNESCAPED_DOLLAR;
7696
+ var init_dollar_math_guard = __esm(() => {
7697
+ ANY_DOLLAR = /\$/g;
7698
+ CURRENCY_SIGNAL = /\$\.?\d|\d\s?\$/;
7699
+ UNESCAPED_DOLLAR = /(?<!\\)\$/g;
7700
+ });
7701
+
7702
+ // render/emphasis-guard.ts
7703
+ function guardAccidentalEmphasis(text) {
7704
+ if (!text.includes("_") && !text.includes("*"))
7705
+ return text;
7706
+ const segments = splitProtectedSegments(text);
7707
+ let hasIntraUnderscore = false;
7708
+ let hasIntraAsterisk = false;
7709
+ let underscoreCount = 0;
7710
+ let asteriskCount = 0;
7711
+ for (const seg of segments) {
7712
+ if (seg.code)
7713
+ continue;
7714
+ if (INTRA_WORD_UNDERSCORE.test(seg.text))
7715
+ hasIntraUnderscore = true;
7716
+ if (INTRA_WORD_ASTERISK.test(seg.text))
7717
+ hasIntraAsterisk = true;
7718
+ underscoreCount += seg.text.match(ANY_UNDERSCORE)?.length ?? 0;
7719
+ asteriskCount += seg.text.match(ANY_ASTERISK)?.length ?? 0;
7720
+ }
7721
+ INTRA_WORD_UNDERSCORE.lastIndex = 0;
7722
+ INTRA_WORD_ASTERISK.lastIndex = 0;
7723
+ const armUnderscore = hasIntraUnderscore && underscoreCount >= 2;
7724
+ const armAsterisk = hasIntraAsterisk && asteriskCount >= 2;
7725
+ if (!armUnderscore && !armAsterisk)
7726
+ return text;
7727
+ return segments.map((seg) => {
7728
+ if (seg.code)
7729
+ return seg.text;
7730
+ let out = seg.text;
7731
+ if (armUnderscore)
7732
+ out = out.replace(INTRA_WORD_UNDERSCORE, "\\_");
7733
+ if (armAsterisk)
7734
+ out = out.replace(INTRA_WORD_ASTERISK, "\\*");
7735
+ return out;
7736
+ }).join("");
7737
+ }
7738
+ var INTRA_WORD_UNDERSCORE, INTRA_WORD_ASTERISK, ANY_UNDERSCORE, ANY_ASTERISK;
7739
+ var init_emphasis_guard = __esm(() => {
7740
+ INTRA_WORD_UNDERSCORE = /(?<=[A-Za-z0-9])_(?=[A-Za-z0-9])/g;
7741
+ INTRA_WORD_ASTERISK = /(?<=[A-Za-z0-9])\*(?=[A-Za-z0-9])/g;
7742
+ ANY_UNDERSCORE = /(?<!\\)_/g;
7743
+ ANY_ASTERISK = /(?<!\\)\*/g;
7744
+ });
7745
+
7746
+ // render/line-start-guard.ts
7747
+ function escapeAccidentalLineStart(line) {
7748
+ const indent = /^ */.exec(line)[0];
7749
+ if (indent.length >= 4)
7750
+ return line;
7751
+ const rest = line.slice(indent.length);
7752
+ if (ACCIDENTAL_BLOCKQUOTE.test(rest)) {
7753
+ return indent + "\\" + rest;
7754
+ }
7755
+ const ol = ACCIDENTAL_ORDERED_LIST.exec(rest);
7756
+ if (ol) {
7757
+ const digits = ol[1];
7758
+ const delim = ol[2];
7759
+ return indent + digits + "\\" + delim + rest.slice(digits.length + 1);
7760
+ }
7761
+ return line;
7762
+ }
7763
+ function guardAccidentalBlockConstructs(text) {
7764
+ if (!text.includes(">") && !/\d{4,}[.)]/.test(text))
7765
+ return text;
7766
+ const segments = splitProtectedSegments(text);
7767
+ let out = "";
7768
+ let atLineStart = true;
7769
+ for (const seg of segments) {
7770
+ if (seg.code) {
7771
+ out += seg.text;
7772
+ atLineStart = seg.text.endsWith(`
7773
+ `);
7774
+ continue;
7775
+ }
7776
+ const lines = seg.text.split(`
7777
+ `);
7778
+ for (let k = 0;k < lines.length; k++) {
7779
+ const lineIsAtStart = k === 0 ? atLineStart : true;
7780
+ const processed = lineIsAtStart ? escapeAccidentalLineStart(lines[k]) : lines[k];
7781
+ out += processed;
7782
+ if (k < lines.length - 1)
7783
+ out += `
7784
+ `;
7785
+ }
7786
+ atLineStart = seg.text.endsWith(`
7787
+ `);
7788
+ }
7789
+ return out;
7790
+ }
7791
+ var ACCIDENTAL_BLOCKQUOTE, ACCIDENTAL_ORDERED_LIST;
7792
+ var init_line_start_guard = __esm(() => {
7793
+ ACCIDENTAL_BLOCKQUOTE = /^>[0-9=]/;
7794
+ ACCIDENTAL_ORDERED_LIST = /^(\d{4,})([.)])(\s|$)/;
7795
+ });
7796
+
7797
+ // render/inline-pairs-guard.ts
7798
+ function countMatches(text, re) {
7799
+ return text.match(re)?.length ?? 0;
7800
+ }
7801
+ function guardAccidentalInlinePairs(text) {
7802
+ if (!/[~=|]/.test(text))
7803
+ return text;
7804
+ const segments = splitProtectedSegments(text);
7805
+ let tildes = 0;
7806
+ let marks = 0;
7807
+ let spoilers = 0;
7808
+ for (const seg of segments) {
7809
+ if (seg.code)
7810
+ continue;
7811
+ tildes += countMatches(seg.text, TILDE_APPROX);
7812
+ marks += countMatches(seg.text, MARK_OP);
7813
+ spoilers += countMatches(seg.text, SPOILER_OP);
7814
+ }
7815
+ const armTilde = tildes >= 2;
7816
+ const armMark = marks >= 2;
7817
+ const armSpoiler = spoilers >= 2;
7818
+ if (!armTilde && !armMark && !armSpoiler)
7819
+ return text;
7820
+ return segments.map((seg) => {
7821
+ if (seg.code)
7822
+ return seg.text;
7823
+ let out = seg.text;
7824
+ if (armTilde)
7825
+ out = out.replace(TILDE_APPROX, () => "\\~");
7826
+ if (armMark)
7827
+ out = out.replace(MARK_OP, () => "\\=\\=");
7828
+ if (armSpoiler)
7829
+ out = out.replace(SPOILER_OP, () => "\\|\\|");
7830
+ return out;
7831
+ }).join("");
7832
+ }
7833
+ var TILDE_APPROX, MARK_OP, SPOILER_OP;
7834
+ var init_inline_pairs_guard = __esm(() => {
7835
+ TILDE_APPROX = /(?<!\\)~(?=\$?\.?\d)/g;
7836
+ MARK_OP = /(?<=\w)==(?=\w)/g;
7837
+ SPOILER_OP = /(?<=\w)\|\|(?=\w)/g;
7838
+ });
7839
+
7528
7840
  // rich-send.ts
7841
+ function guardAccidentalFormatting(markdown) {
7842
+ let out = markdown;
7843
+ out = guardAccidentalEmphasis(out);
7844
+ out = guardAccidentalBlockConstructs(out);
7845
+ out = guardAccidentalInlinePairs(out);
7846
+ out = guardDollarMath(out);
7847
+ return out;
7848
+ }
7529
7849
  function richMessage(markdown) {
7530
- return { markdown };
7850
+ return { markdown: guardAccidentalFormatting(markdown) };
7531
7851
  }
7532
7852
  function isParseEntitiesError(err) {
7533
7853
  if (!(err instanceof import_grammy4.GrammyError) || err.error_code !== 400)
@@ -7545,6 +7865,10 @@ function isLengthError(err) {
7545
7865
  }
7546
7866
  var import_grammy4;
7547
7867
  var init_rich_send = __esm(() => {
7868
+ init_dollar_math_guard();
7869
+ init_emphasis_guard();
7870
+ init_line_start_guard();
7871
+ init_inline_pairs_guard();
7548
7872
  import_grammy4 = __toESM(require_mod2(), 1);
7549
7873
  });
7550
7874
 
@@ -21866,6 +22190,142 @@ var init_loader = __esm(() => {
21866
22190
  };
21867
22191
  });
21868
22192
 
22193
+ // quota-check.ts
22194
+ import { readFileSync as readFileSync17, existsSync as existsSync15 } from "fs";
22195
+ import { join as join20 } from "path";
22196
+ function readOauthToken(claudeConfigDir) {
22197
+ const tokenFile = join20(claudeConfigDir, ".oauth-token");
22198
+ if (!existsSync15(tokenFile))
22199
+ return null;
22200
+ try {
22201
+ const raw = readFileSync17(tokenFile, "utf-8").trim();
22202
+ return raw.length > 0 ? raw : null;
22203
+ } catch {
22204
+ return null;
22205
+ }
22206
+ }
22207
+ function parseFloatHeader(headers, name) {
22208
+ const v = headers.get(name);
22209
+ if (v == null || v.trim().length === 0)
22210
+ return null;
22211
+ const n = Number(v);
22212
+ return Number.isFinite(n) ? n : null;
22213
+ }
22214
+ function parseEpochHeader(headers, name) {
22215
+ const v = headers.get(name);
22216
+ if (v == null)
22217
+ return null;
22218
+ const n = Number(v);
22219
+ if (!Number.isFinite(n) || n <= 0)
22220
+ return null;
22221
+ return new Date(n * 1000);
22222
+ }
22223
+ function parseQuotaHeaders(headers) {
22224
+ const fiveHour = parseFloatHeader(headers, "anthropic-ratelimit-unified-5h-utilization");
22225
+ const sevenDay = parseFloatHeader(headers, "anthropic-ratelimit-unified-7d-utilization");
22226
+ if (fiveHour == null && sevenDay == null) {
22227
+ return {
22228
+ ok: false,
22229
+ reason: "no unified rate-limit headers in response (API token, not OAuth?)"
22230
+ };
22231
+ }
22232
+ return {
22233
+ ok: true,
22234
+ data: {
22235
+ fiveHourUtilizationPct: (fiveHour ?? 0) * 100,
22236
+ sevenDayUtilizationPct: (sevenDay ?? 0) * 100,
22237
+ fiveHourUtilPresent: fiveHour != null,
22238
+ sevenDayUtilPresent: sevenDay != null,
22239
+ fiveHourResetAt: parseEpochHeader(headers, "anthropic-ratelimit-unified-5h-reset"),
22240
+ sevenDayResetAt: parseEpochHeader(headers, "anthropic-ratelimit-unified-7d-reset"),
22241
+ representativeClaim: headers.get("anthropic-ratelimit-unified-representative-claim"),
22242
+ overageStatus: headers.get("anthropic-ratelimit-unified-overage-status"),
22243
+ overageDisabledReason: headers.get("anthropic-ratelimit-unified-overage-disabled-reason")
22244
+ }
22245
+ };
22246
+ }
22247
+ async function fetchQuota(opts) {
22248
+ let token;
22249
+ if (opts.accessToken && opts.claudeConfigDir) {
22250
+ return {
22251
+ ok: false,
22252
+ reason: "pass only one of `accessToken` or `claudeConfigDir`, not both"
22253
+ };
22254
+ }
22255
+ if (opts.accessToken) {
22256
+ token = opts.accessToken.trim().length > 0 ? opts.accessToken : null;
22257
+ } else if (opts.claudeConfigDir) {
22258
+ token = readOauthToken(opts.claudeConfigDir);
22259
+ } else {
22260
+ return {
22261
+ ok: false,
22262
+ reason: "fetchQuota requires `accessToken` or `claudeConfigDir`"
22263
+ };
22264
+ }
22265
+ if (!token) {
22266
+ return { ok: false, reason: "no OAuth token at .oauth-token" };
22267
+ }
22268
+ const controller = new AbortController;
22269
+ const timeout = setTimeout(() => controller.abort(), opts.timeoutMs ?? 1e4);
22270
+ const fetchFn = opts.fetchImpl ?? fetch;
22271
+ let resp;
22272
+ try {
22273
+ resp = await fetchFn("https://api.anthropic.com/v1/messages", {
22274
+ method: "POST",
22275
+ headers: {
22276
+ "anthropic-version": "2023-06-01",
22277
+ "anthropic-beta": OAUTH_BETA,
22278
+ authorization: `Bearer ${token}`,
22279
+ "x-app": "cli",
22280
+ "user-agent": DEFAULT_USER_AGENT,
22281
+ "content-type": "application/json"
22282
+ },
22283
+ body: JSON.stringify({
22284
+ model: opts.model ?? DEFAULT_PROBE_MODEL,
22285
+ max_tokens: 1,
22286
+ messages: [{ role: "user", content: "hi" }]
22287
+ }),
22288
+ signal: controller.signal
22289
+ });
22290
+ } catch (err) {
22291
+ const msg = err?.message ?? String(err);
22292
+ return { ok: false, reason: `request failed: ${msg}` };
22293
+ } finally {
22294
+ clearTimeout(timeout);
22295
+ }
22296
+ if (resp.status === 401 || resp.status === 403) {
22297
+ return { ok: false, reason: `auth rejected (HTTP ${resp.status})` };
22298
+ }
22299
+ const parsed = parseQuotaHeaders(resp.headers);
22300
+ if (!parsed.ok && resp.status >= 400) {
22301
+ return { ok: false, reason: `HTTP ${resp.status}, ${parsed.reason}` };
22302
+ }
22303
+ return parsed;
22304
+ }
22305
+ function formatQuotaLine(q) {
22306
+ const fmt = (n) => `${Math.round(n)}%`;
22307
+ return `${fmt(q.fiveHourUtilizationPct)} / 5h \u00b7 ${fmt(q.sevenDayUtilizationPct)} / 7d`;
22308
+ }
22309
+ function formatResetRelative(target, now = new Date) {
22310
+ if (!target)
22311
+ return "\u2014";
22312
+ const deltaMs = target.getTime() - now.getTime();
22313
+ if (deltaMs <= 0)
22314
+ return "resets now";
22315
+ const totalMin = Math.round(deltaMs / 60000);
22316
+ if (totalMin < 60)
22317
+ return `resets in ${totalMin}m`;
22318
+ const hours = Math.floor(totalMin / 60);
22319
+ const mins = totalMin % 60;
22320
+ if (hours < 24)
22321
+ return mins > 0 ? `resets in ${hours}h ${mins}m` : `resets in ${hours}h`;
22322
+ const days = Math.floor(hours / 24);
22323
+ const remH = hours % 24;
22324
+ return remH > 0 ? `resets in ${days}d ${remH}h` : `resets in ${days}d`;
22325
+ }
22326
+ 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";
22327
+ var init_quota_check = () => {};
22328
+
21869
22329
  // ../node_modules/.bun/@xterm+headless@6.0.0/node_modules/@xterm/headless/lib-headless/xterm-headless.js
21870
22330
  var require_xterm_headless = __commonJS((exports2) => {
21871
22331
  (() => {
@@ -27427,9 +27887,9 @@ var init_flock = () => {};
27427
27887
  // ../src/vault/vault.ts
27428
27888
  import { randomBytes as randomBytes4, scryptSync, createCipheriv, createDecipheriv } from "node:crypto";
27429
27889
  import {
27430
- readFileSync as readFileSync17,
27890
+ readFileSync as readFileSync18,
27431
27891
  writeSync as writeSync2,
27432
- existsSync as existsSync15,
27892
+ existsSync as existsSync16,
27433
27893
  renameSync as renameSync6,
27434
27894
  mkdirSync as mkdirSync16,
27435
27895
  unlinkSync as unlinkSync10,
@@ -27470,12 +27930,12 @@ function normalizeSecrets(raw) {
27470
27930
  return out;
27471
27931
  }
27472
27932
  function openVault(passphrase, vaultPath) {
27473
- if (!existsSync15(vaultPath)) {
27933
+ if (!existsSync16(vaultPath)) {
27474
27934
  throw new VaultError(`Vault file not found: ${vaultPath}`);
27475
27935
  }
27476
27936
  let vaultFile;
27477
27937
  try {
27478
- vaultFile = JSON.parse(readFileSync17(vaultPath, "utf8"));
27938
+ vaultFile = JSON.parse(readFileSync18(vaultPath, "utf8"));
27479
27939
  } catch {
27480
27940
  throw new VaultError(`Failed to read vault file: ${vaultPath}`);
27481
27941
  }
@@ -27529,7 +27989,7 @@ import {
27529
27989
  statSync as statSync5,
27530
27990
  writeSync as writeSync3
27531
27991
  } from "node:fs";
27532
- import { join as join21 } from "node:path";
27992
+ import { join as join23 } from "node:path";
27533
27993
  import { tmpdir as tmpdir2 } from "node:os";
27534
27994
  import { constants as fsConstants } from "node:fs";
27535
27995
  function isVaultReference(value) {
@@ -27581,11 +28041,11 @@ function materializationRoot() {
27581
28041
  return cachedRoot;
27582
28042
  const xdg = process.env.XDG_RUNTIME_DIR;
27583
28043
  if (xdg) {
27584
- const base = join21(xdg, "switchroom", "vault");
28044
+ const base = join23(xdg, "switchroom", "vault");
27585
28045
  mkdirSync17(base, { recursive: true, mode: 448 });
27586
- cachedRoot = mkdtempSync(join21(base, "run-"));
28046
+ cachedRoot = mkdtempSync(join23(base, "run-"));
27587
28047
  } else {
27588
- cachedRoot = mkdtempSync(join21(tmpdir2(), "switchroom-vault-"));
28048
+ cachedRoot = mkdtempSync(join23(tmpdir2(), "switchroom-vault-"));
27589
28049
  }
27590
28050
  chmodSync5(cachedRoot, 448);
27591
28051
  return cachedRoot;
@@ -27600,7 +28060,7 @@ function writeFileExclusive(filePath, content3) {
27600
28060
  }
27601
28061
  }
27602
28062
  function materializeFilesEntry(key, files) {
27603
- const dir = join21(materializationRoot(), key);
28063
+ const dir = join23(materializationRoot(), key);
27604
28064
  if (materializedDirs.has(dir)) {
27605
28065
  try {
27606
28066
  rmSync(dir, { recursive: true, force: true });
@@ -27616,7 +28076,7 @@ function materializeFilesEntry(key, files) {
27616
28076
  if (filename.includes("/") || filename.includes("\\") || filename === ".." || filename === "." || filename.includes("\x00")) {
27617
28077
  throw new Error(`Refusing to materialize vault file with unsafe name: ${filename}`);
27618
28078
  }
27619
- const filePath = join21(dir, filename);
28079
+ const filePath = join23(dir, filename);
27620
28080
  const content3 = encoding === "base64" ? Buffer.from(value, "base64") : value;
27621
28081
  writeFileExclusive(filePath, content3);
27622
28082
  }
@@ -28184,17 +28644,21 @@ __export(exports_history, {
28184
28644
  recordEdit: () => recordEdit,
28185
28645
  query: () => query,
28186
28646
  pruneMessagesOlderThanDays: () => pruneMessagesOlderThanDays,
28647
+ normalizeDeliveryText: () => normalizeDeliveryText,
28187
28648
  lookupMessageRoleAndText: () => lookupMessageRoleAndText,
28188
28649
  initHistory: () => initHistory,
28650
+ hasOutboundWithText: () => hasOutboundWithText,
28189
28651
  hasOutboundDeliveredSince: () => hasOutboundDeliveredSince,
28190
28652
  getRecentOutboundCount: () => getRecentOutboundCount,
28191
28653
  getLatestInboundMessageId: () => getLatestInboundMessageId,
28654
+ deliveryTextMatch: () => deliveryTextMatch,
28192
28655
  deleteFromHistory: () => deleteFromHistory,
28193
28656
  checkpointWal: () => checkpointWal,
28194
- _resetForTests: () => _resetForTests
28657
+ _resetForTests: () => _resetForTests,
28658
+ MIN_PREFIX_MATCH_CHARS: () => MIN_PREFIX_MATCH_CHARS
28195
28659
  });
28196
- import { chmodSync as chmodSync7, existsSync as existsSync21, mkdirSync as mkdirSync21 } from "fs";
28197
- import { join as join24 } from "path";
28660
+ import { chmodSync as chmodSync7, existsSync as existsSync22, mkdirSync as mkdirSync21 } from "fs";
28661
+ import { join as join26 } from "path";
28198
28662
  function loadDatabaseClass() {
28199
28663
  if (DatabaseClass != null)
28200
28664
  return DatabaseClass;
@@ -28217,7 +28681,7 @@ function initHistory(stateDir, retentionDays = 30) {
28217
28681
  return;
28218
28682
  const Database = loadDatabaseClass();
28219
28683
  mkdirSync21(stateDir, { recursive: true, mode: 448 });
28220
- const path2 = join24(stateDir, "history.db");
28684
+ const path2 = join26(stateDir, "history.db");
28221
28685
  dbPath = path2;
28222
28686
  db = new Database(path2, { create: true });
28223
28687
  db.exec("PRAGMA journal_mode = WAL");
@@ -28283,7 +28747,7 @@ function initHistory(stateDir, retentionDays = 30) {
28283
28747
  }
28284
28748
  for (const suffix of ["", "-shm", "-wal"]) {
28285
28749
  const f = path2 + suffix;
28286
- if (existsSync21(f)) {
28750
+ if (existsSync22(f)) {
28287
28751
  try {
28288
28752
  chmodSync7(f, 420);
28289
28753
  } catch {}
@@ -28308,7 +28772,7 @@ function checkpointWal() {
28308
28772
  if (dbPath) {
28309
28773
  for (const suffix of ["-shm", "-wal"]) {
28310
28774
  const f = dbPath + suffix;
28311
- if (existsSync21(f)) {
28775
+ if (existsSync22(f)) {
28312
28776
  try {
28313
28777
  chmodSync7(f, 420);
28314
28778
  } catch {}
@@ -28447,6 +28911,50 @@ function hasOutboundDeliveredSince(chatId, sinceMs, threadId, minChars = 200) {
28447
28911
  return false;
28448
28912
  }
28449
28913
  }
28914
+ function hasOutboundWithText(chatId, text4, threadId, sinceMs) {
28915
+ const needle = normalizeDeliveryText(text4);
28916
+ if (needle.length === 0)
28917
+ return false;
28918
+ try {
28919
+ const params = [chatId];
28920
+ let sql = "SELECT text FROM messages WHERE chat_id = ? AND role = 'assistant'";
28921
+ if (threadId !== undefined) {
28922
+ if (threadId === null) {
28923
+ sql += " AND thread_id IS NULL";
28924
+ } else {
28925
+ sql += " AND thread_id = ?";
28926
+ params.push(threadId);
28927
+ }
28928
+ }
28929
+ if (sinceMs != null && Number.isFinite(sinceMs)) {
28930
+ sql += " AND ts >= ?";
28931
+ params.push(Math.floor(sinceMs / 1000));
28932
+ }
28933
+ sql += " ORDER BY ts DESC LIMIT 500";
28934
+ const rows = requireDb().prepare(sql).all(...params);
28935
+ for (const r of rows) {
28936
+ const hay = normalizeDeliveryText(r.text ?? "");
28937
+ if (hay.length === 0)
28938
+ continue;
28939
+ if (deliveryTextMatch(hay, needle))
28940
+ return true;
28941
+ }
28942
+ return false;
28943
+ } catch {
28944
+ return false;
28945
+ }
28946
+ }
28947
+ function normalizeDeliveryText(text4) {
28948
+ return text4.replace(/\s+/g, " ").trim();
28949
+ }
28950
+ function deliveryTextMatch(hay, needle) {
28951
+ if (hay === needle)
28952
+ return true;
28953
+ const shorter = Math.min(hay.length, needle.length);
28954
+ if (shorter < MIN_PREFIX_MATCH_CHARS)
28955
+ return false;
28956
+ return hay.startsWith(needle) || needle.startsWith(hay);
28957
+ }
28450
28958
  function query(opts) {
28451
28959
  const limit = Math.min(MAX_LIMIT, Math.max(1, opts.limit ?? DEFAULT_LIMIT));
28452
28960
  const params = [opts.chat_id];
@@ -28469,147 +28977,11 @@ function query(opts) {
28469
28977
  rows.reverse();
28470
28978
  return rows;
28471
28979
  }
28472
- var DatabaseClass = null, DEFAULT_LIMIT = 10, MAX_LIMIT = 50, db = null, dbPath = null;
28980
+ var DatabaseClass = null, DEFAULT_LIMIT = 10, MAX_LIMIT = 50, db = null, dbPath = null, MIN_PREFIX_MATCH_CHARS = 40;
28473
28981
  var init_history = __esm(() => {
28474
28982
  init_redact();
28475
28983
  });
28476
28984
 
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
28985
  // ../src/util/atomic.ts
28614
28986
  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
28987
  var TMP_OPEN_FLAGS;
@@ -31573,7 +31945,7 @@ var require_util = __commonJS((exports2) => {
31573
31945
  return path2;
31574
31946
  }
31575
31947
  exports2.normalize = normalize;
31576
- function join31(aRoot, aPath) {
31948
+ function join32(aRoot, aPath) {
31577
31949
  if (aRoot === "") {
31578
31950
  aRoot = ".";
31579
31951
  }
@@ -31605,7 +31977,7 @@ var require_util = __commonJS((exports2) => {
31605
31977
  }
31606
31978
  return joined;
31607
31979
  }
31608
- exports2.join = join31;
31980
+ exports2.join = join32;
31609
31981
  exports2.isAbsolute = function(aPath) {
31610
31982
  return aPath.charAt(0) === "/" || urlRegexp.test(aPath);
31611
31983
  };
@@ -31778,7 +32150,7 @@ var require_util = __commonJS((exports2) => {
31778
32150
  parsed.path = parsed.path.substring(0, index2 + 1);
31779
32151
  }
31780
32152
  }
31781
- sourceURL = join31(urlGenerate(parsed), sourceURL);
32153
+ sourceURL = join32(urlGenerate(parsed), sourceURL);
31782
32154
  }
31783
32155
  return normalize(sourceURL);
31784
32156
  }
@@ -34384,9 +34756,9 @@ function renderAuthLine(state4, agentName3, now = Date.now()) {
34384
34756
 
34385
34757
  // gateway/quota-cache.ts
34386
34758
  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";
34759
+ import { join as join42, dirname as dirname11 } from "path";
34388
34760
  function defaultCachePath() {
34389
- return process.env.SWITCHROOM_QUOTA_CACHE_PATH ?? join41(process.env.HOME ?? "/tmp", ".switchroom", "quota-cache.json");
34761
+ return process.env.SWITCHROOM_QUOTA_CACHE_PATH ?? join42(process.env.HOME ?? "/tmp", ".switchroom", "quota-cache.json");
34390
34762
  }
34391
34763
  function readQuotaCache(opts = {}) {
34392
34764
  const path2 = opts.path ?? defaultCachePath();
@@ -34433,7 +34805,7 @@ var init_quota_cache = __esm(() => {
34433
34805
 
34434
34806
  // gateway/boot-probes.ts
34435
34807
  import { readFileSync as readFileSync40, readdirSync as readdirSync8, existsSync as existsSync38 } from "fs";
34436
- import { join as join42 } from "path";
34808
+ import { join as join43 } from "path";
34437
34809
  import { execFile as execFileCb } from "child_process";
34438
34810
  import { promisify } from "util";
34439
34811
  async function withTimeout(label, p, timeoutMs = PROBE_TIMEOUT_MS) {
@@ -34475,8 +34847,8 @@ function mapPlan(billingType, hasExtra) {
34475
34847
  }
34476
34848
  async function probeAccount(agentDir) {
34477
34849
  return withTimeout("Account", (async () => {
34478
- const claudeDir = join42(agentDir, ".claude");
34479
- const claudeJsonPath = join42(claudeDir, ".claude.json");
34850
+ const claudeDir = join43(agentDir, ".claude");
34851
+ const claudeJsonPath = join43(claudeDir, ".claude.json");
34480
34852
  let cfg = {};
34481
34853
  try {
34482
34854
  const raw = readFileSync40(claudeJsonPath, "utf8");
@@ -34497,8 +34869,8 @@ async function probeAccount(agentDir) {
34497
34869
  let tokenStr = "";
34498
34870
  let status = "ok";
34499
34871
  for (const candidate of [
34500
- join42(claudeDir, ".oauth-token.meta.json"),
34501
- join42(claudeDir, "accounts", "default", ".oauth-token.meta.json")
34872
+ join43(claudeDir, ".oauth-token.meta.json"),
34873
+ join43(claudeDir, "accounts", "default", ".oauth-token.meta.json")
34502
34874
  ]) {
34503
34875
  if (existsSync38(candidate)) {
34504
34876
  try {
@@ -34884,9 +35256,9 @@ async function probeQuota(claudeConfigDir, _agentDir, fetchImpl = fetch, opts =
34884
35256
  let claudeDirForProbe = null;
34885
35257
  for (const candidate of [
34886
35258
  claudeConfigDir,
34887
- join42(claudeConfigDir, "accounts", "default")
35259
+ join43(claudeConfigDir, "accounts", "default")
34888
35260
  ]) {
34889
- if (existsSync38(join42(candidate, ".oauth-token"))) {
35261
+ if (existsSync38(join43(candidate, ".oauth-token"))) {
34890
35262
  claudeDirForProbe = candidate;
34891
35263
  break;
34892
35264
  }
@@ -35121,7 +35493,7 @@ async function probeSkills(agentDir, opts = {}) {
35121
35493
  return withTimeout("Skills", (async () => {
35122
35494
  const fs2 = opts.fs ?? realSkillsFs;
35123
35495
  const max = opts.maxNamesShown ?? 3;
35124
- const skillsDir = join42(agentDir, ".claude", "skills");
35496
+ const skillsDir = join43(agentDir, ".claude", "skills");
35125
35497
  if (!fs2.exists(skillsDir)) {
35126
35498
  return { status: "ok", label: "Skills", detail: "no skills dir" };
35127
35499
  }
@@ -35136,17 +35508,17 @@ async function probeSkills(agentDir, opts = {}) {
35136
35508
  }
35137
35509
  const dangling = [];
35138
35510
  for (const name of entries) {
35139
- const skillPath = join42(skillsDir, name);
35511
+ const skillPath = join43(skillsDir, name);
35140
35512
  if (!fs2.exists(skillPath)) {
35141
35513
  dangling.push(name);
35142
35514
  continue;
35143
35515
  }
35144
- const skillMd = join42(skillPath, "SKILL.md");
35516
+ const skillMd = join43(skillPath, "SKILL.md");
35145
35517
  if (!fs2.exists(skillMd) && !fs2.exists(skillPath + ".md")) {
35146
35518
  continue;
35147
35519
  }
35148
35520
  }
35149
- const overlayDir = opts.overlaySkillsDir ?? join42(agentDir, "skills.d");
35521
+ const overlayDir = opts.overlaySkillsDir ?? join43(agentDir, "skills.d");
35150
35522
  const overlaySlugs = new Set;
35151
35523
  if (fs2.exists(overlayDir)) {
35152
35524
  let overlayEntries = [];
@@ -35188,7 +35560,7 @@ function renderBucketedSkills(switchroom, agent) {
35188
35560
  }
35189
35561
  async function probeConnections(agentDir, opts = {}) {
35190
35562
  return withTimeout("Connections", (async () => {
35191
- const path2 = join42(agentDir, ".claude", "connection-health.json");
35563
+ const path2 = join43(agentDir, ".claude", "connection-health.json");
35192
35564
  const read = opts.readFileImpl ?? ((p) => readFileSync40(p, "utf8"));
35193
35565
  let issues = [];
35194
35566
  try {
@@ -35530,7 +35902,7 @@ __export(exports_boot_card, {
35530
35902
  renderBootCard: () => renderBootCard,
35531
35903
  renderAccountRows: () => renderAuthLine
35532
35904
  });
35533
- import { join as join43 } from "path";
35905
+ import { join as join44 } from "path";
35534
35906
  function resolvePersonaName(slug, loadConfig3) {
35535
35907
  try {
35536
35908
  const config = loadConfig3 ? loadConfig3() : loadConfig();
@@ -35621,7 +35993,7 @@ function renderBootCard(opts) {
35621
35993
  return stackCardLines(flatLines);
35622
35994
  }
35623
35995
  async function runAllProbes(opts) {
35624
- const claudeDir = join43(opts.agentDir, ".claude");
35996
+ const claudeDir = join44(opts.agentDir, ".claude");
35625
35997
  const probes = {};
35626
35998
  const slug = opts.agentSlug ?? opts.agentName;
35627
35999
  await Promise.allSettled([
@@ -37300,8 +37672,8 @@ import {
37300
37672
  unlinkSync as unlinkSync24,
37301
37673
  appendFileSync as appendFileSync6
37302
37674
  } from "fs";
37303
- import { homedir as homedir17 } from "os";
37304
- import { join as join54, extname, sep as sep3, basename as basename12 } from "path";
37675
+ import { homedir as homedir18 } from "os";
37676
+ import { join as join55, extname, sep as sep3, basename as basename13 } from "path";
37305
37677
 
37306
37678
  // plugin-logger.ts
37307
37679
  import { appendFileSync, mkdirSync, renameSync, statSync, existsSync } from "fs";
@@ -40211,16 +40583,34 @@ function clipNarrative(s) {
40211
40583
  return s.split(`
40212
40584
  `)[0].trim().slice(0, STATUS_LINE_MAX);
40213
40585
  }
40214
- function renderActivityHeader(emoji, label, description, elapsedMs, toolCount, state, model) {
40586
+ function renderActivityHeader(emoji, label, description, elapsedMs, toolCount, state, model, totalTokens) {
40215
40587
  const toolWord = toolCount === 1 ? "tool" : "tools";
40216
40588
  const elapsed = formatFeedElapsed(elapsedMs);
40217
40589
  const descPart = description.length > 0 ? ` \u00b7 _${escapeMarkdown(description)}_` : "";
40218
40590
  const line1 = `${emoji} **${escapeMarkdown(label)}**${descPart}`;
40591
+ const tokPart = tokenSegment(totalTokens);
40219
40592
  const modelLabel = formatModelLabel(model);
40220
40593
  const modelPart = modelLabel != null ? ` \u00b7 ${escapeMarkdown(modelLabel)}` : "";
40221
- const line2 = state === "running" ? `_${elapsed} \u00b7 ${toolCount} ${toolWord}${modelPart}_` : `_${state} \u00b7 ${toolCount} ${toolWord} \u00b7 ${elapsed}${modelPart}_`;
40594
+ const line2 = state === "running" ? `_${elapsed} \u00b7 ${toolCount} ${toolWord}${tokPart}${modelPart}_` : `_${state} \u00b7 ${toolCount} ${toolWord}${tokPart} \u00b7 ${elapsed}${modelPart}_`;
40222
40595
  return [line1, line2];
40223
40596
  }
40597
+ function formatTokenCount(n) {
40598
+ if (!Number.isFinite(n) || n <= 0)
40599
+ return "0";
40600
+ if (n < 1000)
40601
+ return String(Math.floor(n));
40602
+ if (n < 1e6) {
40603
+ const k = Number((n / 1000).toFixed(1));
40604
+ if (k < 1000)
40605
+ return `${k.toFixed(1)}k`;
40606
+ }
40607
+ return `${(n / 1e6).toFixed(1)}M`;
40608
+ }
40609
+ function tokenSegment(totalTokens) {
40610
+ if (totalTokens == null || totalTokens <= 0)
40611
+ return "";
40612
+ return ` \u00b7 ${formatTokenCount(totalTokens)} tok`;
40613
+ }
40224
40614
  function formatFeedElapsed(ms) {
40225
40615
  const s = Math.floor(ms / 1000);
40226
40616
  if (s < 60)
@@ -40257,7 +40647,7 @@ function renderStatusCard(opts) {
40257
40647
  const hasChildren = rawChildren.length > 0;
40258
40648
  const steps = rawSteps.map(escapeStepLine);
40259
40649
  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) : [];
40650
+ const headerLines = header != null ? renderActivityHeader(header.emoji, header.label, header.description ?? "", header.elapsedMs, header.toolCount, header.state, header.model, header.totalTokens) : [];
40261
40651
  const out = [...headerLines];
40262
40652
  if (hasChildren) {
40263
40653
  const shownParent = steps.slice(-STATUS_ROLLING_LINES);
@@ -40354,7 +40744,8 @@ function renderActivityFeed(lines, final = false, liveSuffix = "", stepCount, he
40354
40744
  elapsedMs: header.elapsedMs,
40355
40745
  toolCount: header.toolCount,
40356
40746
  state: header.state,
40357
- model: header.model
40747
+ model: header.model,
40748
+ totalTokens: header.totalTokens
40358
40749
  } : undefined,
40359
40750
  steps: lines,
40360
40751
  final,
@@ -40373,7 +40764,8 @@ function renderActivityFeedWithNested(lines, childLines, final = false, liveSuff
40373
40764
  elapsedMs: header.elapsedMs,
40374
40765
  toolCount: header.toolCount,
40375
40766
  state: header.state,
40376
- model: header.model
40767
+ model: header.model,
40768
+ totalTokens: header.totalTokens
40377
40769
  } : undefined,
40378
40770
  steps: lines,
40379
40771
  childSteps: children,
@@ -40398,9 +40790,10 @@ function renderCombinedWorkerFeed(rows, opts) {
40398
40790
  const rowHeader = (r) => {
40399
40791
  const desc = escapeMarkdown(truncate(stripMarkdown(r.description).replace(/\s+/g, " ").trim() || "background task", COMBINED_ROW_DESC_MAX));
40400
40792
  const toolWord = r.toolCount === 1 ? "tool" : "tools";
40793
+ const tokPart = tokenSegment(r.totalTokens);
40401
40794
  const modelLabel = formatModelLabel(r.model);
40402
40795
  const modelPart = modelLabel != null ? ` \u00b7 ${escapeMarkdown(modelLabel)}` : "";
40403
- return `**${desc}** _\u00b7 ${formatFeedElapsed(r.elapsedMs)} \u00b7 ${r.toolCount} ${toolWord}${modelPart}_`;
40796
+ return `**${desc}** _\u00b7 ${formatFeedElapsed(r.elapsedMs)} \u00b7 ${r.toolCount} ${toolWord}${tokPart}${modelPart}_`;
40404
40797
  };
40405
40798
  const rowHistory = (r) => {
40406
40799
  const src = r.historyLines != null && r.historyLines.length > 0 ? r.historyLines : [r.currentStep];
@@ -40511,7 +40904,8 @@ function renderWorkerActivity(v, liveSuffix = "") {
40511
40904
  elapsedMs: v.elapsedMs,
40512
40905
  toolCount: v.toolCount,
40513
40906
  state: v.state,
40514
- model: v.model
40907
+ model: v.model,
40908
+ totalTokens: v.totalTokens
40515
40909
  };
40516
40910
  let result;
40517
40911
  if (finished && v.state !== "incomplete") {
@@ -40536,6 +40930,9 @@ _starting\u2026_`;
40536
40930
  return card;
40537
40931
  }
40538
40932
  var COOLDOWN_JITTER_MS = 500;
40933
+ var WORKER_CARD_SUPERSEDED_BODY = `\uD83D\uDEE0 **Worker** \u00b7 _continued_
40934
+
40935
+ _Live progress moved to a fresh card to stay pinned._`;
40539
40936
  function extractRetryAfterSecs(err) {
40540
40937
  if (err == null || typeof err !== "object")
40541
40938
  return null;
@@ -40570,6 +40967,7 @@ function createWorkerActivityFeed(opts) {
40570
40967
  const maxRows = Math.max(1, Math.floor(opts.maxRows ?? 8));
40571
40968
  const staleWorkerTtlMs = Math.max(1, Math.floor(opts.staleWorkerTtlMs ?? 50 * 60000));
40572
40969
  const absoluteRowLifetimeCapMs = Math.max(1, Math.floor(opts.absoluteRowLifetimeCapMs ?? 6 * 60 * 60000));
40970
+ const groupMessageLifetimeCapMs = Math.max(1, Math.floor(opts.groupMessageLifetimeCapMs ?? 60 * 60000));
40573
40971
  const reconcilePinFn = opts.reconcilePin ?? (() => {});
40574
40972
  const setIntervalFn = opts.setInterval ?? ((cb, ms) => {
40575
40973
  const t = setInterval(cb, ms);
@@ -40670,6 +41068,7 @@ function createWorkerActivityFeed(opts) {
40670
41068
  description: v.description,
40671
41069
  elapsedMs: elapsedFor(r),
40672
41070
  toolCount: v.toolCount,
41071
+ totalTokens: v.totalTokens,
40673
41072
  currentStep,
40674
41073
  historyLines: r.narrative.length > 0 ? [...r.narrative] : undefined,
40675
41074
  model: v.model
@@ -40747,6 +41146,7 @@ function createWorkerActivityFeed(opts) {
40747
41146
  return;
40748
41147
  }
40749
41148
  g.messageId = sent.message_id;
41149
+ g.messageCreatedAtMs = now;
40750
41150
  g.lastBody = body;
40751
41151
  g.lastEditAt = now;
40752
41152
  g.terminalPainted = false;
@@ -40777,6 +41177,7 @@ function createWorkerActivityFeed(opts) {
40777
41177
  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
41178
  } else {
40779
41179
  log(`worker-feed: edit feed=${g.feedKey} chat=${g.chatId} ` + `thread=${g.threadId ?? "-"} msgId=${g.messageId} workers=${g.workers.size} bytes=${body.length}`);
41180
+ syncPin(g);
40780
41181
  }
40781
41182
  if (isTerminal)
40782
41183
  clearStaged();
@@ -40795,6 +41196,7 @@ function createWorkerActivityFeed(opts) {
40795
41196
  }
40796
41197
  if (outcome === "gone") {
40797
41198
  g.messageId = null;
41199
+ g.messageCreatedAtMs = 0;
40798
41200
  g.lastBody = null;
40799
41201
  if (isTerminal)
40800
41202
  clearStaged();
@@ -40838,6 +41240,7 @@ function createWorkerActivityFeed(opts) {
40838
41240
  description: lv?.description ?? "background task",
40839
41241
  lastTool: null,
40840
41242
  toolCount: lv?.toolCount ?? 0,
41243
+ totalTokens: lv?.totalTokens,
40841
41244
  latestSummary: "",
40842
41245
  elapsedMs: liveElapsed(row, nowFn()),
40843
41246
  state: "incomplete",
@@ -40897,6 +41300,16 @@ function createWorkerActivityFeed(opts) {
40897
41300
  const running = runningRows(g);
40898
41301
  if (running.length === 0)
40899
41302
  continue;
41303
+ if (g.messageId != null && now - g.messageCreatedAtMs >= groupMessageLifetimeCapMs) {
41304
+ const age = Math.floor((now - g.messageCreatedAtMs) / 1000);
41305
+ const retiredId = g.messageId;
41306
+ 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`);
41307
+ g.messageId = null;
41308
+ g.messageCreatedAtMs = 0;
41309
+ g.lastBody = null;
41310
+ syncPin(g);
41311
+ opts.bot.editMessageText(g.chatId, retiredId, WORKER_CARD_SUPERSEDED_BODY, sendOptsFor(g)).catch(() => {});
41312
+ }
40900
41313
  if (g.messageId == null) {
40901
41314
  const maxElapsed = Math.max(0, ...running.map((r) => liveElapsed(r, now)));
40902
41315
  if (maxElapsed < firstPaintMin)
@@ -40951,6 +41364,7 @@ function createWorkerActivityFeed(opts) {
40951
41364
  chatId,
40952
41365
  threadId,
40953
41366
  messageId: null,
41367
+ messageCreatedAtMs: 0,
40954
41368
  lastBody: null,
40955
41369
  lastEditAt: 0,
40956
41370
  cooldownUntil: 0,
@@ -40963,6 +41377,7 @@ function createWorkerActivityFeed(opts) {
40963
41377
  }
40964
41378
  if (!g.workers.has(agentId) && g.terminalPainted && !hasLiveWorker(g)) {
40965
41379
  g.messageId = null;
41380
+ g.messageCreatedAtMs = 0;
40966
41381
  g.lastBody = null;
40967
41382
  g.pendingFinalize.clear();
40968
41383
  g.terminalPainted = false;
@@ -41889,8 +42304,18 @@ function buildVaultGrantDeniedInbound(opts) {
41889
42304
  }
41890
42305
  };
41891
42306
  }
42307
+ var MAX_GRANT_REASON_CHARS = 300;
42308
+ function normalizeGrantReason(raw) {
42309
+ if (raw == null)
42310
+ return "";
42311
+ const collapsed = raw.replace(/\s+/g, " ").trim();
42312
+ if (collapsed.length === 0)
42313
+ return "";
42314
+ return collapsed.length > MAX_GRANT_REASON_CHARS ? collapsed.slice(0, MAX_GRANT_REASON_CHARS - 1) + "\u2026" : collapsed;
42315
+ }
41892
42316
  function buildVaultGrantApprovedCardText(opts) {
41893
- return `\u2705 Granted **${opts.agentEscaped}** ${opts.scope} access to ` + `\`${opts.key}\` for ${opts.days}d. ` + `(grant \`${opts.grantId}\`)` + (opts.footer ?? "");
42317
+ const reasonClause = opts.reasonEscaped != null && opts.reasonEscaped.length > 0 ? ` _Reason: ${opts.reasonEscaped}_` : "";
42318
+ return `\u2705 Granted **${opts.agentEscaped}** ${opts.scope} access to ` + `\`${opts.key}\` for ${opts.days}d. ` + `(grant \`${opts.grantId}\`)` + reasonClause + (opts.footer ?? "");
41894
42319
  }
41895
42320
  function buildVaultSaveCompletedInbound(opts) {
41896
42321
  const ts = opts.nowMs ?? Date.now();
@@ -43163,6 +43588,7 @@ function createCallbackQueryHandlers(deps) {
43163
43588
  pendingCardStore.remove(stageId);
43164
43589
  if (pending.card_message_id != null) {
43165
43590
  const days = Math.round(pending.ttl_seconds / 86400);
43591
+ const reasonNormalized = normalizeGrantReason(pending.reason);
43166
43592
  const footer = getVaultApprovalAuthMode() === "telegram-id" ? `
43167
43593
  _Approver verified by Telegram identity \u2014 broker auto-unlocked at startup._` : "";
43168
43594
  await ctx.api.editMessageText(pending.chat_id, pending.card_message_id, richMessage(buildVaultGrantApprovedCardText({
@@ -43171,6 +43597,7 @@ _Approver verified by Telegram identity \u2014 broker auto-unlocked at startup._
43171
43597
  key: pending.key,
43172
43598
  days,
43173
43599
  grantId: id,
43600
+ reasonEscaped: reasonNormalized.length > 0 ? escapeHtmlForTg2(reasonNormalized) : undefined,
43174
43601
  footer
43175
43602
  })), { reply_markup: { inline_keyboard: [] } }).catch(() => {});
43176
43603
  }
@@ -45035,16 +45462,34 @@ function clipNarrative2(s) {
45035
45462
  return s.split(`
45036
45463
  `)[0].trim().slice(0, STATUS_LINE_MAX);
45037
45464
  }
45038
- function renderActivityHeader2(emoji, label, description, elapsedMs, toolCount, state, model) {
45465
+ function renderActivityHeader2(emoji, label, description, elapsedMs, toolCount, state, model, totalTokens) {
45039
45466
  const toolWord = toolCount === 1 ? "tool" : "tools";
45040
45467
  const elapsed = formatFeedElapsed2(elapsedMs);
45041
45468
  const descPart = description.length > 0 ? ` \u00b7 _${escapeMarkdown(description)}_` : "";
45042
45469
  const line1 = `${emoji} **${escapeMarkdown(label)}**${descPart}`;
45470
+ const tokPart = tokenSegment2(totalTokens);
45043
45471
  const modelLabel = formatModelLabel(model);
45044
45472
  const modelPart = modelLabel != null ? ` \u00b7 ${escapeMarkdown(modelLabel)}` : "";
45045
- const line2 = state === "running" ? `_${elapsed} \u00b7 ${toolCount} ${toolWord}${modelPart}_` : `_${state} \u00b7 ${toolCount} ${toolWord} \u00b7 ${elapsed}${modelPart}_`;
45473
+ const line2 = state === "running" ? `_${elapsed} \u00b7 ${toolCount} ${toolWord}${tokPart}${modelPart}_` : `_${state} \u00b7 ${toolCount} ${toolWord}${tokPart} \u00b7 ${elapsed}${modelPart}_`;
45046
45474
  return [line1, line2];
45047
45475
  }
45476
+ function formatTokenCount2(n) {
45477
+ if (!Number.isFinite(n) || n <= 0)
45478
+ return "0";
45479
+ if (n < 1000)
45480
+ return String(Math.floor(n));
45481
+ if (n < 1e6) {
45482
+ const k = Number((n / 1000).toFixed(1));
45483
+ if (k < 1000)
45484
+ return `${k.toFixed(1)}k`;
45485
+ }
45486
+ return `${(n / 1e6).toFixed(1)}M`;
45487
+ }
45488
+ function tokenSegment2(totalTokens) {
45489
+ if (totalTokens == null || totalTokens <= 0)
45490
+ return "";
45491
+ return ` \u00b7 ${formatTokenCount2(totalTokens)} tok`;
45492
+ }
45048
45493
  function formatFeedElapsed2(ms) {
45049
45494
  const s = Math.floor(ms / 1000);
45050
45495
  if (s < 60)
@@ -45081,7 +45526,7 @@ function renderStatusCard2(opts) {
45081
45526
  const hasChildren = rawChildren.length > 0;
45082
45527
  const steps = rawSteps.map(escapeStepLine2);
45083
45528
  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) : [];
45529
+ const headerLines = header != null ? renderActivityHeader2(header.emoji, header.label, header.description ?? "", header.elapsedMs, header.toolCount, header.state, header.model, header.totalTokens) : [];
45085
45530
  const out = [...headerLines];
45086
45531
  if (hasChildren) {
45087
45532
  const shownParent = steps.slice(-STATUS_ROLLING_LINES);
@@ -45178,7 +45623,8 @@ function renderActivityFeed2(lines, final = false, liveSuffix = "", stepCount, h
45178
45623
  elapsedMs: header.elapsedMs,
45179
45624
  toolCount: header.toolCount,
45180
45625
  state: header.state,
45181
- model: header.model
45626
+ model: header.model,
45627
+ totalTokens: header.totalTokens
45182
45628
  } : undefined,
45183
45629
  steps: lines,
45184
45630
  final,
@@ -45197,7 +45643,8 @@ function renderActivityFeedWithNested2(lines, childLines, final = false, liveSuf
45197
45643
  elapsedMs: header.elapsedMs,
45198
45644
  toolCount: header.toolCount,
45199
45645
  state: header.state,
45200
- model: header.model
45646
+ model: header.model,
45647
+ totalTokens: header.totalTokens
45201
45648
  } : undefined,
45202
45649
  steps: lines,
45203
45650
  childSteps: children,
@@ -62872,6 +63319,597 @@ function resolveAnswerLaneConfig(input) {
62872
63319
  };
62873
63320
  }
62874
63321
 
63322
+ // session-tail.ts
63323
+ import { homedir as homedir7 } from "os";
63324
+ import { basename as basename5, join as join21 } from "path";
63325
+
63326
+ // operator-events.ts
63327
+ init_format();
63328
+
63329
+ // raw-error-scrub.ts
63330
+ function stripRawErrorBytes(raw) {
63331
+ if (typeof raw !== "string" || raw.length === 0)
63332
+ return "";
63333
+ let s = raw;
63334
+ s = s.replace(/API Error:?\s*\d*\s*/gi, " ");
63335
+ s = s.replace(/\bb'[^']*'/g, " ");
63336
+ s = s.replace(/\bb"[^"]*"/g, " ");
63337
+ s = s.replace(/[\u00b7\-\s]*\{[\s\S]*?["']type["']\s*:\s*["']error["'][\s\S]*$/i, " ");
63338
+ s = s.replace(/^\s*\{[\s\S]*\}\s*$/g, " ");
63339
+ s = s.replace(/\s+/g, " ").replace(/[\u00b7:\-\s]+$/g, "").replace(/^[\u00b7:\-\s]+/g, "").trim();
63340
+ return s;
63341
+ }
63342
+ function extractRequestId(raw) {
63343
+ if (typeof raw !== "string" || raw.length === 0)
63344
+ return;
63345
+ const m = raw.match(/["']request[_-]?id["']\s*:\s*["']([A-Za-z0-9._-]+)["']/i) ?? raw.match(/\brequest[_-]?id[=:]\s*([A-Za-z0-9._-]+)/i);
63346
+ return m ? m[1] : undefined;
63347
+ }
63348
+
63349
+ // operator-events.ts
63350
+ function classifyClaudeError(raw) {
63351
+ try {
63352
+ return classifyInner(raw);
63353
+ } catch {
63354
+ return "unknown-4xx";
63355
+ }
63356
+ }
63357
+ function classifyInner(raw) {
63358
+ if (raw == null)
63359
+ return "unknown-4xx";
63360
+ const obj = typeof raw === "object" ? raw : {};
63361
+ const errorType = extractString(obj, "error_type") ?? extractString(obj, "type") ?? extractString(getNestedObj(obj, "error"), "type") ?? "";
63362
+ const errorCode = extractString(obj, "code") ?? extractString(getNestedObj(obj, "error"), "code") ?? "";
63363
+ const message = extractString(obj, "message") ?? extractString(getNestedObj(obj, "error"), "message") ?? (typeof raw === "string" ? raw : "") ?? "";
63364
+ const status = extractNumber(obj, "status") ?? extractNumber(obj, "statusCode") ?? extractNumber(obj, "status_code") ?? null;
63365
+ const sdkCode = extractString(obj, "error_code") ?? "";
63366
+ if (errorType === "authentication_error" || errorCode === "authentication_error" || sdkCode === "authentication_error" || message.toLowerCase().includes("authentication_error")) {
63367
+ const msg = message.toLowerCase();
63368
+ if (msg.includes("expired") || msg.includes("refresh")) {
63369
+ return "credentials-expired";
63370
+ }
63371
+ return "credentials-invalid";
63372
+ }
63373
+ 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")) {
63374
+ return "credentials-invalid";
63375
+ }
63376
+ 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")) {
63377
+ return "credit-exhausted";
63378
+ }
63379
+ if (errorType === "rate_limit_error" || errorCode === "rate_limit_error" || sdkCode === "rate_limit_error" || message.toLowerCase().includes("rate_limit_error") || message.toLowerCase().includes("rate limit")) {
63380
+ return "rate-limited";
63381
+ }
63382
+ if (errorType === "overloaded_error" || errorCode === "overloaded_error" || sdkCode === "overloaded_error" || message.toLowerCase().includes("overloaded_error") || message.toLowerCase().includes("overloaded")) {
63383
+ return "rate-limited";
63384
+ }
63385
+ if (errorType === "agent-crashed" || errorCode === "agent-crashed") {
63386
+ return "agent-crashed";
63387
+ }
63388
+ if (errorType === "agent-restarted-unexpectedly" || errorCode === "agent-restarted-unexpectedly") {
63389
+ return "agent-restarted-unexpectedly";
63390
+ }
63391
+ if (status != null) {
63392
+ if (status >= 400 && status < 500)
63393
+ return "unknown-4xx";
63394
+ if (status >= 500 && status < 600)
63395
+ return "unknown-5xx";
63396
+ }
63397
+ return "unknown-4xx";
63398
+ }
63399
+ function extractString(obj, key) {
63400
+ const v = obj[key];
63401
+ return typeof v === "string" && v.length > 0 ? v : null;
63402
+ }
63403
+ function extractNumber(obj, key) {
63404
+ const v = obj[key];
63405
+ return typeof v === "number" ? v : null;
63406
+ }
63407
+ function getNestedObj(obj, key) {
63408
+ const v = obj[key];
63409
+ return typeof v === "object" && v != null ? v : {};
63410
+ }
63411
+ var DEFAULT_OPERATOR_EVENT_COOLDOWN_MS = 5 * 60000;
63412
+ var cooldownMap = new Map;
63413
+
63414
+ // model-unavailable.ts
63415
+ init_quota_check();
63416
+ init_card_format();
63417
+ var transientUpstreamSignals = [
63418
+ "not your usage limit",
63419
+ "not your account",
63420
+ "not your account's",
63421
+ "temporarily limiting requests",
63422
+ "temporarily rate",
63423
+ "server is temporarily",
63424
+ "would exceed your account\u2019s rate limit",
63425
+ "would exceed your account's rate limit"
63426
+ ];
63427
+ function isTransientUpstreamSignal(text4) {
63428
+ if (typeof text4 !== "string" || text4.length === 0)
63429
+ return false;
63430
+ const sample = text4.length > 16384 ? text4.slice(0, 16384) : text4;
63431
+ const lower = sample.toLowerCase();
63432
+ return transientUpstreamSignals.some((s) => lower.includes(s));
63433
+ }
63434
+ var litellmProxyLocal429Signals = [
63435
+ "deployment over user-defined ratelimit",
63436
+ "model rate limit exceeded. tpm limit",
63437
+ "model rate limit exceeded. rpm limit",
63438
+ "deployment over defined rpm limit",
63439
+ "no deployments available for selected model",
63440
+ "litellm rate limit handler",
63441
+ "crossed tpm / rpm",
63442
+ "max parallel request limit reached"
63443
+ ];
63444
+ var litellmV3LimiterSignalPair = ["rate limit exceeded for ", "limit type:"];
63445
+ function isLitellmProxyLocal429(text4) {
63446
+ if (typeof text4 !== "string" || text4.length === 0)
63447
+ return false;
63448
+ const sample = text4.length > 16384 ? text4.slice(0, 16384) : text4;
63449
+ const lower = sample.toLowerCase();
63450
+ if (litellmProxyLocal429Signals.some((s) => lower.includes(s)))
63451
+ return true;
63452
+ return litellmV3LimiterSignalPair.every((s) => lower.includes(s));
63453
+ }
63454
+ function parseLitellmLimitDetail(text4, parseTimeNow = new Date) {
63455
+ const empty2 = { limitType: null, limit: null, currentUsage: null, resetAtMs: null };
63456
+ if (typeof text4 !== "string" || text4.length === 0)
63457
+ return empty2;
63458
+ const sample = text4.length > 16384 ? text4.slice(0, 16384) : text4;
63459
+ const lower = sample.toLowerCase();
63460
+ let limitType = null;
63461
+ let limit = null;
63462
+ const eqLimit = lower.match(/\b([tr]pm)[ _]limit[=:]\s*(\d+)/);
63463
+ if (eqLimit) {
63464
+ limitType = eqLimit[1];
63465
+ limit = Number(eqLimit[2]);
63466
+ }
63467
+ if (limitType == null) {
63468
+ const v3Type = lower.match(/limit type:\s*(tokens|requests|max_parallel_requests)/);
63469
+ if (v3Type)
63470
+ limitType = v3Type[1];
63471
+ }
63472
+ if (limit == null) {
63473
+ const v3Limit = lower.match(/current limit:\s*(\d+)/);
63474
+ if (v3Limit)
63475
+ limit = Number(v3Limit[1]);
63476
+ }
63477
+ let currentUsage = null;
63478
+ const usage = lower.match(/current usage=(\d+)/);
63479
+ if (usage)
63480
+ currentUsage = Number(usage[1]);
63481
+ let resetAtMs = null;
63482
+ const resetsAt = sample.match(/limit resets at:\s*(\d{4}-\d{2}-\d{2}) (\d{2}:\d{2}:\d{2}) UTC/i);
63483
+ if (resetsAt) {
63484
+ const d = new Date(`${resetsAt[1]}T${resetsAt[2]}Z`);
63485
+ if (!Number.isNaN(d.getTime()))
63486
+ resetAtMs = d.getTime();
63487
+ }
63488
+ if (resetAtMs == null) {
63489
+ const tryAgain = lower.match(/try again in\s+(\d+(?:\.\d+)?)\s*seconds/);
63490
+ if (tryAgain) {
63491
+ const secs = Number(tryAgain[1]);
63492
+ if (Number.isFinite(secs) && secs > 0 && secs < 7 * 24 * 3600) {
63493
+ resetAtMs = parseTimeNow.getTime() + Math.round(secs * 1000);
63494
+ }
63495
+ }
63496
+ }
63497
+ return {
63498
+ limitType,
63499
+ limit: limit != null && Number.isFinite(limit) ? limit : null,
63500
+ currentUsage: currentUsage != null && Number.isFinite(currentUsage) ? currentUsage : null,
63501
+ resetAtMs
63502
+ };
63503
+ }
63504
+ function detectModelUnavailable(stderr) {
63505
+ if (typeof stderr !== "string" || stderr.length === 0)
63506
+ return null;
63507
+ const sample = stderr.length > 16384 ? stderr.slice(0, 16384) : stderr;
63508
+ const lower = sample.toLowerCase();
63509
+ if (transientUpstreamSignals.some((s) => lower.includes(s))) {
63510
+ const resetAt = parseResetTime(sample);
63511
+ return resetAt !== undefined ? { kind: "overload", resetAt, raw: stderr } : { kind: "overload", raw: stderr };
63512
+ }
63513
+ if (isLitellmProxyLocal429(sample)) {
63514
+ const resetAt = parseResetTime(sample);
63515
+ return resetAt !== undefined ? { kind: "overload", resetAt, raw: stderr } : { kind: "overload", raw: stderr };
63516
+ }
63517
+ const quotaSignals = [
63518
+ "out of extra usage",
63519
+ "extra usage",
63520
+ "credit_balance_too_low",
63521
+ "credit balance too low",
63522
+ "usage limit",
63523
+ "usage_limit",
63524
+ "quota exhausted",
63525
+ "quota_exhausted",
63526
+ "plan limit",
63527
+ "subscription limit",
63528
+ "hit your limit",
63529
+ "hit the limit",
63530
+ "session limit",
63531
+ "session cap"
63532
+ ];
63533
+ if (quotaSignals.some((s) => lower.includes(s))) {
63534
+ const resetAt = parseResetTime(sample);
63535
+ return resetAt !== undefined ? { kind: "quota_exhausted", resetAt, raw: stderr } : { kind: "quota_exhausted", raw: stderr };
63536
+ }
63537
+ const overloadSignals = [
63538
+ "overloaded_error",
63539
+ "overloaded",
63540
+ "rate_limit_error",
63541
+ "rate limit",
63542
+ "rate-limited",
63543
+ "http 429",
63544
+ '"status":429',
63545
+ "status: 429",
63546
+ " 429 ",
63547
+ "503 service",
63548
+ "service unavailable",
63549
+ '"status":529',
63550
+ "http 529",
63551
+ " 529 "
63552
+ ];
63553
+ if (overloadSignals.some((s) => lower.includes(s))) {
63554
+ const resetAt = parseResetTime(sample);
63555
+ return resetAt !== undefined ? { kind: "overload", resetAt, raw: stderr } : { kind: "overload", raw: stderr };
63556
+ }
63557
+ const networkSignals = [
63558
+ "econnrefused",
63559
+ "econnreset",
63560
+ "etimedout",
63561
+ "enotfound",
63562
+ "eai_again",
63563
+ "fetch failed",
63564
+ "network error",
63565
+ "socket hang up",
63566
+ "request timed out",
63567
+ "connection refused",
63568
+ "getaddrinfo"
63569
+ ];
63570
+ if (networkSignals.some((s) => lower.includes(s))) {
63571
+ return { kind: "network", raw: stderr };
63572
+ }
63573
+ return null;
63574
+ }
63575
+ function parseResetTime(text4, parseTimeNow = new Date) {
63576
+ const lower = text4.toLowerCase();
63577
+ const retryAfter = lower.match(/retry[\s-]*after[:\s]+(\d+)\s*(seconds?|s\b|minutes?|m\b|hours?|h\b)?/);
63578
+ if (retryAfter) {
63579
+ const n = Number(retryAfter[1]);
63580
+ if (Number.isFinite(n) && n > 0 && n < 7 * 24 * 3600) {
63581
+ const unit = (retryAfter[2] ?? "seconds").toLowerCase();
63582
+ const ms = unit.startsWith("h") ? n * 3600000 : unit.startsWith("m") ? n * 60000 : n * 1000;
63583
+ return new Date(parseTimeNow.getTime() + ms);
63584
+ }
63585
+ }
63586
+ const relReset = lower.match(/resets?\s+in\s+([0-9hms\s]+)/);
63587
+ if (relReset) {
63588
+ const ms = parseRelativeDuration(relReset[1]);
63589
+ if (ms != null)
63590
+ return new Date(parseTimeNow.getTime() + ms);
63591
+ }
63592
+ 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/);
63593
+ if (iso) {
63594
+ const d = new Date(iso[0]);
63595
+ if (!Number.isNaN(d.getTime()))
63596
+ return d;
63597
+ }
63598
+ 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)?))?)/);
63599
+ if (calReset) {
63600
+ const candidate = `${calReset[1]} ${parseTimeNow.getUTCFullYear()}`;
63601
+ const d = new Date(candidate);
63602
+ if (!Number.isNaN(d.getTime()))
63603
+ return d;
63604
+ }
63605
+ 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);
63606
+ if (timeOnly) {
63607
+ const d = resolveNextWallClock(Number(timeOnly[1]), timeOnly[2] ? Number(timeOnly[2]) : 0, timeOnly[3]?.toLowerCase(), timeOnly[4]?.trim(), parseTimeNow);
63608
+ if (d != null)
63609
+ return d;
63610
+ }
63611
+ return;
63612
+ }
63613
+ function resolveNextWallClock(hour12or24, minute, ampm, tz, nowDate) {
63614
+ let hour = hour12or24;
63615
+ if (ampm === "pm" && hour < 12)
63616
+ hour += 12;
63617
+ if (ampm === "am" && hour === 12)
63618
+ hour = 0;
63619
+ if (!Number.isFinite(hour) || hour > 23 || hour < 0)
63620
+ return;
63621
+ if (!Number.isFinite(minute) || minute > 59 || minute < 0)
63622
+ return;
63623
+ const nowMs2 = nowDate.getTime();
63624
+ const base = new Date(nowMs2);
63625
+ for (let dayOffset = 0;dayOffset <= 2; dayOffset++) {
63626
+ const dateParts = tzDateParts(new Date(nowMs2 + dayOffset * 86400000), tz);
63627
+ if (dateParts == null)
63628
+ return;
63629
+ const epoch = wallClockToEpoch(dateParts.year, dateParts.month, dateParts.day, hour, minute, tz);
63630
+ if (epoch != null && epoch > nowMs2)
63631
+ return new Date(epoch);
63632
+ }
63633
+ return;
63634
+ }
63635
+ function tzDateParts(d, tz) {
63636
+ if (!tz) {
63637
+ return { year: d.getUTCFullYear(), month: d.getUTCMonth(), day: d.getUTCDate() };
63638
+ }
63639
+ try {
63640
+ const fmt = new Intl.DateTimeFormat("en-US", {
63641
+ timeZone: tz,
63642
+ year: "numeric",
63643
+ month: "2-digit",
63644
+ day: "2-digit"
63645
+ });
63646
+ const parts = Object.fromEntries(fmt.formatToParts(d).filter((p) => p.type !== "literal").map((p) => [p.type, p.value]));
63647
+ return {
63648
+ year: Number(parts.year),
63649
+ month: Number(parts.month) - 1,
63650
+ day: Number(parts.day)
63651
+ };
63652
+ } catch {
63653
+ return null;
63654
+ }
63655
+ }
63656
+ function wallClockToEpoch(year, month, day, hour, minute, tz) {
63657
+ const asUtc = Date.UTC(year, month, day, hour, minute, 0);
63658
+ if (!tz)
63659
+ return asUtc;
63660
+ try {
63661
+ const fmt = new Intl.DateTimeFormat("en-US", {
63662
+ timeZone: tz,
63663
+ year: "numeric",
63664
+ month: "2-digit",
63665
+ day: "2-digit",
63666
+ hour: "2-digit",
63667
+ minute: "2-digit",
63668
+ second: "2-digit",
63669
+ hour12: false
63670
+ });
63671
+ const parts = Object.fromEntries(fmt.formatToParts(new Date(asUtc)).filter((p) => p.type !== "literal").map((p) => [p.type, p.value]));
63672
+ const shown = Date.UTC(Number(parts.year), Number(parts.month) - 1, Number(parts.day), Number(parts.hour) % 24, Number(parts.minute), Number(parts.second));
63673
+ const offset = shown - asUtc;
63674
+ return asUtc - offset;
63675
+ } catch {
63676
+ return null;
63677
+ }
63678
+ }
63679
+ function parseRelativeDuration(s) {
63680
+ let total = 0;
63681
+ let matched = false;
63682
+ const re = /(\d+)\s*(h|hours?|m|minutes?|s|seconds?)/g;
63683
+ let m;
63684
+ while ((m = re.exec(s)) != null) {
63685
+ matched = true;
63686
+ const n = Number(m[1]);
63687
+ const unit = m[2].toLowerCase();
63688
+ if (unit.startsWith("h"))
63689
+ total += n * 3600000;
63690
+ else if (unit.startsWith("m"))
63691
+ total += n * 60000;
63692
+ else
63693
+ total += n * 1000;
63694
+ }
63695
+ return matched && total > 0 ? total : null;
63696
+ }
63697
+
63698
+ // session-tail.ts
63699
+ function sanitizeCwdToProjectName(cwd) {
63700
+ return cwd.replace(/[^a-zA-Z0-9]/g, "-");
63701
+ }
63702
+ function getProjectsDirForCwd(cwd = process.cwd(), claudeHome = process.env.CLAUDE_CONFIG_DIR ?? join21(homedir7(), ".claude")) {
63703
+ return join21(claudeHome, "projects", sanitizeCwdToProjectName(cwd));
63704
+ }
63705
+ function parseChannelMeta(content3) {
63706
+ const grab = (key) => {
63707
+ const m = content3.match(new RegExp(`(?:^|[\\s"'])${key}="([^"]+)"`));
63708
+ return m ? m[1] : null;
63709
+ };
63710
+ return {
63711
+ chatId: grab("chat_id"),
63712
+ messageId: grab("message_id"),
63713
+ threadId: grab("message_thread_id")
63714
+ };
63715
+ }
63716
+ var MAX_JSONL_LINE_BYTES = 2 * 1024 * 1024;
63717
+ var MAX_ERROR_TEXT_CHARS = 500;
63718
+ function extractToolResultErrorText(content3) {
63719
+ if (typeof content3 === "string") {
63720
+ return content3.slice(0, MAX_ERROR_TEXT_CHARS);
63721
+ }
63722
+ if (Array.isArray(content3)) {
63723
+ const parts = [];
63724
+ for (const block of content3) {
63725
+ if (typeof block === "object" && block != null) {
63726
+ const b = block;
63727
+ if (b.type === "text" && typeof b.text === "string") {
63728
+ parts.push(b.text);
63729
+ }
63730
+ }
63731
+ }
63732
+ return parts.join(`
63733
+ `).slice(0, MAX_ERROR_TEXT_CHARS);
63734
+ }
63735
+ return "";
63736
+ }
63737
+ function projectAssistantTextBlocks(content3, make) {
63738
+ const out = new Map;
63739
+ let lastToolUseIdx = -1;
63740
+ content3.forEach((c, i) => {
63741
+ if (c.type === "tool_use")
63742
+ lastToolUseIdx = i;
63743
+ });
63744
+ content3.forEach((c, i) => {
63745
+ if (c.type !== "text")
63746
+ return;
63747
+ const text4 = c.text ?? "";
63748
+ if (text4.trim().length === 0)
63749
+ return;
63750
+ out.set(i, make(text4, i, i > lastToolUseIdx));
63751
+ });
63752
+ return out;
63753
+ }
63754
+ function sumUsageTokens(usage) {
63755
+ if (usage == null || typeof usage !== "object")
63756
+ return 0;
63757
+ const u = usage;
63758
+ const n = (v) => typeof v === "number" && Number.isFinite(v) ? v : 0;
63759
+ return n(u.input_tokens) + n(u.output_tokens) + n(u.cache_creation_input_tokens);
63760
+ }
63761
+ function projectTrailingAnswerFromTranscript(transcriptText) {
63762
+ const buf = [];
63763
+ let lastMeaningful = null;
63764
+ for (const rawLine of transcriptText.split(`
63765
+ `)) {
63766
+ const line = rawLine.trim();
63767
+ if (!line)
63768
+ continue;
63769
+ if (isRealUserTurnBoundary(line)) {
63770
+ buf.length = 0;
63771
+ lastMeaningful = null;
63772
+ continue;
63773
+ }
63774
+ for (const ev of projectTranscriptLine(line)) {
63775
+ if (ev.kind === "enqueue") {
63776
+ buf.length = 0;
63777
+ lastMeaningful = null;
63778
+ } else if (ev.kind === "tool_use") {
63779
+ buf.length = 0;
63780
+ lastMeaningful = "tool_use";
63781
+ } else if (ev.kind === "text") {
63782
+ const t = ev.text ?? "";
63783
+ if (t.trim().length > 0) {
63784
+ buf.push(t);
63785
+ lastMeaningful = "text";
63786
+ }
63787
+ }
63788
+ }
63789
+ }
63790
+ const text4 = buf.join("").trim();
63791
+ return { text: text4, trailingIsText: lastMeaningful === "text" && text4.length > 0 };
63792
+ }
63793
+ function isRealUserTurnBoundary(line) {
63794
+ let obj;
63795
+ try {
63796
+ obj = JSON.parse(line);
63797
+ } catch {
63798
+ return false;
63799
+ }
63800
+ if (obj.type !== "user")
63801
+ return false;
63802
+ const message = obj.message;
63803
+ const content3 = message?.content;
63804
+ if (typeof content3 === "string")
63805
+ return content3.trim().length > 0;
63806
+ if (Array.isArray(content3)) {
63807
+ for (const c of content3) {
63808
+ if (typeof c === "object" && c != null && c.type === "text") {
63809
+ const t = String(c.text ?? "");
63810
+ if (t.trim().length > 0)
63811
+ return true;
63812
+ }
63813
+ }
63814
+ }
63815
+ return false;
63816
+ }
63817
+ function projectTranscriptLine(line) {
63818
+ if (line.length > MAX_JSONL_LINE_BYTES)
63819
+ return [];
63820
+ let obj;
63821
+ try {
63822
+ obj = JSON.parse(line);
63823
+ } catch {
63824
+ return [];
63825
+ }
63826
+ const type = obj.type;
63827
+ if (!type)
63828
+ return [];
63829
+ if (type === "queue-operation") {
63830
+ const op = obj.operation;
63831
+ if (op === "enqueue") {
63832
+ const content3 = obj.content ?? "";
63833
+ const { chatId, messageId, threadId } = parseChannelMeta(content3);
63834
+ return [{ kind: "enqueue", chatId, messageId, threadId, rawContent: content3 }];
63835
+ }
63836
+ if (op === "dequeue") {
63837
+ return [{ kind: "dequeue" }];
63838
+ }
63839
+ return [];
63840
+ }
63841
+ if (type === "assistant") {
63842
+ const message = obj.message;
63843
+ const content3 = message?.content;
63844
+ if (!Array.isArray(content3))
63845
+ return [];
63846
+ if (obj.isApiErrorMessage === true) {
63847
+ const mainModel2 = message?.model;
63848
+ return typeof mainModel2 === "string" && !isModelSentinel(mainModel2) ? [{ kind: "model", model: mainModel2 }] : [];
63849
+ }
63850
+ const events = [];
63851
+ const mainModel = message?.model;
63852
+ if (typeof mainModel === "string" && !isModelSentinel(mainModel)) {
63853
+ events.push({ kind: "model", model: mainModel });
63854
+ }
63855
+ const mainUsageTotal = sumUsageTokens(message?.usage);
63856
+ if (mainUsageTotal > 0) {
63857
+ const mainMsgId = message?.id;
63858
+ events.push({
63859
+ kind: "usage",
63860
+ messageId: typeof mainMsgId === "string" ? mainMsgId : null,
63861
+ totalTokens: mainUsageTotal
63862
+ });
63863
+ }
63864
+ const textEvents = projectAssistantTextBlocks(content3, (text4, blockIndex, lastInMessage) => ({ kind: "text", text: text4, blockIndex, lastInMessage }));
63865
+ content3.forEach((c, i) => {
63866
+ const ct = c.type;
63867
+ if (ct === "thinking") {
63868
+ events.push({ kind: "thinking" });
63869
+ } else if (ct === "tool_use") {
63870
+ const input = c.input;
63871
+ events.push({
63872
+ kind: "tool_use",
63873
+ toolName: c.name ?? "",
63874
+ toolUseId: c.id ?? null,
63875
+ input: input && typeof input === "object" ? input : undefined
63876
+ });
63877
+ } else if (ct === "text") {
63878
+ const ev = textEvents.get(i);
63879
+ if (ev != null)
63880
+ events.push(ev);
63881
+ }
63882
+ });
63883
+ return events;
63884
+ }
63885
+ if (type === "user") {
63886
+ const message = obj.message;
63887
+ const content3 = message?.content;
63888
+ if (!Array.isArray(content3))
63889
+ return [];
63890
+ const events = [];
63891
+ for (const c of content3) {
63892
+ if (c.type === "tool_result") {
63893
+ const isError2 = c.is_error === true ? true : undefined;
63894
+ events.push({
63895
+ kind: "tool_result",
63896
+ toolUseId: c.tool_use_id ?? "",
63897
+ toolName: null,
63898
+ isError: isError2,
63899
+ errorText: isError2 ? extractToolResultErrorText(c.content) : undefined
63900
+ });
63901
+ }
63902
+ }
63903
+ return events;
63904
+ }
63905
+ if (type === "system" && obj.subtype === "turn_duration") {
63906
+ return [
63907
+ { kind: "turn_end", durationMs: obj.durationMs ?? 0 }
63908
+ ];
63909
+ }
63910
+ return [];
63911
+ }
63912
+
62875
63913
  // pty-tail.ts
62876
63914
  var import_headless = __toESM(require_xterm_headless(), 1);
62877
63915
  var PTY_DEBUG = process.env.SWITCHROOM_PTY_DEBUG === "1";
@@ -62956,7 +63994,7 @@ async function gatewayStartupRetry(fn, opts = {}) {
62956
63994
 
62957
63995
  // gateway/quarantine.ts
62958
63996
  import { mkdirSync as mkdirSync15, writeFileSync as writeFileSync15 } from "node:fs";
62959
- import { join as join20 } from "node:path";
63997
+ import { join as join22 } from "node:path";
62960
63998
  var QUARANTINE_FILENAME = "quarantine.json";
62961
63999
  function writeQuarantineMarker(telegramStateDir, reason, detail, nowFn = Date.now) {
62962
64000
  mkdirSync15(telegramStateDir, { recursive: true, mode: 448 });
@@ -62966,7 +64004,7 @@ function writeQuarantineMarker(telegramStateDir, reason, detail, nowFn = Date.no
62966
64004
  ts: nowFn(),
62967
64005
  detail
62968
64006
  };
62969
- writeFileSync15(join20(telegramStateDir, QUARANTINE_FILENAME), JSON.stringify(marker) + `
64007
+ writeFileSync15(join22(telegramStateDir, QUARANTINE_FILENAME), JSON.stringify(marker) + `
62970
64008
  `, "utf-8");
62971
64009
  }
62972
64010
 
@@ -63985,9 +65023,9 @@ function defaultAddAccount(label, credentials, opts) {
63985
65023
  // ../src/auth/broker/client.ts
63986
65024
  init_protocol2();
63987
65025
  import * as net3 from "node:net";
63988
- import { homedir as homedir7 } from "node:os";
65026
+ import { homedir as homedir8 } from "node:os";
63989
65027
  import { randomUUID as randomUUID4 } from "node:crypto";
63990
- import { join as join22 } from "node:path";
65028
+ import { join as join24 } from "node:path";
63991
65029
  var DEFAULT_TIMEOUT_MS3 = 5000;
63992
65030
  function reviveDate2(v) {
63993
65031
  if (v == null)
@@ -63997,8 +65035,8 @@ function reviveDate2(v) {
63997
65035
  const d = new Date(v);
63998
65036
  return Number.isNaN(d.getTime()) ? null : d;
63999
65037
  }
64000
- function operatorSocketPath2(home2 = homedir7()) {
64001
- return join22(home2, ".switchroom", "state", "auth-broker-operator", "sock");
65038
+ function operatorSocketPath2(home2 = homedir8()) {
65039
+ return join24(home2, ".switchroom", "state", "auth-broker-operator", "sock");
64002
65040
  }
64003
65041
  function resolveAuthBrokerSocketPath2(opts) {
64004
65042
  if (opts?.socket)
@@ -64312,13 +65350,13 @@ class AuthBrokerClient2 {
64312
65350
  init_loader();
64313
65351
  init_resolver();
64314
65352
  init_vault();
64315
- import { existsSync as existsSync16 } from "node:fs";
65353
+ import { existsSync as existsSync17 } from "node:fs";
64316
65354
  var DEFAULT_VOICE_API_KEY_REF = "vault:openai/api-key";
64317
65355
  function tryDirectVaultRead(ref, config, passphrase) {
64318
65356
  if (!passphrase)
64319
65357
  return null;
64320
65358
  const vaultPath = resolvePath(config.vault?.path ?? "~/.switchroom/vault.enc");
64321
- if (!existsSync16(vaultPath))
65359
+ if (!existsSync17(vaultPath))
64322
65360
  return null;
64323
65361
  try {
64324
65362
  const secrets = openVault(passphrase, vaultPath);
@@ -64374,14 +65412,14 @@ async function materializeVoiceKey(opts = {}, logger2 = (line) => process.stderr
64374
65412
  init_loader();
64375
65413
  init_resolver();
64376
65414
  init_vault();
64377
- import { existsSync as existsSync17 } from "node:fs";
65415
+ import { existsSync as existsSync18 } from "node:fs";
64378
65416
  var VOICE_SIDECAR_TOKEN_KEY = "voice/sidecar-token";
64379
65417
  var DEFAULT_VOICE_SIDECAR_TOKEN_REF = `vault:${VOICE_SIDECAR_TOKEN_KEY}`;
64380
65418
  function tryDirectVaultRead2(ref, config, passphrase) {
64381
65419
  if (!passphrase)
64382
65420
  return null;
64383
65421
  const vaultPath = resolvePath(config.vault?.path ?? "~/.switchroom/vault.enc");
64384
- if (!existsSync17(vaultPath))
65422
+ if (!existsSync18(vaultPath))
64385
65423
  return null;
64386
65424
  try {
64387
65425
  const secrets = openVault(passphrase, vaultPath);
@@ -64434,17 +65472,17 @@ async function materializeSidecarToken(opts = {}, logger2 = (line) => process.st
64434
65472
  }
64435
65473
 
64436
65474
  // ../src/setup/host-capabilities.ts
64437
- import { existsSync as existsSync18, mkdirSync as mkdirSync18, readFileSync as readFileSync18, writeFileSync as writeFileSync16 } from "node:fs";
65475
+ import { existsSync as existsSync19, mkdirSync as mkdirSync18, readFileSync as readFileSync19, writeFileSync as writeFileSync16 } from "node:fs";
64438
65476
  init_paths();
64439
65477
  function hostCapabilitiesPath() {
64440
65478
  return resolveStatePath("host-capabilities.json");
64441
65479
  }
64442
65480
  function loadHostCapabilities() {
64443
65481
  const path2 = hostCapabilitiesPath();
64444
- if (!existsSync18(path2))
65482
+ if (!existsSync19(path2))
64445
65483
  return null;
64446
65484
  try {
64447
- const parsed = JSON.parse(readFileSync18(path2, "utf-8"));
65485
+ const parsed = JSON.parse(readFileSync19(path2, "utf-8"));
64448
65486
  if (parsed && typeof parsed === "object" && "voice" in parsed && typeof parsed.voice === "object") {
64449
65487
  return parsed;
64450
65488
  }
@@ -64552,16 +65590,16 @@ function resolveExhaustUntil(resetAtMs, now = Date.now()) {
64552
65590
 
64553
65591
  // gateway/auth-add-flow.ts
64554
65592
  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";
65593
+ 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";
65594
+ import { homedir as homedir9 } from "node:os";
65595
+ import { join as join25 } from "node:path";
64558
65596
  import { randomBytes as randomBytes5 } from "node:crypto";
64559
65597
 
64560
65598
  // ../src/auth/manager.ts
64561
65599
  import {
64562
- readFileSync as readFileSync19,
65600
+ readFileSync as readFileSync20,
64563
65601
  readdirSync as readdirSync3,
64564
- existsSync as existsSync19,
65602
+ existsSync as existsSync20,
64565
65603
  writeFileSync as writeFileSync17,
64566
65604
  mkdirSync as mkdirSync19,
64567
65605
  mkdtempSync as mkdtempSync2,
@@ -64588,9 +65626,9 @@ function parseSetupTokenUrl(output) {
64588
65626
  }
64589
65627
  function readTokenFromCredentialsFile(credentialsFilePath) {
64590
65628
  try {
64591
- if (!existsSync19(credentialsFilePath))
65629
+ if (!existsSync20(credentialsFilePath))
64592
65630
  return null;
64593
- const raw = readFileSync19(credentialsFilePath, "utf-8");
65631
+ const raw = readFileSync20(credentialsFilePath, "utf-8");
64594
65632
  const parsed = JSON.parse(raw);
64595
65633
  const token = parsed?.claudeAiOauth?.accessToken;
64596
65634
  if (typeof token !== "string")
@@ -64648,9 +65686,9 @@ function makeAuthAddTmuxOps(tmuxBin = "tmux") {
64648
65686
  };
64649
65687
  }
64650
65688
  var pendingAuthAddFlows = new Map;
64651
- function pickScratchDir(label, home2 = homedir8()) {
65689
+ function pickScratchDir(label, home2 = homedir9()) {
64652
65690
  const suffix = randomBytes5(8).toString("hex");
64653
- return join23(home2, ".switchroom", "accounts", ".in-progress", `${label}-${suffix}`);
65691
+ return join25(home2, ".switchroom", "accounts", ".in-progress", `${label}-${suffix}`);
64654
65692
  }
64655
65693
  function cleanScratchDir(scratchDir) {
64656
65694
  try {
@@ -64659,8 +65697,8 @@ function cleanScratchDir(scratchDir) {
64659
65697
  }
64660
65698
  var AUTH_TMUX_SESSION_FILE = ".auth-tmux-session";
64661
65699
  function sweepOrphanSessions(home2, tmux, nowMs2 = Date.now()) {
64662
- const inProgressDir = join23(home2, ".switchroom", "accounts", ".in-progress");
64663
- if (!existsSync20(inProgressDir))
65700
+ const inProgressDir = join25(home2, ".switchroom", "accounts", ".in-progress");
65701
+ if (!existsSync21(inProgressDir))
64664
65702
  return;
64665
65703
  let entries;
64666
65704
  try {
@@ -64670,16 +65708,16 @@ function sweepOrphanSessions(home2, tmux, nowMs2 = Date.now()) {
64670
65708
  }
64671
65709
  const tenMinMs = 10 * 60000;
64672
65710
  for (const entry of entries) {
64673
- const dir = join23(inProgressDir, entry);
64674
- const sessionFile = join23(dir, AUTH_TMUX_SESSION_FILE);
64675
- if (!existsSync20(sessionFile))
65711
+ const dir = join25(inProgressDir, entry);
65712
+ const sessionFile = join25(dir, AUTH_TMUX_SESSION_FILE);
65713
+ if (!existsSync21(sessionFile))
64676
65714
  continue;
64677
65715
  let fileContents;
64678
65716
  let fileMtime;
64679
65717
  try {
64680
65718
  const stat = statSync7(sessionFile);
64681
65719
  fileMtime = stat.mtimeMs;
64682
- fileContents = readFileSync20(sessionFile, "utf8").trim();
65720
+ fileContents = readFileSync21(sessionFile, "utf8").trim();
64683
65721
  } catch {
64684
65722
  continue;
64685
65723
  }
@@ -64699,7 +65737,7 @@ async function startAccountAuthSession(label, opts = {}) {
64699
65737
  if (process.env.SWITCHROOM_TMUX_SUPERVISOR !== "1" && !opts.tmuxOps) {
64700
65738
  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
65739
  }
64702
- const home2 = opts.home ?? homedir8();
65740
+ const home2 = opts.home ?? homedir9();
64703
65741
  const urlTimeoutMs = opts.urlTimeoutMs ?? 12000;
64704
65742
  const agentName3 = opts.agentName ?? process.env.SWITCHROOM_AGENT_NAME ?? "gateway";
64705
65743
  const tmux = opts.tmuxOps ?? makeAuthAddTmuxOps(opts.tmuxBin);
@@ -64711,7 +65749,7 @@ async function startAccountAuthSession(label, opts = {}) {
64711
65749
  const tmuxSocket = `switchroom-${agentName3}`;
64712
65750
  const tmuxSession = `auth-add-${label}-${hexSuffix}`.slice(0, 64);
64713
65751
  try {
64714
- writeFileSync18(join23(scratchDir, AUTH_TMUX_SESSION_FILE), `${tmuxSocket}
65752
+ writeFileSync18(join25(scratchDir, AUTH_TMUX_SESSION_FILE), `${tmuxSocket}
64715
65753
  ${tmuxSession}`, "utf8");
64716
65754
  } catch {}
64717
65755
  const sessionEnv = {
@@ -64760,7 +65798,7 @@ async function submitAccountAuthCode(flow3, code2, opts = {}) {
64760
65798
  const pollIntervalMs = opts.pollIntervalMs ?? 250;
64761
65799
  const pollTimeoutMs = opts.pollTimeoutMs ?? 300000;
64762
65800
  const tmux = opts.tmuxOps ?? makeAuthAddTmuxOps();
64763
- const credentialsPath = join23(flow3.scratchDir, ".credentials.json");
65801
+ const credentialsPath = join25(flow3.scratchDir, ".credentials.json");
64764
65802
  try {
64765
65803
  tmux.send(flow3.tmuxSocket, flow3.tmuxSession, code2);
64766
65804
  } catch (err) {
@@ -64770,11 +65808,11 @@ async function submitAccountAuthCode(flow3, code2, opts = {}) {
64770
65808
  const deadline = Date.now() + pollTimeoutMs;
64771
65809
  while (Date.now() < deadline) {
64772
65810
  await new Promise((r) => setTimeout(r, pollIntervalMs));
64773
- if (existsSync20(credentialsPath)) {
65811
+ if (existsSync21(credentialsPath)) {
64774
65812
  const token = readTokenFromCredentialsFile(credentialsPath);
64775
65813
  if (token) {
64776
65814
  try {
64777
- const raw = readFileSync20(credentialsPath, "utf-8");
65815
+ const raw = readFileSync21(credentialsPath, "utf-8");
64778
65816
  const parsed = JSON.parse(raw);
64779
65817
  if (parsed.claudeAiOauth?.accessToken) {
64780
65818
  tmux.killSession(flow3.tmuxSocket, flow3.tmuxSession);
@@ -64784,7 +65822,7 @@ async function submitAccountAuthCode(flow3, code2, opts = {}) {
64784
65822
  }
64785
65823
  }
64786
65824
  if (!tmux.hasSession(flow3.tmuxSocket, flow3.tmuxSession)) {
64787
- if (!existsSync20(credentialsPath)) {
65825
+ if (!existsSync21(credentialsPath)) {
64788
65826
  cleanScratchDir(flow3.scratchDir);
64789
65827
  throw new Error("claude setup-token exited without writing credentials \u2014 the code may be invalid or expired");
64790
65828
  }
@@ -65236,28 +66274,6 @@ function autoClassifyMidTurnInbound(i) {
65236
66274
 
65237
66275
  // operator-events.ts
65238
66276
  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
66277
  function renderOperatorEvent(ev) {
65262
66278
  const agent = escapeMarkdown(ev.agent);
65263
66279
  const detail = escapeMarkdown(stripRawErrorBytes(ev.detail));
@@ -65445,15 +66461,15 @@ function renderOperatorEvent(ev) {
65445
66461
  };
65446
66462
  }
65447
66463
  }
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) {
66464
+ var DEFAULT_OPERATOR_EVENT_COOLDOWN_MS2 = 5 * 60000;
66465
+ var cooldownMap2 = new Map;
66466
+ function shouldEmitOperatorEvent(agent, kind, now = Date.now(), cooldownMs = DEFAULT_OPERATOR_EVENT_COOLDOWN_MS2) {
65451
66467
  const key = `${agent}:${kind}`;
65452
- const last = cooldownMap.get(key);
66468
+ const last = cooldownMap2.get(key);
65453
66469
  if (last != null && now - last < cooldownMs) {
65454
66470
  return false;
65455
66471
  }
65456
- cooldownMap.set(key, now);
66472
+ cooldownMap2.set(key, now);
65457
66473
  return true;
65458
66474
  }
65459
66475
 
@@ -65464,290 +66480,6 @@ function recordOperatorEvent(event, now = Date.now()) {
65464
66480
  store.set(event.agent, { event, storedAt: now });
65465
66481
  }
65466
66482
 
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
66483
  // throttle-tier.ts
65752
66484
  init_card_format();
65753
66485
  init_quota_check();
@@ -65807,72 +66539,6 @@ function renderThrottleEscalationNotice(opts) {
65807
66539
  ${tail}`;
65808
66540
  }
65809
66541
 
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
66542
  // llm-error-present.ts
65877
66543
  function extractModel(raw) {
65878
66544
  const m = raw.match(/["']?model["']?\s*[=:]\s*["']?((?:claude|sr)[A-Za-z0-9._-]+)/i);
@@ -67368,9 +68034,50 @@ function backOffOpenInline2(text4, cut) {
67368
68034
  }
67369
68035
 
67370
68036
  // rich-send.ts
68037
+ init_dollar_math_guard();
68038
+ init_emphasis_guard();
68039
+ init_line_start_guard();
68040
+ init_inline_pairs_guard();
67371
68041
  var import_grammy8 = __toESM(require_mod2(), 1);
68042
+ function guardAccidentalFormatting2(markdown) {
68043
+ let out = markdown;
68044
+ out = guardAccidentalEmphasis(out);
68045
+ out = guardAccidentalBlockConstructs(out);
68046
+ out = guardAccidentalInlinePairs(out);
68047
+ out = guardDollarMath(out);
68048
+ return out;
68049
+ }
67372
68050
  function richMessage2(markdown) {
67373
- return { markdown };
68051
+ return { markdown: guardAccidentalFormatting2(markdown) };
68052
+ }
68053
+
68054
+ // gateway/redelivery-decision.ts
68055
+ var REDELIVERY_PREFIX = "Recovered from an interrupted turn:";
68056
+ function decideRedeliverCapture(input) {
68057
+ if (input.willBeResumed)
68058
+ return { capture: false, skipReason: "will-be-resumed" };
68059
+ if (!input.hasSessionId)
68060
+ return { capture: false, skipReason: "no-session-id" };
68061
+ return { capture: true };
68062
+ }
68063
+ function frameRedelivery(capturedText) {
68064
+ return `${REDELIVERY_PREFIX}
68065
+
68066
+ ${capturedText.trim()}`;
68067
+ }
68068
+ function decideRedeliver(input) {
68069
+ const text4 = input.capturedText.trim();
68070
+ if (text4.length === 0)
68071
+ return { redeliver: false, skipReason: "empty-text" };
68072
+ if (!input.trailingIsText)
68073
+ return { redeliver: false, skipReason: "trailing-not-text" };
68074
+ if (input.alreadyRedelivered)
68075
+ return { redeliver: false, skipReason: "already-redelivered" };
68076
+ if (input.hasDeliveredText)
68077
+ return { redeliver: false, skipReason: "already-delivered" };
68078
+ if (input.ageMs > input.maxAgeMs)
68079
+ return { redeliver: false, skipReason: "stale" };
68080
+ return { redeliver: true, framedText: frameRedelivery(text4) };
67374
68081
  }
67375
68082
 
67376
68083
  // text-voice-scrub.ts
@@ -68360,10 +69067,10 @@ function resolveAgentDirFromEnv() {
68360
69067
 
68361
69068
  // active-reactions.ts
68362
69069
  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";
69070
+ import { join as join27 } from "node:path";
68364
69071
  var ACTIVE_REACTIONS_FILENAME = ".active-reactions.json";
68365
69072
  function reactionsPath(agentDir) {
68366
- return join26(agentDir, ACTIVE_REACTIONS_FILENAME);
69073
+ return join27(agentDir, ACTIVE_REACTIONS_FILENAME);
68367
69074
  }
68368
69075
  function readActiveReactions(agentDir) {
68369
69076
  const p = reactionsPath(agentDir);
@@ -68428,10 +69135,10 @@ function clearActiveReactions(agentDir) {
68428
69135
 
68429
69136
  // active-reactions.ts
68430
69137
  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";
69138
+ import { join as join28 } from "node:path";
68432
69139
  var ACTIVE_REACTIONS_FILENAME2 = ".active-reactions.json";
68433
69140
  function reactionsPath2(agentDir) {
68434
- return join27(agentDir, ACTIVE_REACTIONS_FILENAME2);
69141
+ return join28(agentDir, ACTIVE_REACTIONS_FILENAME2);
68435
69142
  }
68436
69143
  function readActiveReactions2(agentDir) {
68437
69144
  const p = reactionsPath2(agentDir);
@@ -69349,12 +70056,12 @@ async function approvalRecord(args, opts) {
69349
70056
 
69350
70057
  // quota-check.ts
69351
70058
  import { readFileSync as readFileSync24, existsSync as existsSync25 } from "fs";
69352
- import { join as join28 } from "path";
70059
+ import { join as join29 } from "path";
69353
70060
  var OAUTH_BETA2 = "oauth-2025-04-20";
69354
70061
  var DEFAULT_USER_AGENT2 = "claude-cli/1.0.0 (external, cli)";
69355
70062
  var DEFAULT_PROBE_MODEL2 = "claude-haiku-4-5-20251001";
69356
70063
  function readOauthToken2(claudeConfigDir) {
69357
- const tokenFile = join28(claudeConfigDir, ".oauth-token");
70064
+ const tokenFile = join29(claudeConfigDir, ".oauth-token");
69358
70065
  if (!existsSync25(tokenFile))
69359
70066
  return null;
69360
70067
  try {
@@ -69754,18 +70461,32 @@ async function injectSlashCommand(agentName3, command, opts = {}) {
69754
70461
  const socket = opts.socketName ?? defaultSocketName(agentName3);
69755
70462
  const session = opts.sessionName ?? agentName3;
69756
70463
  const settleMs = opts.settleMs ?? 2000;
69757
- const timeoutMs = opts.timeoutMs ?? 5000;
70464
+ const signalMode = !!(opts.successPattern || opts.errorPattern);
70465
+ const timeoutMs = opts.timeoutMs ?? (signalMode ? 8000 : 5000);
69758
70466
  return withPaneLock(`${socket}:${session}`, () => injectSlashCommandWith(makeTmuxRunner(tmuxBin), {
69759
70467
  socket,
69760
70468
  session,
69761
70469
  command: command.trim(),
69762
70470
  settleMs,
69763
70471
  timeoutMs,
69764
- precondition: opts.precondition
70472
+ precondition: opts.precondition,
70473
+ successPattern: opts.successPattern,
70474
+ errorPattern: opts.errorPattern,
70475
+ settleBeforeSendMs: opts.settleBeforeSendMs
69765
70476
  }));
69766
70477
  }
69767
70478
  async function injectSlashCommandWith(runner, args) {
69768
- const { socket, session, command, settleMs, timeoutMs, precondition } = args;
70479
+ const {
70480
+ socket,
70481
+ session,
70482
+ command,
70483
+ settleMs,
70484
+ timeoutMs,
70485
+ precondition,
70486
+ successPattern,
70487
+ errorPattern,
70488
+ settleBeforeSendMs
70489
+ } = args;
69769
70490
  let bareVerb;
69770
70491
  try {
69771
70492
  bareVerb = validateInjectCommand(command);
@@ -69806,6 +70527,17 @@ async function injectSlashCommandWith(runner, args) {
69806
70527
  errorMessage: "inject precondition returned false at write time; send aborted " + "(no keys sent)."
69807
70528
  };
69808
70529
  }
70530
+ if (settleBeforeSendMs && settleBeforeSendMs > 0) {
70531
+ const settleStart = Date.now();
70532
+ let prevSettle = runner.capture(socket, session) ?? "";
70533
+ while (Date.now() - settleStart < settleBeforeSendMs) {
70534
+ await sleep(POLL_INTERVAL_MS2);
70535
+ const cur = runner.capture(socket, session) ?? "";
70536
+ if (cur === prevSettle)
70537
+ break;
70538
+ prevSettle = cur;
70539
+ }
70540
+ }
69809
70541
  const before = runner.capture(socket, session) ?? "";
69810
70542
  try {
69811
70543
  runner.send(socket, session, ["send-keys", "-l", command]);
@@ -69824,9 +70556,23 @@ async function injectSlashCommandWith(runner, args) {
69824
70556
  const start = Date.now();
69825
70557
  let last = before;
69826
70558
  let stableSince = null;
70559
+ const signalMode = !!(successPattern || errorPattern);
69827
70560
  while (Date.now() - start < timeoutMs) {
69828
70561
  await sleep(POLL_INTERVAL_MS2);
69829
70562
  const cur = runner.capture(socket, session) ?? "";
70563
+ if (signalMode) {
70564
+ last = cur;
70565
+ if (cur !== before) {
70566
+ const { output: region } = diffPane(before, cur, command);
70567
+ const regionLines = region.split(`
70568
+ `).map((l) => l.trim());
70569
+ if (errorPattern && regionLines.some((l) => errorPattern.test(l)))
70570
+ break;
70571
+ if (successPattern && regionLines.some((l) => successPattern.test(l)))
70572
+ break;
70573
+ }
70574
+ continue;
70575
+ }
69830
70576
  if (cur === last && cur !== before) {
69831
70577
  if (stableSince === null) {
69832
70578
  stableSince = Date.now();
@@ -70261,7 +71007,11 @@ async function handleModelCommand(parsed, deps) {
70261
71007
  const verbHtml = `\`/model ${deps.escapeHtml(model)}\``;
70262
71008
  let result;
70263
71009
  try {
70264
- result = await deps.inject(deps.getAgentName(), `/model ${model}`);
71010
+ result = await deps.inject(deps.getAgentName(), `/model ${model}`, {
71011
+ successPattern: MODEL_SWITCH_CONFIRMATION_PREFIX,
71012
+ errorPattern: MODEL_SWITCH_ERROR_RE,
71013
+ settleBeforeSendMs: 1500
71014
+ });
70265
71015
  } catch (err) {
70266
71016
  const msg = err instanceof Error ? err.message : String(err);
70267
71017
  return {
@@ -70269,21 +71019,21 @@ async function handleModelCommand(parsed, deps) {
70269
71019
  html: true
70270
71020
  };
70271
71021
  }
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);
71022
+ if (result.outcome === "ok" || result.outcome === "ok_no_output") {
71023
+ const confirmation = result.outcome === "ok" ? modelSwitchConfirmationLine(result.output) : null;
70286
71024
  if (confirmation) {
71025
+ if (isKeptModelConfirmation(confirmation)) {
71026
+ return {
71027
+ text: [
71028
+ `${verbHtml}`,
71029
+ deps.preBlock(confirmation),
71030
+ ...result.truncated ? ["_truncated_"] : [],
71031
+ PERSIST_NOTE
71032
+ ].join(`
71033
+ `),
71034
+ html: true
71035
+ };
71036
+ }
70287
71037
  const confirmed = sessionModelFromConfirmation(confirmation) ?? model;
70288
71038
  return {
70289
71039
  text: [
@@ -70294,26 +71044,31 @@ async function handleModelCommand(parsed, deps) {
70294
71044
  ].join(`
70295
71045
  `),
70296
71046
  html: true,
70297
- ...isKeptModelConfirmation(confirmation) ? {} : { selectedModel: confirmed }
71047
+ selectedModel: confirmed
70298
71048
  };
70299
71049
  }
70300
- return {
70301
- text: [
70302
- `${verbHtml} \u2014 sent, but couldn't confirm the switch \u2014 check \`/status\`.`,
70303
- PERSIST_NOTE
70304
- ].join(`
71050
+ const errLine = result.outcome === "ok" ? modelSwitchErrorLine(result.output) : null;
71051
+ if (errLine) {
71052
+ return {
71053
+ text: [
71054
+ `\u274c ${verbHtml} \u2014 the switch did not take:`,
71055
+ deps.preBlock(errLine),
71056
+ "Check `/model` for a valid, available model."
71057
+ ].join(`
70305
71058
  `),
70306
- html: true
70307
- };
70308
- }
70309
- if (result.outcome === "ok_no_output") {
71059
+ html: true
71060
+ };
71061
+ }
71062
+ const optimisticLabel = optimisticModelRecordLabel(model);
70310
71063
  return {
70311
71064
  text: [
70312
- `${verbHtml} \u2014 sent, but no response captured. The agent may be mid-turn; check \`/inject /status\` to confirm the active model.`,
71065
+ `${verbHtml} \u2014 sent, but couldn't read a confirmation line. \`/status\` will show the live model once it's confirmed.`,
70313
71066
  PERSIST_NOTE
70314
71067
  ].join(`
70315
71068
  `),
70316
- html: true
71069
+ html: true,
71070
+ selectedModel: optimisticLabel,
71071
+ optimistic: true
70317
71072
  };
70318
71073
  }
70319
71074
  if (result.errorCode === "session_missing") {
@@ -70376,6 +71131,15 @@ function expandSrAlias(arg) {
70376
71131
  function srFriendlyLabel(srName) {
70377
71132
  return SR_MODEL_LABELS[srName] ?? srName.replace(/^sr-/, "").replace(/-/g, " ");
70378
71133
  }
71134
+ function optimisticModelRecordLabel(token) {
71135
+ if (isSrModel(token))
71136
+ return token;
71137
+ const lower = token.toLowerCase();
71138
+ if (MODEL_ALIASES.includes(lower)) {
71139
+ return lower.charAt(0).toUpperCase() + lower.slice(1);
71140
+ }
71141
+ return token;
71142
+ }
70379
71143
  function classifyDiscoveredOptions(options) {
70380
71144
  return {
70381
71145
  claude: options.filter((o) => !o.label.startsWith("sr-") && !o.label.includes("/") && (/^[A-Z]/.test(o.label) || o.label.startsWith("claude-"))),
@@ -70548,7 +71312,11 @@ async function handleModelMenuCallback(data, deps) {
70548
71312
  }
70549
71313
  let aliasResult;
70550
71314
  try {
70551
- aliasResult = await deps.inject(deps.getAgentName(), `/model ${alias}`);
71315
+ aliasResult = await deps.inject(deps.getAgentName(), `/model ${alias}`, {
71316
+ successPattern: MODEL_SWITCH_CONFIRMATION_PREFIX,
71317
+ errorPattern: MODEL_SWITCH_ERROR_RE,
71318
+ settleBeforeSendMs: 1500
71319
+ });
70552
71320
  } catch (err) {
70553
71321
  const msg = err instanceof Error ? err.message : String(err);
70554
71322
  return {
@@ -70556,16 +71324,32 @@ async function handleModelMenuCallback(data, deps) {
70556
71324
  reply: await menuWithBanner(deps, `\u274c Switch to **${deps.escapeHtml(alias)}** failed: ${deps.escapeHtml(msg)}`)
70557
71325
  };
70558
71326
  }
70559
- if (aliasResult.outcome === "ok") {
70560
- const confirmation = modelSwitchConfirmationLine(aliasResult.output) ?? `Switched to ${alias} (session)`;
70561
- const kept = isKeptModelConfirmation(confirmation);
71327
+ if (aliasResult.outcome === "ok" || aliasResult.outcome === "ok_no_output") {
71328
+ const confirmation = aliasResult.outcome === "ok" ? modelSwitchConfirmationLine(aliasResult.output) : null;
71329
+ if (confirmation) {
71330
+ const kept = isKeptModelConfirmation(confirmation);
71331
+ return {
71332
+ answer: confirmation,
71333
+ reply: await menuWithBannerStatic(deps, `\u2705 ${deps.escapeHtml(confirmation)}`),
71334
+ ...kept ? {} : {
71335
+ selectedModel: sessionModelFromConfirmation(confirmation) ?? optimisticModelRecordLabel(alias),
71336
+ selectedModelToken: alias
71337
+ }
71338
+ };
71339
+ }
71340
+ const aliasErr = aliasResult.outcome === "ok" ? modelSwitchErrorLine(aliasResult.output) : null;
71341
+ if (aliasErr) {
71342
+ return {
71343
+ answer: "Switch failed",
71344
+ reply: await menuWithBanner(deps, `\u274c Switch to **${deps.escapeHtml(alias)}** did not take: ${deps.escapeHtml(aliasErr)}`)
71345
+ };
71346
+ }
71347
+ const optimisticLabel = optimisticModelRecordLabel(alias);
70562
71348
  return {
70563
- answer: confirmation,
70564
- reply: await menuWithBannerStatic(deps, `\u2705 ${deps.escapeHtml(confirmation)}`),
70565
- ...kept ? {} : {
70566
- selectedModel: sessionModelFromConfirmation(confirmation) ?? alias,
70567
- selectedModelToken: alias
70568
- }
71349
+ answer: `Sent /model ${alias} \u2014 check /status`,
71350
+ 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.`),
71351
+ selectedModel: optimisticLabel,
71352
+ selectedModelToken: alias
70569
71353
  };
70570
71354
  }
70571
71355
  return {
@@ -70662,7 +71446,7 @@ function modelSwitchConfirmationLine(output) {
70662
71446
  `).map((l) => l.trim()).find((l) => MODEL_SWITCH_CONFIRMATION_PREFIX.test(l));
70663
71447
  return line && line.length > 0 ? line : null;
70664
71448
  }
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;
71449
+ 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
71450
  function modelSwitchErrorLine(output) {
70667
71451
  const line = output.split(`
70668
71452
  `).map((l) => l.trim()).find((l) => MODEL_SWITCH_ERROR_RE.test(l));
@@ -70711,7 +71495,7 @@ async function menuWithBannerStatic(deps, banner) {
70711
71495
 
70712
71496
  // gateway/session-model-file.ts
70713
71497
  import { readFileSync as readFileSync25, writeFileSync as writeFileSync21, renameSync as renameSync9, rmSync as rmSync4 } from "node:fs";
70714
- import { join as join29 } from "node:path";
71498
+ import { join as join30 } from "node:path";
70715
71499
 
70716
71500
  // gateway/model-command.ts
70717
71501
  var MODEL_ARG_RE2 = /^[A-Za-z0-9][A-Za-z0-9._/\[\]-]{0,99}$/;
@@ -70740,18 +71524,18 @@ function writeSessionModelFile(agentDir, model, configuredDefaultAtWrite) {
70740
71524
  if (!isValidModelArg2(model)) {
70741
71525
  throw new Error(`refusing to persist non-canonical session model token: ${JSON.stringify(model)}`);
70742
71526
  }
70743
- atomicWrite(join29(agentDir, SESSION_MODEL_FILE), serializeSessionModel({ model, configuredDefaultAtWrite, ts: Date.now() }));
71527
+ atomicWrite(join30(agentDir, SESSION_MODEL_FILE), serializeSessionModel({ model, configuredDefaultAtWrite, ts: Date.now() }));
70744
71528
  }
70745
71529
  function readSessionModelFileRaw(agentDir) {
70746
71530
  try {
70747
- return readFileSync25(join29(agentDir, SESSION_MODEL_FILE), "utf8");
71531
+ return readFileSync25(join30(agentDir, SESSION_MODEL_FILE), "utf8");
70748
71532
  } catch {
70749
71533
  return null;
70750
71534
  }
70751
71535
  }
70752
71536
  function clearSessionModelFile(agentDir) {
70753
71537
  try {
70754
- rmSync4(join29(agentDir, SESSION_MODEL_FILE), { force: true });
71538
+ rmSync4(join30(agentDir, SESSION_MODEL_FILE), { force: true });
70755
71539
  } catch {}
70756
71540
  }
70757
71541
  function restoreSessionModelFileRaw(agentDir, raw) {
@@ -70760,12 +71544,12 @@ function restoreSessionModelFileRaw(agentDir, raw) {
70760
71544
  return;
70761
71545
  }
70762
71546
  try {
70763
- atomicWrite(join29(agentDir, SESSION_MODEL_FILE), raw);
71547
+ atomicWrite(join30(agentDir, SESSION_MODEL_FILE), raw);
70764
71548
  } catch {}
70765
71549
  }
70766
71550
  function readConfiguredDefaultModel(agentDir) {
70767
71551
  try {
70768
- const v = readFileSync25(join29(agentDir, CONFIGURED_DEFAULT_MODEL_FILE), "utf8").trim();
71552
+ const v = readFileSync25(join30(agentDir, CONFIGURED_DEFAULT_MODEL_FILE), "utf8").trim();
70769
71553
  return v.length > 0 ? v : null;
70770
71554
  } catch {
70771
71555
  return null;
@@ -70777,12 +71561,12 @@ function writeSessionEffortFile(agentDir, level, configuredDefaultAtWrite) {
70777
71561
  if (!EFFORT_LEVEL_RE.test(level)) {
70778
71562
  throw new Error(`refusing to persist non-allowlisted effort level: ${JSON.stringify(level)}`);
70779
71563
  }
70780
- atomicWrite(join29(agentDir, SESSION_EFFORT_FILE), `${JSON.stringify({ level, configuredDefaultAtWrite: configuredDefaultAtWrite ?? "", ts: Date.now() })}
71564
+ atomicWrite(join30(agentDir, SESSION_EFFORT_FILE), `${JSON.stringify({ level, configuredDefaultAtWrite: configuredDefaultAtWrite ?? "", ts: Date.now() })}
70781
71565
  `);
70782
71566
  }
70783
71567
  function clearSessionEffortFile(agentDir) {
70784
71568
  try {
70785
- rmSync4(join29(agentDir, SESSION_EFFORT_FILE), { force: true });
71569
+ rmSync4(join30(agentDir, SESSION_EFFORT_FILE), { force: true });
70786
71570
  } catch {}
70787
71571
  }
70788
71572
  var PREMIUM_RECOVERY_FILE = ".premium-recovery";
@@ -70805,13 +71589,13 @@ function writePremiumRecoveryFile(agentDir, premiumModel, chats) {
70805
71589
  if (clean.length === 0) {
70806
71590
  throw new Error("refusing to persist premium-recovery marker with no chats to notify");
70807
71591
  }
70808
- atomicWrite(join29(agentDir, PREMIUM_RECOVERY_FILE), `${JSON.stringify({ premiumModel, chats: clean, ts: Date.now() })}
71592
+ atomicWrite(join30(agentDir, PREMIUM_RECOVERY_FILE), `${JSON.stringify({ premiumModel, chats: clean, ts: Date.now() })}
70809
71593
  `);
70810
71594
  }
70811
71595
  function readPremiumRecoveryFile(agentDir) {
70812
71596
  let raw;
70813
71597
  try {
70814
- raw = readFileSync25(join29(agentDir, PREMIUM_RECOVERY_FILE), "utf8");
71598
+ raw = readFileSync25(join30(agentDir, PREMIUM_RECOVERY_FILE), "utf8");
70815
71599
  } catch {
70816
71600
  return null;
70817
71601
  }
@@ -70824,7 +71608,7 @@ function readPremiumRecoveryFile(agentDir) {
70824
71608
  }
70825
71609
  function clearPremiumRecoveryFile(agentDir) {
70826
71610
  try {
70827
- rmSync4(join29(agentDir, PREMIUM_RECOVERY_FILE), { force: true });
71611
+ rmSync4(join30(agentDir, PREMIUM_RECOVERY_FILE), { force: true });
70828
71612
  } catch {}
70829
71613
  }
70830
71614
 
@@ -71016,7 +71800,7 @@ function makeIo(agentName3, opts) {
71016
71800
  socket: opts.socketName ?? `switchroom-${agentName3}`,
71017
71801
  session: opts.sessionName ?? agentName3,
71018
71802
  stepMs: opts.stepMs ?? 600,
71019
- timeoutMs: opts.timeoutMs ?? 1e4,
71803
+ timeoutMs: opts.timeoutMs ?? 12000,
71020
71804
  sleep: opts._sleep ?? realSleep,
71021
71805
  log: opts._log ?? ((line) => process.stderr.write(`${line}
71022
71806
  `)),
@@ -71039,7 +71823,7 @@ function sendLiteral(io, text4) {
71039
71823
  function sendKey(io, key) {
71040
71824
  io.runner.send(io.socket, io.session, ["send-keys", key]);
71041
71825
  }
71042
- async function openPicker(io) {
71826
+ async function openPicker(io, deadlineMs) {
71043
71827
  sendLiteral(io, "/model");
71044
71828
  sendKey(io, "Enter");
71045
71829
  for (;; ) {
@@ -71048,10 +71832,24 @@ async function openPicker(io) {
71048
71832
  const parsed = parseModelPicker(pane);
71049
71833
  if (parsed?.footerSeen)
71050
71834
  return parsed;
71051
- if (expired(io))
71835
+ if (deadlineMs != null && Date.now() >= deadlineMs || expired(io)) {
71052
71836
  return parsed;
71837
+ }
71053
71838
  }
71054
71839
  }
71840
+ async function openPickerWithRetry(io) {
71841
+ const remaining = io.timeoutMs - (Date.now() - io.startedAt);
71842
+ const firstDeadline = Date.now() + Math.max(io.stepMs * 2, Math.floor(remaining / 2));
71843
+ const parsed = await openPicker(io, firstDeadline);
71844
+ if (parsed?.footerSeen)
71845
+ return parsed;
71846
+ if (expired(io))
71847
+ return parsed;
71848
+ await dismissPicker(io);
71849
+ if (expired(io))
71850
+ return parsed;
71851
+ return openPicker(io);
71852
+ }
71055
71853
  async function dismissPicker(io) {
71056
71854
  for (let attempt = 0;attempt < 2; attempt++) {
71057
71855
  try {
@@ -71081,7 +71879,7 @@ async function discoverModels(agentName3, opts = {}) {
71081
71879
  let parsed = null;
71082
71880
  let dismissed = true;
71083
71881
  try {
71084
- parsed = await openPicker(io);
71882
+ parsed = await openPickerWithRetry(io);
71085
71883
  } finally {
71086
71884
  dismissed = await dismissOrWarn(io, "discover");
71087
71885
  }
@@ -71106,7 +71904,7 @@ async function selectModel(agentName3, targetLabel, opts = {}) {
71106
71904
  io.startedAt = Date.now();
71107
71905
  let selected = false;
71108
71906
  try {
71109
- const parsed = await openPicker(io);
71907
+ const parsed = await openPickerWithRetry(io);
71110
71908
  if (!parsed || !parsed.footerSeen) {
71111
71909
  return { ok: false, reason: "picker did not render \u2014 agent may be mid-turn" };
71112
71910
  }
@@ -71163,7 +71961,7 @@ function extractConfirmation(pane) {
71163
71961
  }
71164
71962
 
71165
71963
  // ../src/agents/scaffold.ts
71166
- import { join as join32, resolve as resolve6 } from "node:path";
71964
+ import { join as join33, resolve as resolve6 } from "node:path";
71167
71965
  init_atomic();
71168
71966
 
71169
71967
  // ../src/agents/agent-uid.ts
@@ -71187,8 +71985,8 @@ var CONTAINER_DEFAULT_UTC_ZONES = new Set([
71187
71985
  ]);
71188
71986
 
71189
71987
  // ../src/cli/agent-config.ts
71190
- import { join as join30 } from "node:path";
71191
- import { homedir as homedir9 } from "node:os";
71988
+ import { join as join31 } from "node:path";
71989
+ import { homedir as homedir10 } from "node:os";
71192
71990
 
71193
71991
  // ../src/cli/helpers.ts
71194
71992
  init_loader();
@@ -71208,12 +72006,12 @@ var WEBKITE_VAULT_KEYS = new Set([
71208
72006
  init_overlay_loader();
71209
72007
 
71210
72008
  // ../src/cli/agent-config.ts
71211
- var AUDIT_ROOT = join30(homedir9(), ".switchroom", "audit");
72009
+ var AUDIT_ROOT = join31(homedir10(), ".switchroom", "audit");
71212
72010
 
71213
72011
  // ../src/agents/profiles.ts
71214
72012
  var import_handlebars = __toESM(require_lib(), 1);
71215
72013
  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";
72014
+ import { resolve as resolve5, join as join32, sep as pathSep } from "node:path";
71217
72015
  var PROFILES_ROOT = resolve5(import.meta.dirname, "../../profiles");
71218
72016
  import_handlebars.default.registerHelper("json", (value) => {
71219
72017
  return new import_handlebars.default.SafeString(JSON.stringify(value, null, 2));
@@ -71224,7 +72022,7 @@ import_handlebars.default.registerHelper("isNumber", (value) => {
71224
72022
  var SHARED_FRAGMENTS_DIR = resolve5(PROFILES_ROOT, "_shared");
71225
72023
  var SHARED_FRAGMENTS = ["vault-protocol", "agent-self-service", "execution-discipline", "reply-discipline", "dev-protocol"];
71226
72024
  for (const name of SHARED_FRAGMENTS) {
71227
- const fragPath = join31(SHARED_FRAGMENTS_DIR, `${name}.md.hbs`);
72025
+ const fragPath = join32(SHARED_FRAGMENTS_DIR, `${name}.md.hbs`);
71228
72026
  if (existsSync26(fragPath)) {
71229
72027
  import_handlebars.default.registerPartial(name, readFileSync26(fragPath, "utf-8"));
71230
72028
  }
@@ -71936,7 +72734,7 @@ init_overlay_loader();
71936
72734
  init_merge();
71937
72735
  var import_yaml4 = __toESM(require_dist(), 1);
71938
72736
  import { readFileSync as readFileSync27, existsSync as existsSync27 } from "node:fs";
71939
- import { homedir as homedir10 } from "node:os";
72737
+ import { homedir as homedir11 } from "node:os";
71940
72738
  import { resolve as resolve7 } from "node:path";
71941
72739
 
71942
72740
  class ConfigError2 extends Error {
@@ -71993,7 +72791,7 @@ function coerceLegacyGoogleWorkspaceKeys2(parsed, filePath) {
71993
72791
  }
71994
72792
  function findConfigFile2(startDir) {
71995
72793
  const envPath = process.env.SWITCHROOM_CONFIG;
71996
- const home2 = homedir10();
72794
+ const home2 = homedir11();
71997
72795
  const userDir = resolve7(home2, ".switchroom");
71998
72796
  const searchPaths = [
71999
72797
  envPath ? resolve7(envPath) : null,
@@ -72632,7 +73430,7 @@ function numField(obj, key) {
72632
73430
 
72633
73431
  // gateway/context-occupancy.ts
72634
73432
  import { mkdirSync as mkdirSync23, writeFileSync as writeFileSync23 } from "node:fs";
72635
- import { join as join33 } from "node:path";
73433
+ import { join as join34 } from "node:path";
72636
73434
  var CONTEXT_OCCUPANCY_FILENAME = "context-occupancy.json";
72637
73435
  var TIGHT_FRACTION = 0.8;
72638
73436
  function buildContextOccupancy(occupancy, cap, now) {
@@ -72655,7 +73453,7 @@ function buildContextOccupancy(occupancy, cap, now) {
72655
73453
  }
72656
73454
  function writeContextOccupancySnapshot(stateDir, snapshot, deps) {
72657
73455
  try {
72658
- const path2 = join33(stateDir, CONTEXT_OCCUPANCY_FILENAME);
73456
+ const path2 = join34(stateDir, CONTEXT_OCCUPANCY_FILENAME);
72659
73457
  (deps?.mkdir ?? ((p, o) => mkdirSync23(p, o)))(stateDir, { recursive: true });
72660
73458
  (deps?.writeFile ?? ((p, d) => writeFileSync23(p, d)))(path2, JSON.stringify(snapshot, null, 2) + `
72661
73459
  `);
@@ -73150,12 +73948,12 @@ function startWebhookIngestServer(opts) {
73150
73948
 
73151
73949
  // ../src/web/webhook-gateway-record.ts
73152
73950
  import { appendFileSync as appendFileSync5, mkdirSync as mkdirSync26 } from "fs";
73153
- import { join as join36 } from "path";
73154
- import { homedir as homedir12 } from "os";
73951
+ import { join as join37 } from "path";
73952
+ import { homedir as homedir13 } from "os";
73155
73953
 
73156
73954
  // ../src/web/webhook-handler.ts
73157
73955
  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";
73956
+ import { join as join35 } from "path";
73159
73957
  var DEDUP_MAX = 1000;
73160
73958
  var DEDUP_TTL_MS = 24 * 60 * 60 * 1000;
73161
73959
  function loadDedupFile(path2) {
@@ -73184,8 +73982,8 @@ var agentDedupCache = new Map;
73184
73982
  function createFileDedupStore(resolveAgentDir) {
73185
73983
  return {
73186
73984
  check(agent, deliveryId, now) {
73187
- const telegramDir = join34(resolveAgentDir(agent), "telegram");
73188
- const filePath = join34(telegramDir, "webhook-dedup.json");
73985
+ const telegramDir = join35(resolveAgentDir(agent), "telegram");
73986
+ const filePath = join35(telegramDir, "webhook-dedup.json");
73189
73987
  if (!agentDedupCache.has(agent)) {
73190
73988
  agentDedupCache.set(agent, loadDedupFile(filePath));
73191
73989
  }
@@ -73207,8 +74005,8 @@ var throttleIssueWindow = new Map;
73207
74005
 
73208
74006
  // ../src/web/webhook-dispatch.ts
73209
74007
  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";
74008
+ import { join as join36 } from "path";
74009
+ import { homedir as homedir12 } from "os";
73212
74010
 
73213
74011
  // ../src/agent-scheduler/ipc-client.ts
73214
74012
  import { createConnection as createConnection2 } from "node:net";
@@ -73527,8 +74325,8 @@ function createFileCooldownStore(resolveAgentDir) {
73527
74325
  isCoolingDown(agent, key, cooldownMs, now) {
73528
74326
  if (cooldownMs <= 0)
73529
74327
  return false;
73530
- const telegramDir = join35(resolveAgentDir(agent), "telegram");
73531
- const filePath = join35(telegramDir, "webhook-cooldown.json");
74328
+ const telegramDir = join36(resolveAgentDir(agent), "telegram");
74329
+ const filePath = join36(telegramDir, "webhook-cooldown.json");
73532
74330
  if (!cache.has(agent)) {
73533
74331
  cache.set(agent, loadCooldownFile(filePath));
73534
74332
  }
@@ -73585,9 +74383,9 @@ async function defaultInject(socketPath, agentName3, inbound) {
73585
74383
  }
73586
74384
  function injectWebhookInbound(agent, prompt, ctx, deps = {}) {
73587
74385
  const log = deps.log ?? ((s) => process.stderr.write(s));
73588
- const resolveAgentDir = deps.resolveAgentDir ?? ((a) => join35(homedir11(), ".switchroom", "agents", a));
74386
+ const resolveAgentDir = deps.resolveAgentDir ?? ((a) => join36(homedir12(), ".switchroom", "agents", a));
73589
74387
  const now = (deps.now ?? Date.now)();
73590
- const socketPath = join35(resolveAgentDir(agent), "telegram", "gateway.sock");
74388
+ const socketPath = join36(resolveAgentDir(agent), "telegram", "gateway.sock");
73591
74389
  const inbound = {
73592
74390
  type: "inbound",
73593
74391
  chatId: ctx.chatId,
@@ -73659,7 +74457,7 @@ function evaluateDispatch(args, deps = {}) {
73659
74457
  const log = deps.log ?? ((s) => process.stderr.write(s));
73660
74458
  const now = (deps.now ?? Date.now)();
73661
74459
  const nowDate = deps.nowDate ?? (() => new Date(now));
73662
- const resolveAgentDir = deps.resolveAgentDir ?? ((a) => join35(homedir11(), ".switchroom", "agents", a));
74460
+ const resolveAgentDir = deps.resolveAgentDir ?? ((a) => join36(homedir12(), ".switchroom", "agents", a));
73663
74461
  const cooldownStore = deps.cooldownStore ?? createFileCooldownStore(resolveAgentDir);
73664
74462
  if (!DISPATCH_SOURCES.includes(args.source))
73665
74463
  return 0;
@@ -73737,10 +74535,10 @@ var CONSOLIDATION_COMPLETED_EVENT = "consolidation.completed";
73737
74535
  function recordWebhookEvent(rec, deps = {}) {
73738
74536
  const log = deps.log ?? ((s) => process.stderr.write(s));
73739
74537
  const now = rec.ts || (deps.now ?? Date.now)();
73740
- const resolveAgentDir = deps.resolveAgentDir ?? ((a) => join36(homedir12(), ".switchroom", "agents", a));
74538
+ const resolveAgentDir = deps.resolveAgentDir ?? ((a) => join37(homedir13(), ".switchroom", "agents", a));
73741
74539
  const dedupStore = deps.dedupStore ?? createFileDedupStore(resolveAgentDir);
73742
74540
  const agent = rec.agent;
73743
- const telegramDir = join36(resolveAgentDir(agent), "telegram");
74541
+ const telegramDir = join37(resolveAgentDir(agent), "telegram");
73744
74542
  if (rec.source === "github" && rec.delivery_id) {
73745
74543
  const originalTs = dedupStore.check(agent, rec.delivery_id, now);
73746
74544
  if (originalTs !== undefined) {
@@ -73749,7 +74547,7 @@ function recordWebhookEvent(rec, deps = {}) {
73749
74547
  return { status: "deduped", ts: originalTs };
73750
74548
  }
73751
74549
  }
73752
- const logPath = join36(telegramDir, "webhook-events.jsonl");
74550
+ const logPath = join37(telegramDir, "webhook-events.jsonl");
73753
74551
  try {
73754
74552
  mkdirSync26(telegramDir, { recursive: true });
73755
74553
  const record = {
@@ -77197,17 +77995,17 @@ import {
77197
77995
  readFileSync as readFileSync31,
77198
77996
  writeSync as writeSync5
77199
77997
  } from "node:fs";
77200
- import { join as join37 } from "node:path";
77998
+ import { join as join38 } from "node:path";
77201
77999
  import { randomUUID as randomUUID5 } from "node:crypto";
77202
78000
  var PROPOSALS_FILE2 = "skill-proposals.jsonl";
77203
78001
  var REJECTED_FILE2 = "skill-proposals-rejected.jsonl";
77204
78002
  var REJECTION_TTL_MS2 = 90 * 24 * 60 * 60 * 1000;
77205
78003
  var PROPOSAL_SIM_THRESHOLD = 0.5;
77206
78004
  function proposalsPath2(stateDir) {
77207
- return join37(stateDir, PROPOSALS_FILE2);
78005
+ return join38(stateDir, PROPOSALS_FILE2);
77208
78006
  }
77209
78007
  function rejectedPath2(stateDir) {
77210
- return join37(stateDir, REJECTED_FILE2);
78008
+ return join38(stateDir, REJECTED_FILE2);
77211
78009
  }
77212
78010
  function ensureDir3(stateDir) {
77213
78011
  if (!existsSync33(stateDir)) {
@@ -78702,17 +79500,17 @@ import {
78702
79500
  readdirSync as readdirSync6,
78703
79501
  readFileSync as readFileSync37
78704
79502
  } from "fs";
78705
- import { join as join39 } from "path";
79503
+ import { join as join40 } from "path";
78706
79504
 
78707
79505
  // session-tail.ts
78708
- function sanitizeCwdToProjectName(cwd) {
79506
+ function sanitizeCwdToProjectName2(cwd) {
78709
79507
  return cwd.replace(/[^a-zA-Z0-9]/g, "-");
78710
79508
  }
78711
- var MAX_JSONL_LINE_BYTES = 2 * 1024 * 1024;
78712
- var MAX_ERROR_TEXT_CHARS = 500;
78713
- function extractToolResultErrorText(content3) {
79509
+ var MAX_JSONL_LINE_BYTES2 = 2 * 1024 * 1024;
79510
+ var MAX_ERROR_TEXT_CHARS2 = 500;
79511
+ function extractToolResultErrorText2(content3) {
78714
79512
  if (typeof content3 === "string") {
78715
- return content3.slice(0, MAX_ERROR_TEXT_CHARS);
79513
+ return content3.slice(0, MAX_ERROR_TEXT_CHARS2);
78716
79514
  }
78717
79515
  if (Array.isArray(content3)) {
78718
79516
  const parts = [];
@@ -78725,11 +79523,11 @@ function extractToolResultErrorText(content3) {
78725
79523
  }
78726
79524
  }
78727
79525
  return parts.join(`
78728
- `).slice(0, MAX_ERROR_TEXT_CHARS);
79526
+ `).slice(0, MAX_ERROR_TEXT_CHARS2);
78729
79527
  }
78730
79528
  return "";
78731
79529
  }
78732
- function projectAssistantTextBlocks(content3, make) {
79530
+ function projectAssistantTextBlocks2(content3, make) {
78733
79531
  const out = new Map;
78734
79532
  let lastToolUseIdx = -1;
78735
79533
  content3.forEach((c, i) => {
@@ -78746,6 +79544,13 @@ function projectAssistantTextBlocks(content3, make) {
78746
79544
  });
78747
79545
  return out;
78748
79546
  }
79547
+ function sumUsageTokens2(usage) {
79548
+ if (usage == null || typeof usage !== "object")
79549
+ return 0;
79550
+ const u = usage;
79551
+ const n = (v) => typeof v === "number" && Number.isFinite(v) ? v : 0;
79552
+ return n(u.input_tokens) + n(u.output_tokens) + n(u.cache_creation_input_tokens);
79553
+ }
78749
79554
  function assistantLineCarriesAnswerSurface(content3) {
78750
79555
  if (!Array.isArray(content3))
78751
79556
  return false;
@@ -78805,7 +79610,7 @@ function projectSubagentLine(line, agentId, state4) {
78805
79610
  agentId,
78806
79611
  toolUseId: cc.tool_use_id ?? "",
78807
79612
  isError: isError2,
78808
- errorText: isError2 ? extractToolResultErrorText(cc.content) : undefined
79613
+ errorText: isError2 ? extractToolResultErrorText2(cc.content) : undefined
78809
79614
  });
78810
79615
  }
78811
79616
  }
@@ -78821,7 +79626,17 @@ function projectSubagentLine(line, agentId, state4) {
78821
79626
  if (typeof subModel === "string" && !isModelSentinel(subModel)) {
78822
79627
  events.push({ kind: "sub_agent_model", agentId, model: subModel });
78823
79628
  }
78824
- const textEvents = projectAssistantTextBlocks(content3, (text4, blockIndex, lastInMessage) => ({
79629
+ const subUsageTotal = sumUsageTokens2(message?.usage);
79630
+ if (subUsageTotal > 0) {
79631
+ const subMsgId = message?.id;
79632
+ events.push({
79633
+ kind: "sub_agent_usage",
79634
+ agentId,
79635
+ messageId: typeof subMsgId === "string" ? subMsgId : null,
79636
+ totalTokens: subUsageTotal
79637
+ });
79638
+ }
79639
+ const textEvents = projectAssistantTextBlocks2(content3, (text4, blockIndex, lastInMessage) => ({
78825
79640
  kind: "sub_agent_text",
78826
79641
  agentId,
78827
79642
  text: text4,
@@ -78945,7 +79760,7 @@ function sanitiseToolArg(name, raw) {
78945
79760
  case "NotebookEdit": {
78946
79761
  const fp = raw.file_path;
78947
79762
  if (typeof fp === "string" && fp.length > 0)
78948
- out = basename5(fp);
79763
+ out = basename6(fp);
78949
79764
  break;
78950
79765
  }
78951
79766
  case "Bash": {
@@ -78981,7 +79796,7 @@ function sanitiseToolArg(name, raw) {
78981
79796
  out = out.slice(0, SANITISE_MAX_LEN - 1) + "\u2026";
78982
79797
  return out;
78983
79798
  }
78984
- function basename5(p) {
79799
+ function basename6(p) {
78985
79800
  const idx = p.lastIndexOf("/");
78986
79801
  return idx === -1 ? p : p.slice(idx + 1);
78987
79802
  }
@@ -79185,10 +80000,10 @@ import {
79185
80000
  utimesSync,
79186
80001
  writeFileSync as writeFileSync29
79187
80002
  } from "node:fs";
79188
- import { join as join38 } from "node:path";
80003
+ import { join as join39 } from "node:path";
79189
80004
  var TURN_ACTIVE_MARKER_FILE = "turn-active.json";
79190
80005
  function touchTurnActiveMarker(stateDir) {
79191
- const path2 = join38(stateDir, TURN_ACTIVE_MARKER_FILE);
80006
+ const path2 = join39(stateDir, TURN_ACTIVE_MARKER_FILE);
79192
80007
  if (!existsSync34(path2))
79193
80008
  return;
79194
80009
  const now = new Date;
@@ -79355,6 +80170,7 @@ function readSubTail(entry, tail, now, onDescriptionUpdate, fs2, log, db2, paren
79355
80170
  },
79356
80171
  lastTool: entry.lastTool,
79357
80172
  toolCount: entry.toolCount,
80173
+ totalTokens: entry.totalTokens,
79358
80174
  model: entry.currentModel,
79359
80175
  skeleton: true
79360
80176
  });
@@ -79436,6 +80252,7 @@ function readSubTail(entry, tail, now, onDescriptionUpdate, fs2, log, db2, paren
79436
80252
  },
79437
80253
  lastTool: entry.lastTool,
79438
80254
  toolCount: entry.toolCount,
80255
+ totalTokens: entry.totalTokens,
79439
80256
  model: entry.currentModel
79440
80257
  });
79441
80258
  return true;
@@ -79546,6 +80363,15 @@ function readSubTail(entry, tail, now, onDescriptionUpdate, fs2, log, db2, paren
79546
80363
  }
79547
80364
  continue;
79548
80365
  }
80366
+ if (ev.kind === "sub_agent_usage") {
80367
+ if (ev.messageId == null) {
80368
+ entry.totalTokens += ev.totalTokens;
80369
+ } else if (!entry.seenUsageMessageIds.has(ev.messageId)) {
80370
+ entry.seenUsageMessageIds.add(ev.messageId);
80371
+ entry.totalTokens += ev.totalTokens;
80372
+ }
80373
+ continue;
80374
+ }
79549
80375
  if (ev.kind === "sub_agent_tool_use") {
79550
80376
  const narrativeJustFired = resolvePendingSubNarrative(ev.toolName, ev.input);
79551
80377
  if (REPLY_TOOLS2.has(ev.toolName) && typeof ev.input?.text === "string") {
@@ -79574,6 +80400,7 @@ function readSubTail(entry, tail, now, onDescriptionUpdate, fs2, log, db2, paren
79574
80400
  },
79575
80401
  lastTool: entry.lastTool,
79576
80402
  toolCount: entry.toolCount,
80403
+ totalTokens: entry.totalTokens,
79577
80404
  progressLine: toolLine,
79578
80405
  model: entry.currentModel
79579
80406
  });
@@ -79648,7 +80475,7 @@ function readSubTail(entry, tail, now, onDescriptionUpdate, fs2, log, db2, paren
79648
80475
  }
79649
80476
  function startSubagentWatcher(config) {
79650
80477
  const agentDir = config.agentDir;
79651
- const expectedProjectSlug = config.agentCwd != null ? sanitizeCwdToProjectName(config.agentCwd) : null;
80478
+ const expectedProjectSlug = config.agentCwd != null ? sanitizeCwdToProjectName2(config.agentCwd) : null;
79652
80479
  const extraWatchCwdsProvider = config.extraWatchCwdsProvider ?? null;
79653
80480
  const warnedForeignSlugs = new Set;
79654
80481
  const stallThresholdMs = config.stallThresholdMs ?? parseEnvMs("SWITCHROOM_SUBAGENT_STALL_MS") ?? DEFAULT_STALL_THRESHOLD_MS;
@@ -79713,6 +80540,8 @@ function startSubagentWatcher(config) {
79713
80540
  dispatchedAt: n,
79714
80541
  lastActivityAt: n,
79715
80542
  toolCount: 0,
80543
+ totalTokens: 0,
80544
+ seenUsageMessageIds: new Set,
79716
80545
  stallNotified: false,
79717
80546
  stalledAt: null,
79718
80547
  completionNotified: false,
@@ -79849,6 +80678,7 @@ function startSubagentWatcher(config) {
79849
80678
  state: entry.state,
79850
80679
  outcome: entry.errored ? "failed" : entry.historical ? "orphan" : "completed",
79851
80680
  toolCount: entry.toolCount,
80681
+ totalTokens: entry.totalTokens,
79852
80682
  durationMs: nowFn() - entry.dispatchedAt,
79853
80683
  description: entry.description,
79854
80684
  resultText: entry.errored ? entry.lastResultText || entry.errorDetail || "" : entry.lastResultText,
@@ -79869,6 +80699,7 @@ function startSubagentWatcher(config) {
79869
80699
  state: entry.state,
79870
80700
  outcome: "failed",
79871
80701
  toolCount: entry.toolCount,
80702
+ totalTokens: entry.totalTokens,
79872
80703
  durationMs: nowFn() - entry.dispatchedAt,
79873
80704
  description: entry.description,
79874
80705
  resultText: entry.lastResultText,
@@ -80125,8 +80956,8 @@ function startSubagentWatcher(config) {
80125
80956
  if (stopped)
80126
80957
  return;
80127
80958
  pruneVanishedDirWatchers();
80128
- const claudeHome = join39(agentDir, ".claude");
80129
- const projectsRoot = join39(claudeHome, "projects");
80959
+ const claudeHome = join40(agentDir, ".claude");
80960
+ const projectsRoot = join40(claudeHome, "projects");
80130
80961
  if (!fs2.existsSync(projectsRoot))
80131
80962
  return;
80132
80963
  let projectDirs;
@@ -80142,7 +80973,7 @@ function startSubagentWatcher(config) {
80142
80973
  if (extraWatchCwdsProvider != null) {
80143
80974
  try {
80144
80975
  for (const cwd of extraWatchCwdsProvider()) {
80145
- allowedSlugs.add(sanitizeCwdToProjectName(cwd));
80976
+ allowedSlugs.add(sanitizeCwdToProjectName2(cwd));
80146
80977
  }
80147
80978
  } catch (err) {
80148
80979
  providerOk = false;
@@ -80160,7 +80991,7 @@ function startSubagentWatcher(config) {
80160
80991
  continue;
80161
80992
  }
80162
80993
  warnedForeignSlugs.delete(pDir);
80163
- const projectPath = join39(projectsRoot, pDir);
80994
+ const projectPath = join40(projectsRoot, pDir);
80164
80995
  let sessionDirs;
80165
80996
  try {
80166
80997
  sessionDirs = fs2.readdirSync(projectPath);
@@ -80170,7 +81001,7 @@ function startSubagentWatcher(config) {
80170
81001
  for (const sDir of sessionDirs) {
80171
81002
  if (sDir.endsWith(".jsonl"))
80172
81003
  continue;
80173
- const subagentsPath = join39(projectPath, sDir, "subagents");
81004
+ const subagentsPath = join40(projectPath, sDir, "subagents");
80174
81005
  if (!fs2.existsSync(subagentsPath))
80175
81006
  continue;
80176
81007
  const watchAndScan = (dirPath) => {
@@ -80179,7 +81010,7 @@ function startSubagentWatcher(config) {
80179
81010
  const w = fs2.watch(dirPath, (_event, filename) => {
80180
81011
  if (!filename || !filename.toString().startsWith("agent-") || !filename.toString().endsWith(".jsonl"))
80181
81012
  return;
80182
- const filePath = join39(dirPath, filename.toString());
81013
+ const filePath = join40(dirPath, filename.toString());
80183
81014
  if (!knownFiles.has(filePath)) {
80184
81015
  scanSubagentsDir(dirPath);
80185
81016
  }
@@ -80193,7 +81024,7 @@ function startSubagentWatcher(config) {
80193
81024
  scanSubagentsDir(dirPath);
80194
81025
  };
80195
81026
  watchAndScan(subagentsPath);
80196
- const workflowsPath = join39(subagentsPath, "workflows");
81027
+ const workflowsPath = join40(subagentsPath, "workflows");
80197
81028
  if (fs2.existsSync(workflowsPath)) {
80198
81029
  let wfDirs;
80199
81030
  try {
@@ -80203,7 +81034,7 @@ function startSubagentWatcher(config) {
80203
81034
  }
80204
81035
  for (const wfDir of wfDirs) {
80205
81036
  try {
80206
- const wfPath = join39(workflowsPath, wfDir);
81037
+ const wfPath = join40(workflowsPath, wfDir);
80207
81038
  if (!fs2.statSync(wfPath).isDirectory())
80208
81039
  continue;
80209
81040
  watchAndScan(wfPath);
@@ -80223,7 +81054,7 @@ function startSubagentWatcher(config) {
80223
81054
  for (const e of entries) {
80224
81055
  if (!e.startsWith("agent-") || !e.endsWith(".jsonl"))
80225
81056
  continue;
80226
- const filePath = join39(subagentsPath, e);
81057
+ const filePath = join40(subagentsPath, e);
80227
81058
  if (knownFiles.has(filePath))
80228
81059
  continue;
80229
81060
  const agentId = e.slice("agent-".length, -".jsonl".length);
@@ -80335,13 +81166,13 @@ import {
80335
81166
  existsSync as existsSync36,
80336
81167
  renameSync as renameSync15
80337
81168
  } from "node:fs";
80338
- import { join as join40, resolve as resolve8 } from "node:path";
80339
- import { homedir as homedir13 } from "node:os";
81169
+ import { join as join41, resolve as resolve8 } from "node:path";
81170
+ import { homedir as homedir14 } from "node:os";
80340
81171
  function registryDir() {
80341
- return resolve8(process.env.SWITCHROOM_WORKTREE_DIR ?? join40(homedir13(), ".switchroom", "worktrees"));
81172
+ return resolve8(process.env.SWITCHROOM_WORKTREE_DIR ?? join41(homedir14(), ".switchroom", "worktrees"));
80342
81173
  }
80343
81174
  function recordPath(id) {
80344
- return join40(registryDir(), `${id}.json`);
81175
+ return join41(registryDir(), `${id}.json`);
80345
81176
  }
80346
81177
  function ensureDir4() {
80347
81178
  mkdirSync29(registryDir(), { recursive: true });
@@ -80392,12 +81223,12 @@ function recordExists(id) {
80392
81223
 
80393
81224
  // worktree-watch-cwds.ts
80394
81225
  import { realpathSync as realpathSync3 } from "node:fs";
80395
- import { basename as basename7 } from "node:path";
81226
+ import { basename as basename8 } from "node:path";
80396
81227
  var identityEscalated = false;
80397
81228
  function defaultDeriveName(agentDir) {
80398
81229
  if (!agentDir || agentDir.trim().length === 0)
80399
81230
  return "";
80400
- const leaf = basename7(agentDir).trim();
81231
+ const leaf = basename8(agentDir).trim();
80401
81232
  return leaf;
80402
81233
  }
80403
81234
  function resolveOwnerIdentity(self, agentDir, deriveName) {
@@ -80520,14 +81351,14 @@ init_boot_card();
80520
81351
 
80521
81352
  // gateway/update-announce.ts
80522
81353
  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";
81354
+ import { join as join46 } from "node:path";
81355
+ import { homedir as homedir16 } from "node:os";
80525
81356
 
80526
81357
  // ../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");
81358
+ import { homedir as homedir15 } from "node:os";
81359
+ import { join as join45 } from "node:path";
81360
+ function defaultAuditLogPath(home2 = homedir15()) {
81361
+ return join45(home2, ".switchroom", "host-control-audit.log");
80531
81362
  }
80532
81363
  function parseAuditLine(line) {
80533
81364
  const trimmed = line.trim();
@@ -80712,15 +81543,15 @@ function renderUpdateOutcomeLine(entry) {
80712
81543
  `);
80713
81544
  }
80714
81545
  function claimUpdateAnnouncement(requestId, opts = {}) {
80715
- const stateDir = opts.stateDir ?? process.env.TELEGRAM_STATE_DIR ?? join45(homedir15(), ".switchroom");
80716
- const dir = join45(stateDir, "update-announced");
81546
+ const stateDir = opts.stateDir ?? process.env.TELEGRAM_STATE_DIR ?? join46(homedir16(), ".switchroom");
81547
+ const dir = join46(stateDir, "update-announced");
80717
81548
  try {
80718
81549
  mkdirSync33(dir, { recursive: true });
80719
81550
  } catch {
80720
81551
  return false;
80721
81552
  }
80722
81553
  const safeId = requestId.replace(/[^A-Za-z0-9_.-]/g, "_").slice(0, 200);
80723
- const path2 = join45(dir, safeId);
81554
+ const path2 = join46(dir, safeId);
80724
81555
  try {
80725
81556
  const fd = openSync9(path2, "wx");
80726
81557
  closeSync9(fd);
@@ -80942,7 +81773,7 @@ function createIssuesCardHandle(opts) {
80942
81773
 
80943
81774
  // issues-watcher.ts
80944
81775
  import { existsSync as existsSync43, statSync as statSync12 } from "node:fs";
80945
- import { join as join47 } from "node:path";
81776
+ import { join as join48 } from "node:path";
80946
81777
 
80947
81778
  // ../src/issues/store.ts
80948
81779
  import {
@@ -80958,7 +81789,7 @@ import {
80958
81789
  writeFileSync as writeFileSync36,
80959
81790
  writeSync as writeSync6
80960
81791
  } from "node:fs";
80961
- import { join as join46 } from "node:path";
81792
+ import { join as join47 } from "node:path";
80962
81793
  import { randomBytes as randomBytes7 } from "node:crypto";
80963
81794
  import { execSync } from "node:child_process";
80964
81795
 
@@ -80977,7 +81808,7 @@ init_redact();
80977
81808
  var ISSUES_FILE = "issues.jsonl";
80978
81809
  var ISSUES_LOCK = "issues.lock";
80979
81810
  function readAll(stateDir) {
80980
- const path2 = join46(stateDir, ISSUES_FILE);
81811
+ const path2 = join47(stateDir, ISSUES_FILE);
80981
81812
  if (!existsSync42(path2))
80982
81813
  return [];
80983
81814
  let raw;
@@ -81014,7 +81845,7 @@ function list2(stateDir, opts = {}) {
81014
81845
  });
81015
81846
  }
81016
81847
  function resolve9(stateDir, fingerprint, nowFn = Date.now) {
81017
- if (!existsSync42(join46(stateDir, ISSUES_FILE)))
81848
+ if (!existsSync42(join47(stateDir, ISSUES_FILE)))
81018
81849
  return 0;
81019
81850
  return withLock(stateDir, () => {
81020
81851
  const all2 = readAll(stateDir);
@@ -81032,7 +81863,7 @@ function resolve9(stateDir, fingerprint, nowFn = Date.now) {
81032
81863
  });
81033
81864
  }
81034
81865
  function writeAll(stateDir, events) {
81035
- const path2 = join46(stateDir, ISSUES_FILE);
81866
+ const path2 = join47(stateDir, ISSUES_FILE);
81036
81867
  sweepOrphanTmpFiles(stateDir);
81037
81868
  const tmp = `${path2}.tmp-${process.pid}-${randomBytes7(4).toString("hex")}`;
81038
81869
  const body = events.length === 0 ? "" : events.map((e) => JSON.stringify(e)).join(`
@@ -81054,7 +81885,7 @@ function sweepOrphanTmpFiles(stateDir) {
81054
81885
  for (const entry of entries) {
81055
81886
  if (!entry.startsWith(TMP_PREFIX))
81056
81887
  continue;
81057
- const tmpPath2 = join46(stateDir, entry);
81888
+ const tmpPath2 = join47(stateDir, entry);
81058
81889
  try {
81059
81890
  const stat = statSync11(tmpPath2);
81060
81891
  if (stat.mtimeMs < cutoff) {
@@ -81066,7 +81897,7 @@ function sweepOrphanTmpFiles(stateDir) {
81066
81897
  var LOCK_RETRY_MS = 25;
81067
81898
  var LOCK_TIMEOUT_MS = 1e4;
81068
81899
  function withLock(stateDir, fn) {
81069
- const lockPath = join46(stateDir, ISSUES_LOCK);
81900
+ const lockPath = join47(stateDir, ISSUES_LOCK);
81070
81901
  const startedAt = Date.now();
81071
81902
  let fd = null;
81072
81903
  while (fd === null) {
@@ -81151,7 +81982,7 @@ function isIssueEvent(v) {
81151
81982
  // issues-watcher.ts
81152
81983
  var DEFAULT_POLL_INTERVAL_MS2 = 2000;
81153
81984
  function startIssuesWatcher(opts) {
81154
- const path2 = join47(opts.stateDir, ISSUES_FILE);
81985
+ const path2 = join48(opts.stateDir, ISSUES_FILE);
81155
81986
  const log = opts.log ?? (() => {});
81156
81987
  const intervalMs = opts.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS2;
81157
81988
  const setIntervalFn = opts.setInterval ?? setInterval;
@@ -81213,10 +82044,10 @@ function defaultReadEvents(stateDir) {
81213
82044
  }
81214
82045
  // permission-title.ts
81215
82046
  init_card_format();
81216
- import { basename as basename9 } from "node:path";
82047
+ import { basename as basename10 } from "node:path";
81217
82048
 
81218
82049
  // permission-rule.ts
81219
- import { basename as basename8 } from "node:path";
82050
+ import { basename as basename9 } from "node:path";
81220
82051
  var FILE_TOOLS = new Set([
81221
82052
  "Edit",
81222
82053
  "Write",
@@ -81294,7 +82125,7 @@ function skillBasenameFromPath(input) {
81294
82125
  if (!path2)
81295
82126
  return null;
81296
82127
  const trimmed = path2.replace(/\/SKILL\.md$/i, "").replace(/\/$/, "");
81297
- return basename8(trimmed) || null;
82128
+ return basename9(trimmed) || null;
81298
82129
  }
81299
82130
  function matchesAllowRule(rule, toolName, inputPreview) {
81300
82131
  if (!rule || !toolName)
@@ -81586,11 +82417,11 @@ function describeGrant(toolName, inputPreview, option) {
81586
82417
  return m ? `run ${m[1]} commands` : "run that command";
81587
82418
  }
81588
82419
  if (t === "Edit" || t === "MultiEdit" || t === "NotebookEdit")
81589
- return `edit ${basename9(arg)}`;
82420
+ return `edit ${basename10(arg)}`;
81590
82421
  if (t === "Write")
81591
- return `write ${basename9(arg)}`;
82422
+ return `write ${basename10(arg)}`;
81592
82423
  if (t === "Read")
81593
- return `read ${basename9(arg)}`;
82424
+ return `read ${basename10(arg)}`;
81594
82425
  return naturalAction(toolName, inputPreview);
81595
82426
  }
81596
82427
  switch (rule) {
@@ -81629,12 +82460,12 @@ function fileBase(input, rawPreview) {
81629
82460
  if (input) {
81630
82461
  const p = readString2(input, "file_path") ?? readString2(input, "notebook_path");
81631
82462
  if (p)
81632
- return basename9(p);
82463
+ return basename10(p);
81633
82464
  }
81634
82465
  if (rawPreview) {
81635
82466
  const p = extractFilePathFromRaw2(rawPreview);
81636
82467
  if (p)
81637
- return basename9(p);
82468
+ return basename10(p);
81638
82469
  }
81639
82470
  return null;
81640
82471
  }
@@ -81765,7 +82596,7 @@ function truncate6(text4, max) {
81765
82596
  }
81766
82597
 
81767
82598
  // permission-rule.ts
81768
- import { basename as basename10 } from "node:path";
82599
+ import { basename as basename11 } from "node:path";
81769
82600
  var FILE_TOOLS2 = new Set([
81770
82601
  "Edit",
81771
82602
  "Write",
@@ -81897,14 +82728,14 @@ function skillBasenameFromPath3(input) {
81897
82728
  if (!path2)
81898
82729
  return null;
81899
82730
  const trimmed = path2.replace(/\/SKILL\.md$/i, "").replace(/\/$/, "");
81900
- return basename10(trimmed) || null;
82731
+ return basename11(trimmed) || null;
81901
82732
  }
81902
82733
  function isRulePersisted(resolvedAllow, ruleRule) {
81903
82734
  return resolvedAllow.includes(ruleRule);
81904
82735
  }
81905
82736
 
81906
82737
  // scoped-approval.ts
81907
- import { basename as basename11 } from "node:path";
82738
+ import { basename as basename12 } from "node:path";
81908
82739
  var SCOPED_APPROVAL_DEFAULT_TTL_MS = 30 * 60 * 1000;
81909
82740
  function scopedApprovalTtlMs(env = process.env) {
81910
82741
  const raw = env.SWITCHROOM_SCOPED_APPROVAL_TTL_MS;
@@ -81929,7 +82760,7 @@ function resolveTimeBox(toolName, inputPreview, choices) {
81929
82760
  const fileMatch = FILE_RULE.exec(rule);
81930
82761
  if (fileMatch) {
81931
82762
  const verb = fileMatch[1] === "Read" ? "reads of" : "edits to";
81932
- return { rule, breadth: `${verb} ${basename11(fileMatch[2])}` };
82763
+ return { rule, breadth: `${verb} ${basename12(fileMatch[2])}` };
81933
82764
  }
81934
82765
  const bashMatch = BASH_FAMILY_RULE.exec(rule);
81935
82766
  if (bashMatch) {
@@ -82030,7 +82861,7 @@ function readBashCommand(inputPreview) {
82030
82861
 
82031
82862
  // gateway/scoped-grant-store.ts
82032
82863
  import { readFileSync as readFileSync47, writeFileSync as writeFileSync37 } from "node:fs";
82033
- import { join as join48 } from "node:path";
82864
+ import { join as join49 } from "node:path";
82034
82865
 
82035
82866
  // scoped-approval.ts
82036
82867
  var SCOPED_APPROVAL_DEFAULT_TTL_MS2 = 30 * 60 * 1000;
@@ -82072,7 +82903,7 @@ function scopedGrantPersistEnabled(env = process.env) {
82072
82903
  return env.SWITCHROOM_SCOPED_GRANT_PERSIST !== "0";
82073
82904
  }
82074
82905
  function createScopedGrantStore(stateDir, env = process.env) {
82075
- const filePath = join48(stateDir, "scoped-grants.json");
82906
+ const filePath = join49(stateDir, "scoped-grants.json");
82076
82907
  const enabled8 = scopedGrantPersistEnabled(env);
82077
82908
  function read() {
82078
82909
  try {
@@ -82446,7 +83277,7 @@ function isDiffPreApproved(agentName3, unifiedDiff, deps) {
82446
83277
  // credits-watch.ts
82447
83278
  init_card_format();
82448
83279
  import { readFileSync as readFileSync48, writeFileSync as writeFileSync38, existsSync as existsSync44, mkdirSync as mkdirSync35 } from "fs";
82449
- import { join as join49 } from "path";
83280
+ import { join as join50 } from "path";
82450
83281
  var STATE_FILE = "credits-watch.json";
82451
83282
  var DEFAULT_CREDIT_FATAL_REASONS = new Set;
82452
83283
  var KNOWN_CREDIT_REASONS = [
@@ -82468,7 +83299,7 @@ function emptyCreditState() {
82468
83299
  return { lastNotifiedReason: null, lastNotifiedAt: 0 };
82469
83300
  }
82470
83301
  function readClaudeJsonOverage(claudeConfigDir) {
82471
- const path2 = join49(claudeConfigDir, ".claude.json");
83302
+ const path2 = join50(claudeConfigDir, ".claude.json");
82472
83303
  if (!existsSync44(path2))
82473
83304
  return null;
82474
83305
  let raw;
@@ -82551,7 +83382,7 @@ function humanizeReason(reason) {
82551
83382
  }
82552
83383
  }
82553
83384
  function loadCreditState(stateDir) {
82554
- const path2 = join49(stateDir, STATE_FILE);
83385
+ const path2 = join50(stateDir, STATE_FILE);
82555
83386
  if (!existsSync44(path2))
82556
83387
  return emptyCreditState();
82557
83388
  try {
@@ -82568,7 +83399,7 @@ function loadCreditState(stateDir) {
82568
83399
  }
82569
83400
  function saveCreditState(stateDir, state4) {
82570
83401
  mkdirSync35(stateDir, { recursive: true });
82571
- const path2 = join49(stateDir, STATE_FILE);
83402
+ const path2 = join50(stateDir, STATE_FILE);
82572
83403
  writeFileSync38(path2, JSON.stringify(state4, null, 2) + `
82573
83404
  `, { mode: 384 });
82574
83405
  }
@@ -82577,7 +83408,7 @@ function saveCreditState(stateDir, state4) {
82577
83408
  init_auth_snapshot_format();
82578
83409
  init_card_format();
82579
83410
  import { readFileSync as readFileSync49, writeFileSync as writeFileSync39, existsSync as existsSync45, mkdirSync as mkdirSync36 } from "fs";
82580
- import { join as join50 } from "path";
83411
+ import { join as join51 } from "path";
82581
83412
  var STATE_FILE2 = "quota-watch.json";
82582
83413
  function emptyQuotaWatchState() {
82583
83414
  return {};
@@ -82815,7 +83646,7 @@ function buildRecoveryMessage(agentName3, snap) {
82815
83646
  `);
82816
83647
  }
82817
83648
  function loadQuotaWatchState(stateDir) {
82818
- const path2 = join50(stateDir, STATE_FILE2);
83649
+ const path2 = join51(stateDir, STATE_FILE2);
82819
83650
  if (!existsSync45(path2))
82820
83651
  return emptyQuotaWatchState();
82821
83652
  try {
@@ -82837,7 +83668,7 @@ function loadQuotaWatchState(stateDir) {
82837
83668
  }
82838
83669
  function saveQuotaWatchState(stateDir, state4) {
82839
83670
  mkdirSync36(stateDir, { recursive: true });
82840
- const path2 = join50(stateDir, STATE_FILE2);
83671
+ const path2 = join51(stateDir, STATE_FILE2);
82841
83672
  writeFileSync39(path2, JSON.stringify(state4, null, 2) + `
82842
83673
  `, { mode: 384 });
82843
83674
  }
@@ -82895,17 +83726,17 @@ import {
82895
83726
  utimesSync as utimesSync2,
82896
83727
  writeFileSync as writeFileSync40
82897
83728
  } from "node:fs";
82898
- import { join as join51 } from "node:path";
83729
+ import { join as join52 } from "node:path";
82899
83730
  var TURN_ACTIVE_MARKER_FILE2 = "turn-active.json";
82900
83731
  function writeTurnActiveMarker(stateDir, marker) {
82901
83732
  try {
82902
83733
  mkdirSync37(stateDir, { recursive: true });
82903
- writeFileSync40(join51(stateDir, TURN_ACTIVE_MARKER_FILE2), JSON.stringify(marker, null, 2) + `
83734
+ writeFileSync40(join52(stateDir, TURN_ACTIVE_MARKER_FILE2), JSON.stringify(marker, null, 2) + `
82904
83735
  `, { mode: 384 });
82905
83736
  } catch {}
82906
83737
  }
82907
83738
  function touchTurnActiveMarker2(stateDir) {
82908
- const path2 = join51(stateDir, TURN_ACTIVE_MARKER_FILE2);
83739
+ const path2 = join52(stateDir, TURN_ACTIVE_MARKER_FILE2);
82909
83740
  if (!existsSync46(path2))
82910
83741
  return;
82911
83742
  const now = new Date;
@@ -82920,11 +83751,11 @@ function touchTurnActiveMarker2(stateDir) {
82920
83751
  }
82921
83752
  function removeTurnActiveMarker(stateDir) {
82922
83753
  try {
82923
- unlinkSync21(join51(stateDir, TURN_ACTIVE_MARKER_FILE2));
83754
+ unlinkSync21(join52(stateDir, TURN_ACTIVE_MARKER_FILE2));
82924
83755
  } catch {}
82925
83756
  }
82926
83757
  function sweepStaleTurnActiveMarker(stateDir, opts) {
82927
- const path2 = join51(stateDir, TURN_ACTIVE_MARKER_FILE2);
83758
+ const path2 = join52(stateDir, TURN_ACTIVE_MARKER_FILE2);
82928
83759
  if (!existsSync46(path2))
82929
83760
  return false;
82930
83761
  const now = opts.now ?? Date.now();
@@ -82955,7 +83786,7 @@ function sweepStaleTurnActiveMarker(stateDir, opts) {
82955
83786
  }
82956
83787
  }
82957
83788
  function readTurnActiveMarkerAgeMs(stateDir, now) {
82958
- const path2 = join51(stateDir, TURN_ACTIVE_MARKER_FILE2);
83789
+ const path2 = join52(stateDir, TURN_ACTIVE_MARKER_FILE2);
82959
83790
  try {
82960
83791
  const st = statSync13(path2);
82961
83792
  return (now ?? Date.now()) - st.mtimeMs;
@@ -82965,10 +83796,10 @@ function readTurnActiveMarkerAgeMs(stateDir, now) {
82965
83796
  }
82966
83797
 
82967
83798
  // ../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;
83799
+ var VERSION = "0.18.26";
83800
+ var COMMIT_SHA = "ceb7d1a9";
83801
+ var COMMIT_DATE = "2026-07-15T11:05:52Z";
83802
+ var LATEST_PR = 3258;
82972
83803
  var COMMITS_AHEAD_OF_TAG = 0;
82973
83804
 
82974
83805
  // gateway/boot-version.ts
@@ -83045,12 +83876,12 @@ init_protocol();
83045
83876
  init_peercred();
83046
83877
  import * as net5 from "node:net";
83047
83878
  import * as fs2 from "node:fs";
83048
- import { homedir as homedir16 } from "node:os";
83049
- import { join as join52 } from "node:path";
83879
+ import { homedir as homedir17 } from "node:os";
83880
+ import { join as join53 } from "node:path";
83050
83881
  var DEFAULT_TIMEOUT_MS4 = 2000;
83051
83882
  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");
83883
+ var LEGACY_SOCKET_PATH2 = join53(homedir17(), ".switchroom", "vault-broker.sock");
83884
+ var OPERATOR_SOCKET_PATH2 = join53(homedir17(), ".switchroom", "broker-operator", "sock");
83054
83885
  function defaultBrokerSocketPath2() {
83055
83886
  if (fs2.existsSync(OPERATOR_SOCKET_PATH2))
83056
83887
  return OPERATOR_SOCKET_PATH2;
@@ -83933,7 +84764,7 @@ function resolveVaultApprovalPosture(broker) {
83933
84764
 
83934
84765
  // registry/turns-schema.ts
83935
84766
  import { chmodSync as chmodSync10, mkdirSync as mkdirSync38 } from "fs";
83936
- import { join as join53 } from "path";
84767
+ import { join as join54 } from "path";
83937
84768
  var DatabaseClass2 = null;
83938
84769
  function loadDatabaseClass2() {
83939
84770
  if (DatabaseClass2 != null)
@@ -83984,12 +84815,16 @@ var PHASE2_MIGRATIONS = [
83984
84815
  var PHASE3_MIGRATIONS = [
83985
84816
  `ALTER TABLE turns ADD COLUMN resumed_at INTEGER`
83986
84817
  ];
84818
+ var PHASE4_MIGRATIONS = [
84819
+ `ALTER TABLE turns ADD COLUMN session_id TEXT`,
84820
+ `ALTER TABLE turns ADD COLUMN answer_redelivered_at INTEGER`
84821
+ ];
83987
84822
  function applySchema(db2) {
83988
84823
  db2.exec("PRAGMA journal_mode = WAL");
83989
84824
  db2.exec("PRAGMA synchronous = NORMAL");
83990
84825
  db2.exec("PRAGMA busy_timeout = 5000");
83991
84826
  db2.exec(SCHEMA_SQL);
83992
- for (const sql of [...PHASE1_MIGRATIONS, ...PHASE2_MIGRATIONS, ...PHASE3_MIGRATIONS]) {
84827
+ for (const sql of [...PHASE1_MIGRATIONS, ...PHASE2_MIGRATIONS, ...PHASE3_MIGRATIONS, ...PHASE4_MIGRATIONS]) {
83993
84828
  try {
83994
84829
  db2.exec(sql);
83995
84830
  } catch (err) {
@@ -84001,9 +84836,9 @@ function applySchema(db2) {
84001
84836
  }
84002
84837
  function openTurnsDb(agentDir) {
84003
84838
  const Database = loadDatabaseClass2();
84004
- const dir = join53(agentDir, "telegram");
84839
+ const dir = join54(agentDir, "telegram");
84005
84840
  mkdirSync38(dir, { recursive: true, mode: 448 });
84006
- const path2 = join53(dir, "registry.db");
84841
+ const path2 = join54(dir, "registry.db");
84007
84842
  const db2 = new Database(path2, { create: true });
84008
84843
  applySchema(db2);
84009
84844
  try {
@@ -84032,6 +84867,8 @@ function mapRow(row) {
84032
84867
  tool_call_count: row.tool_call_count,
84033
84868
  interrupt_reason: row.interrupt_reason,
84034
84869
  resumed_at: row.resumed_at,
84870
+ session_id: row.session_id ?? null,
84871
+ answer_redelivered_at: row.answer_redelivered_at ?? null,
84035
84872
  created_at: row.created_at,
84036
84873
  updated_at: row.updated_at
84037
84874
  };
@@ -84127,6 +84964,24 @@ function markTurnResumed(db2, turnKey2, now = Date.now()) {
84127
84964
  WHERE turn_key = ? AND resumed_at IS NULL
84128
84965
  `).run(now, now, turnKey2);
84129
84966
  }
84967
+ function stampTurnSessionId(db2, turnKey2, sessionId, now = Date.now()) {
84968
+ if (!sessionId)
84969
+ return;
84970
+ db2.prepare(`
84971
+ UPDATE turns
84972
+ SET session_id = ?,
84973
+ updated_at = ?
84974
+ WHERE turn_key = ? AND session_id IS NULL
84975
+ `).run(sessionId, now, turnKey2);
84976
+ }
84977
+ function markAnswerRedelivered(db2, turnKey2, now = Date.now()) {
84978
+ db2.prepare(`
84979
+ UPDATE turns
84980
+ SET answer_redelivered_at = ?,
84981
+ updated_at = ?
84982
+ WHERE turn_key = ? AND answer_redelivered_at IS NULL
84983
+ `).run(now, now, turnKey2);
84984
+ }
84130
84985
  function findLatestTurnIfInterrupted(db2) {
84131
84986
  const row = db2.prepare(`
84132
84987
  SELECT * FROM turns
@@ -84756,7 +85611,7 @@ installGlobalErrorHandlers();
84756
85611
  process.on("beforeExit", () => {
84757
85612
  shutdownAnalytics();
84758
85613
  });
84759
- var STATE_DIR = process.env.TELEGRAM_STATE_DIR ?? join54(homedir17(), ".claude", "channels", "telegram");
85614
+ var STATE_DIR = process.env.TELEGRAM_STATE_DIR ?? join55(homedir18(), ".claude", "channels", "telegram");
84760
85615
  var permCardStore = createPermissionCardStore(STATE_DIR);
84761
85616
  var BLOCKED_APPROVALS_DIR = process.env.SWITCHROOM_BLOCKED_APPROVALS_DIR ?? "/state/blocked-approvals";
84762
85617
  var AGENT_NAME = process.env.SWITCHROOM_AGENT_NAME ?? "agent";
@@ -84856,11 +85711,11 @@ function scheduleAlwaysAllowPersistDrain() {
84856
85711
  }, ALWAYS_ALLOW_DRAIN_INTERVAL_MS);
84857
85712
  timer3.unref?.();
84858
85713
  }
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");
85714
+ var ACCESS_FILE = join55(STATE_DIR, "access.json");
85715
+ var APPROVED_DIR = join55(STATE_DIR, "approved");
85716
+ var ENV_FILE = join55(STATE_DIR, ".env");
85717
+ var INBOX_DIR = join55(STATE_DIR, "inbox");
85718
+ var PEOPLE_FILE = join55(STATE_DIR, "people.json");
84864
85719
  function triggerSelfRestart(targetAgent, reason, delayMs = 300) {
84865
85720
  const isDocker = process.env.SWITCHROOM_RUNTIME === "docker";
84866
85721
  const selfAgent = process.env.SWITCHROOM_AGENT_NAME;
@@ -85057,7 +85912,7 @@ function assertSendable(f) {
85057
85912
  } catch {
85058
85913
  throw new Error(`refusing to send file \u2014 cannot resolve real path: ${f}`);
85059
85914
  }
85060
- const inbox = join54(stateReal, "inbox");
85915
+ const inbox = join55(stateReal, "inbox");
85061
85916
  if (real.startsWith(stateReal + sep3) && !real.startsWith(inbox + sep3)) {
85062
85917
  throw new Error(`refusing to send channel state: ${f}`);
85063
85918
  }
@@ -85182,7 +86037,7 @@ var HISTORY_ENABLED = HISTORY_ACCESS.historyEnabled !== false;
85182
86037
  if (HISTORY_ENABLED) {
85183
86038
  try {
85184
86039
  initHistory(STATE_DIR, HISTORY_ACCESS.historyRetentionDays ?? 30);
85185
- process.stderr.write(`telegram gateway: history capture enabled at ${join54(STATE_DIR, "history.db")}
86040
+ process.stderr.write(`telegram gateway: history capture enabled at ${join55(STATE_DIR, "history.db")}
85186
86041
  `);
85187
86042
  } catch (err) {
85188
86043
  process.stderr.write(`telegram gateway: history init failed (${err.message}) \u2014 capture disabled
@@ -85191,6 +86046,7 @@ if (HISTORY_ENABLED) {
85191
86046
  }
85192
86047
  var turnsDb = null;
85193
86048
  var bootResumeInbound = null;
86049
+ var pendingRedelivery = null;
85194
86050
  var bridgeDeadPriorStreak = 0;
85195
86051
  try {
85196
86052
  const agentDir = STATE_DIR.endsWith("/telegram") ? STATE_DIR.slice(0, -"/telegram".length) : STATE_DIR;
@@ -85199,7 +86055,7 @@ try {
85199
86055
  let markerTurnKey = null;
85200
86056
  let markerAgeMs = null;
85201
86057
  try {
85202
- const markerPath = join54(STATE_DIR, TURN_ACTIVE_MARKER_FILE2);
86058
+ const markerPath = join55(STATE_DIR, TURN_ACTIVE_MARKER_FILE2);
85203
86059
  if (existsSync50(markerPath)) {
85204
86060
  const st = statSync16(markerPath);
85205
86061
  markerAgeMs = Date.now() - st.mtimeMs;
@@ -85224,10 +86080,10 @@ try {
85224
86080
  process.stderr.write(`telegram gateway: turn-registry boot-reaper stamped ${reaped} orphaned turn(s)${timeoutTurnKey ? ` (turnKey=${timeoutTurnKey} as 'timeout', markerAgeMs=${markerAgeMs})` : " as 'restart'"}
85225
86081
  `);
85226
86082
  } else {
85227
- process.stderr.write(`telegram gateway: turn-registry initialized at ${join54(agentDir, "telegram", "registry.db")}
86083
+ process.stderr.write(`telegram gateway: turn-registry initialized at ${join55(agentDir, "telegram", "registry.db")}
85228
86084
  `);
85229
86085
  }
85230
- const bridgeDeadMarker = consumeBridgeDeadEscalationMarker(join54(STATE_DIR, "bridge-dead-escalation.json"));
86086
+ const bridgeDeadMarker = consumeBridgeDeadEscalationMarker(join55(STATE_DIR, "bridge-dead-escalation.json"));
85231
86087
  if (bridgeDeadMarker != null) {
85232
86088
  bridgeDeadPriorStreak = bridgeDeadMarker.count ?? 1;
85233
86089
  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 +86096,7 @@ try {
85240
86096
  const pending2 = findLatestTurnIfInterrupted(turnsDb);
85241
86097
  const selfAgent = process.env.SWITCHROOM_AGENT_NAME ?? "";
85242
86098
  if (pending2 != null && selfAgent) {
85243
- const bootResumeMarkerPath = process.env.SWITCHROOM_GATEWAY_CLEAN_SHUTDOWN_MARKER ?? join54(STATE_DIR, "clean-shutdown.json");
86099
+ const bootResumeMarkerPath = process.env.SWITCHROOM_GATEWAY_CLEAN_SHUTDOWN_MARKER ?? join55(STATE_DIR, "clean-shutdown.json");
85244
86100
  const bootResumeCleanMarker = readCleanShutdownMarker(bootResumeMarkerPath);
85245
86101
  const bootResumeForceAlways = process.env.SWITCHROOM_BOOT_RESUME_ALWAYS === "1";
85246
86102
  const bootResumeMode = parseBootResumeMode(process.env.SWITCHROOM_BOOT_RESUME);
@@ -85258,6 +86114,19 @@ try {
85258
86114
  ageMs: Math.max(0, Date.now() - pending2.started_at),
85259
86115
  maxAgeMs: RESUME_MAX_AGE_MS
85260
86116
  });
86117
+ const redeliverCapture = decideRedeliverCapture({
86118
+ willBeResumed: bootResumeKind === "resume",
86119
+ hasSessionId: Boolean(pending2.session_id)
86120
+ });
86121
+ if (redeliverCapture.capture) {
86122
+ pendingRedelivery = { turn: pending2, maxAgeMs: RESUME_MAX_AGE_MS };
86123
+ } else if (redeliverCapture.skipReason === "will-be-resumed") {
86124
+ 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)
86125
+ `);
86126
+ } else {
86127
+ 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
86128
+ `);
86129
+ }
85261
86130
  let interruptedSubagents = [];
85262
86131
  try {
85263
86132
  interruptedSubagents = listNonTerminalSubagentsForTurn(turnsDb, pending2.turn_key).map((s) => ({ agentType: s.agent_type, description: s.description, status: s.status }));
@@ -85340,7 +86209,7 @@ try {
85340
86209
  `);
85341
86210
  }
85342
86211
  }
85343
- const pendingEnvPath = join54(agentDir, ".pending-turn.env");
86212
+ const pendingEnvPath = join55(agentDir, ".pending-turn.env");
85344
86213
  try {
85345
86214
  if (pending2 != null) {
85346
86215
  const lines = [
@@ -85396,6 +86265,10 @@ function resolveSubagentOriginChat(agentId) {
85396
86265
  var WORKER_FEED_FALLBACK_LOG_CAP = 256;
85397
86266
  var WORKER_FEED_STALE_TTL_MARGIN_MS = 300000;
85398
86267
  var WORKER_FEED_ABSOLUTE_ROW_LIFETIME_CAP_MULTIPLE = 4;
86268
+ var WORKER_FEED_GROUP_MESSAGE_LIFETIME_CAP_MS = (() => {
86269
+ const v = Number(process.env.SWITCHROOM_WORKER_FEED_GROUP_MESSAGE_LIFETIME_CAP_MS);
86270
+ return Number.isFinite(v) && v > 0 ? v : 3600000;
86271
+ })();
85399
86272
  var workerFeedOwnerDmFallbackLogged = new Set;
85400
86273
  function resolveWorkerFeedChat(agentId, fleetChatId) {
85401
86274
  const origin = resolveSubagentOriginChat(agentId);
@@ -85458,7 +86331,7 @@ function checkApprovals() {
85458
86331
  return;
85459
86332
  }
85460
86333
  for (const senderId of files) {
85461
- const file = join54(APPROVED_DIR, senderId);
86334
+ const file = join55(APPROVED_DIR, senderId);
85462
86335
  bot.api.sendMessage(senderId, "Paired! Say hi to Claude.").then(() => rmSync6(file, { force: true }), (err) => {
85463
86336
  process.stderr.write(`telegram gateway: failed to send approval confirm: ${err}
85464
86337
  `);
@@ -85563,7 +86436,7 @@ function noteAgentOutputAt(key, ts) {
85563
86436
  lastAgentOutputAt.delete(oldest);
85564
86437
  }
85565
86438
  }
85566
- var OBLIGATION_STORE_PATH = join54(STATE_DIR, "obligations.json");
86439
+ var OBLIGATION_STORE_PATH = join55(STATE_DIR, "obligations.json");
85567
86440
  var obligationStoreFs = {
85568
86441
  readFileSync: (p) => readFileSync54(p, "utf8"),
85569
86442
  writeFileSync: (p, d) => writeFileSync43(p, d),
@@ -85678,6 +86551,7 @@ var pendingRestarts = new Map;
85678
86551
  var pendingSessionCommand = createPendingSessionCommandSlots();
85679
86552
  var PENDING_CMD_DRAIN_CAP_MS = 60000;
85680
86553
  var lastSessionActiveFile = null;
86554
+ var lastSessionStampedTurnKey = null;
85681
86555
  var compactState = initialCompactState();
85682
86556
  var compactDispatching = false;
85683
86557
  var COMPACT_CARD_TIMEOUT_MS = 900000;
@@ -87891,7 +88765,7 @@ var statusPinState = new Map;
87891
88765
  var statusPinChatIds = new Map;
87892
88766
  var statusPinPinnedAt = new Map;
87893
88767
  var statusPinRightsCache = new PinRightsCache2;
87894
- var STATUS_PIN_STORE_PATH = join54(STATE_DIR, "status-pins.json");
88768
+ var STATUS_PIN_STORE_PATH = join55(STATE_DIR, "status-pins.json");
87895
88769
  var statusPinStoreFs = {
87896
88770
  readFileSync: (p) => readFileSync54(p, "utf8"),
87897
88771
  writeFileSync: (p, d) => writeFileSync43(p, d),
@@ -87899,7 +88773,7 @@ var statusPinStoreFs = {
87899
88773
  existsSync: (p) => existsSync50(p)
87900
88774
  };
87901
88775
  var statusPinPersistEnabled = !STATIC && PIN_STATUS_WHILE_WORKING;
87902
- var ACTIVITY_CARD_STORE_PATH = join54(STATE_DIR, "activity-cards-pending.json");
88776
+ var ACTIVITY_CARD_STORE_PATH = join55(STATE_DIR, "activity-cards-pending.json");
87903
88777
  var activityCardStoreFs = {
87904
88778
  readFileSync: (p) => readFileSync54(p, "utf8"),
87905
88779
  writeFileSync: (p, d) => writeFileSync43(p, d),
@@ -87907,7 +88781,7 @@ var activityCardStoreFs = {
87907
88781
  existsSync: (p) => existsSync50(p)
87908
88782
  };
87909
88783
  var activityCardPersistEnabled = !STATIC;
87910
- var QUEUED_CARD_STORE_PATH = join54(STATE_DIR, "queued-cards-pending.json");
88784
+ var QUEUED_CARD_STORE_PATH = join55(STATE_DIR, "queued-cards-pending.json");
87911
88785
  var queuedCardStoreFs = {
87912
88786
  readFileSync: (p) => readFileSync54(p, "utf8"),
87913
88787
  writeFileSync: (p, d) => writeFileSync43(p, d),
@@ -88261,11 +89135,11 @@ var getPinnedProgressCardMessageId = null;
88261
89135
  var completeProgressCardTurn = null;
88262
89136
  var subagentWatcher = null;
88263
89137
  var workerActivityFeed = null;
88264
- var SOCKET_PATH = process.env.SWITCHROOM_GATEWAY_SOCKET ?? join54(STATE_DIR, "gateway.sock");
89138
+ var SOCKET_PATH = process.env.SWITCHROOM_GATEWAY_SOCKET ?? join55(STATE_DIR, "gateway.sock");
88265
89139
  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");
89140
+ var GATEWAY_PID_PATH = process.env.SWITCHROOM_GATEWAY_PID_FILE ?? join55(STATE_DIR, "gateway.pid.json");
89141
+ var GATEWAY_SESSION_MARKER_PATH = process.env.SWITCHROOM_GATEWAY_SESSION_MARKER ?? join55(STATE_DIR, "gateway-session.json");
89142
+ var GATEWAY_CLEAN_SHUTDOWN_MARKER_PATH = process.env.SWITCHROOM_GATEWAY_CLEAN_SHUTDOWN_MARKER ?? join55(STATE_DIR, "clean-shutdown.json");
88269
89143
  var GATEWAY_STARTED_AT_MS = Date.now();
88270
89144
  var BOOT_CARD_ENABLED = process.env.SWITCHROOM_BOOT_CARD !== "false";
88271
89145
  var activeBootCard = null;
@@ -88294,7 +89168,7 @@ function ensureIssuesCard(chatId, threadId) {
88294
89168
  bot: botApi,
88295
89169
  log: (msg) => process.stderr.write(`telegram gateway: ${msg}
88296
89170
  `),
88297
- persistPath: join54(stateDir, "issues-card.json")
89171
+ persistPath: join55(stateDir, "issues-card.json")
88298
89172
  });
88299
89173
  activeIssuesWatcher = startIssuesWatcher({
88300
89174
  stateDir,
@@ -88625,7 +89499,7 @@ startTimer2({
88625
89499
  }
88626
89500
  });
88627
89501
  var inboundSpool = STATIC ? undefined : createInboundSpool({
88628
- path: join54(STATE_DIR, "inbound-spool.jsonl"),
89502
+ path: join55(STATE_DIR, "inbound-spool.jsonl"),
88629
89503
  fs: {
88630
89504
  appendFileSync: (p, d) => appendFileSync6(p, d),
88631
89505
  readFileSync: (p) => readFileSync54(p, "utf8"),
@@ -88760,6 +89634,88 @@ async function deliverCapturedProse(args) {
88760
89634
  }
88761
89635
  }
88762
89636
  }
89637
+ async function maybeRedeliverUndeliveredAnswer() {
89638
+ const candidate = pendingRedelivery;
89639
+ pendingRedelivery = null;
89640
+ if (candidate == null || turnsDb == null)
89641
+ return;
89642
+ const { turn, maxAgeMs } = candidate;
89643
+ const sessionId = turn.session_id;
89644
+ if (!sessionId)
89645
+ return;
89646
+ let transcriptText;
89647
+ try {
89648
+ const projectsDir = getProjectsDirForCwd();
89649
+ const path2 = join55(projectsDir, `${sessionId}.jsonl`);
89650
+ if (!existsSync50(path2)) {
89651
+ process.stderr.write(`telegram gateway: crash-redelivery \u2014 transcript not found for turnKey=${turn.turn_key} session=${sessionId} (${path2}); skipping
89652
+ `);
89653
+ return;
89654
+ }
89655
+ transcriptText = readFileSync54(path2, "utf8");
89656
+ } catch (err) {
89657
+ process.stderr.write(`telegram gateway: crash-redelivery \u2014 transcript read failed turnKey=${turn.turn_key}: ${err.message}
89658
+ `);
89659
+ return;
89660
+ }
89661
+ const projected = projectTrailingAnswerFromTranscript(transcriptText);
89662
+ const threadIdNum2 = turn.thread_id != null && turn.thread_id !== "" ? Number(turn.thread_id) : undefined;
89663
+ const threadIdForOracle = threadIdNum2 != null && Number.isFinite(threadIdNum2) ? threadIdNum2 : null;
89664
+ const decision = decideRedeliver({
89665
+ capturedText: projected.text,
89666
+ trailingIsText: projected.trailingIsText,
89667
+ hasDeliveredText: HISTORY_ENABLED ? hasOutboundWithText(turn.chat_id, projected.text, threadIdForOracle, turn.started_at) : false,
89668
+ alreadyRedelivered: turn.answer_redelivered_at != null,
89669
+ ageMs: Math.max(0, Date.now() - turn.started_at),
89670
+ maxAgeMs
89671
+ });
89672
+ if (!decision.redeliver || decision.framedText == null) {
89673
+ process.stderr.write(`telegram gateway: crash-redelivery skipped turnKey=${turn.turn_key} reason=${decision.skipReason ?? "unknown"}
89674
+ `);
89675
+ return;
89676
+ }
89677
+ const chatId = turn.chat_id;
89678
+ const out = redactOutboundText(decision.framedText, "crash_redelivery");
89679
+ const chunks = splitMarkdownChunks2(out, RICH_MESSAGE_MAX_CHARS2);
89680
+ const sentIds = [];
89681
+ try {
89682
+ let liveThreadId = threadIdNum2 != null && Number.isFinite(threadIdNum2) ? threadIdNum2 : undefined;
89683
+ for (const c of chunks) {
89684
+ const sent = await retryWithThreadFallback2(robustApiCall, (tid) => {
89685
+ const opts = {
89686
+ link_preview_options: { is_disabled: true },
89687
+ ...tid != null ? { message_thread_id: tid } : {}
89688
+ };
89689
+ return bot.api.sendRichMessage(chatId, richMessage2(c), opts);
89690
+ }, { threadId: liveThreadId, chat_id: chatId, verb: "crash-redelivery.sendMessage" });
89691
+ if (liveThreadId != null && sent.message_thread_id == null) {
89692
+ liveThreadId = undefined;
89693
+ }
89694
+ sentIds.push(sent.message_id);
89695
+ }
89696
+ if (HISTORY_ENABLED && sentIds.length > 0) {
89697
+ try {
89698
+ recordOutbound({
89699
+ chat_id: chatId,
89700
+ thread_id: threadIdForOracle,
89701
+ message_ids: sentIds,
89702
+ texts: chunks
89703
+ });
89704
+ } catch {}
89705
+ }
89706
+ try {
89707
+ markAnswerRedelivered(turnsDb, turn.turn_key);
89708
+ } catch (err) {
89709
+ process.stderr.write(`telegram gateway: crash-redelivery markAnswerRedelivered failed turnKey=${turn.turn_key}: ${err.message}
89710
+ `);
89711
+ }
89712
+ 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}
89713
+ `);
89714
+ } catch (err) {
89715
+ process.stderr.write(`telegram gateway: crash-redelivery send failed turnKey=${turn.turn_key}: ${err.message} ` + `\u2014 left un-stamped for a later retry
89716
+ `);
89717
+ }
89718
+ }
88763
89719
  function obligationSweep() {
88764
89720
  if (!OBLIGATION_LEDGER_ENABLED)
88765
89721
  return;
@@ -88919,8 +89875,8 @@ var bridgeDeadWatchdog = createBridgeDeadWatchdog({
88919
89875
  isSessionAlive: () => findAgentProcessInContainer2()?.comm === "claude",
88920
89876
  isShuttingDown: () => shuttingDown,
88921
89877
  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"),
89878
+ crashLogPath: join55(STATE_DIR, "bridge-crash.log"),
89879
+ markerPath: join55(STATE_DIR, "bridge-dead-escalation.json"),
88924
89880
  log: (line) => process.stderr.write(`${line}
88925
89881
  `),
88926
89882
  priorStreak: bridgeDeadPriorStreak,
@@ -89047,8 +90003,8 @@ var ipcServer = createIpcServer({
89047
90003
  probeQuotaViaBroker: (t) => probeQuotaForBootCard(agentSlug, t),
89048
90004
  tmuxSupervisor: process.env.SWITCHROOM_TMUX_SUPERVISOR === "1",
89049
90005
  dockerMode: process.env.SWITCHROOM_RUNTIME === "docker",
89050
- configSnapshotPath: join54(resolvedAgentDirForCard, ".config-snapshot.json"),
89051
- bootCardStatePath: join54(resolvedAgentDirForCard, ".boot-card-msgid.json"),
90006
+ configSnapshotPath: join55(resolvedAgentDirForCard, ".config-snapshot.json"),
90007
+ bootCardStatePath: join55(resolvedAgentDirForCard, ".boot-card-msgid.json"),
89052
90008
  floodStatePath: FLOOD_STATE_PATH,
89053
90009
  ...updateOutcomeLine ? { updateOutcomeLine } : {}
89054
90010
  }, ackMsgId).then((handle) => {
@@ -89133,6 +90089,21 @@ var ipcServer = createIpcServer({
89133
90089
  return;
89134
90090
  if (msg.activeFile)
89135
90091
  lastSessionActiveFile = msg.activeFile;
90092
+ if (turnsDb != null && msg.activeFile != null) {
90093
+ const stampKey = currentTurn?.registryKey ?? null;
90094
+ if (stampKey != null && stampKey !== lastSessionStampedTurnKey) {
90095
+ const sessionId = basename13(msg.activeFile).replace(/\.jsonl$/, "");
90096
+ if (sessionId) {
90097
+ try {
90098
+ stampTurnSessionId(turnsDb, stampKey, sessionId);
90099
+ lastSessionStampedTurnKey = stampKey;
90100
+ } catch (err) {
90101
+ process.stderr.write(`telegram gateway: stampTurnSessionId failed turnKey=${stampKey}: ${err.message}
90102
+ `);
90103
+ }
90104
+ }
90105
+ }
90106
+ }
89136
90107
  const ev = msg.event;
89137
90108
  handleSessionEvent(ev);
89138
90109
  toolFlightTracker.onEvent(ev);
@@ -89715,7 +90686,7 @@ var ipcServer = createIpcServer({
89715
90686
  const receiverUid = receiverUidRaw ? Number(receiverUidRaw) : NaN;
89716
90687
  if (Number.isInteger(receiverUid))
89717
90688
  allowedUids.push(receiverUid);
89718
- const socketPath = join54(STATE_DIR, "webhook.sock");
90689
+ const socketPath = join55(STATE_DIR, "webhook.sock");
89719
90690
  const webhookInject = (agentName3, inbound) => {
89720
90691
  const msg = inbound;
89721
90692
  const delivered = ipcServer.sendToAgent(agentName3, msg);
@@ -89950,9 +90921,9 @@ function redactOutboundText(text5, site) {
89950
90921
  var VOICE_OUT_DEFAULT_CHUNK_CHARS = 600;
89951
90922
  var VOICE_OUT_HARD_CHUNK_CAP = 4096;
89952
90923
  var voiceOnDemandCache = new VoiceOnDemandCache({
89953
- persistPath: join54(STATE_DIR, "voice-ondemand.json")
90924
+ persistPath: join55(STATE_DIR, "voice-ondemand.json")
89954
90925
  });
89955
- var VOICE_CACHE_DIR = join54(STATE_DIR, "voice-cache");
90926
+ var VOICE_CACHE_DIR = join55(STATE_DIR, "voice-cache");
89956
90927
  var voicePreSynthQueue = new PreSynthQueue({
89957
90928
  runJob: async (job) => {
89958
90929
  const sidecarToken = await materializeSidecarToken();
@@ -90931,7 +91902,7 @@ async function executeSendGif(rawArgs) {
90931
91902
  };
90932
91903
  }
90933
91904
  async function publishToTelegraph(text5, shortName, authorName) {
90934
- const accountPath = join54(STATE_DIR, "telegraph-account.json");
91905
+ const accountPath = join55(STATE_DIR, "telegraph-account.json");
90935
91906
  let account = null;
90936
91907
  try {
90937
91908
  if (existsSync50(accountPath)) {
@@ -91854,7 +92825,8 @@ function composeTurnActivity(turn, final = false, liveSuffix = "") {
91854
92825
  elapsedMs: turn.startedAt > 0 ? Date.now() - turn.startedAt : 0,
91855
92826
  toolCount: turn.labeledToolCount,
91856
92827
  state: final ? "done" : "running",
91857
- model: turn.currentModel
92828
+ model: turn.currentModel,
92829
+ totalTokens: turn.totalTokens
91858
92830
  };
91859
92831
  return renderActivityFeedWithNested2(turn.mirrorLines, childLines, final, liveSuffix, stepCount, header);
91860
92832
  }
@@ -92315,6 +93287,8 @@ function handleSessionEvent(ev) {
92315
93287
  lastAssistantDone: false,
92316
93288
  toolCallCount: 0,
92317
93289
  labeledToolCount: 0,
93290
+ totalTokens: 0,
93291
+ seenUsageMessageIds: new Set,
92318
93292
  activityMessageId: null,
92319
93293
  activityInFlight: null,
92320
93294
  activityPendingRender: null,
@@ -92388,6 +93362,18 @@ function handleSessionEvent(ev) {
92388
93362
  sessionModelSource.noteTranscriptModel(ev.model);
92389
93363
  return;
92390
93364
  }
93365
+ case "usage": {
93366
+ const turn = currentTurn;
93367
+ if (turn == null)
93368
+ return;
93369
+ if (ev.messageId != null) {
93370
+ if (turn.seenUsageMessageIds.has(ev.messageId))
93371
+ return;
93372
+ turn.seenUsageMessageIds.add(ev.messageId);
93373
+ }
93374
+ turn.totalTokens += ev.totalTokens;
93375
+ return;
93376
+ }
92391
93377
  case "thinking": {
92392
93378
  const turn = currentTurn;
92393
93379
  if (turn == null)
@@ -94258,7 +95244,7 @@ function getMyAgentName() {
94258
95244
  const fromEnv = process.env.SWITCHROOM_AGENT_NAME;
94259
95245
  if (fromEnv && fromEnv.trim().length > 0)
94260
95246
  return fromEnv.trim();
94261
- return basename12(process.cwd());
95247
+ return basename13(process.cwd());
94262
95248
  }
94263
95249
  function isSelfTargetingCommand(name) {
94264
95250
  if (name === "all")
@@ -94271,7 +95257,7 @@ function restartMarkerPath() {
94271
95257
  const agentDir = resolveAgentDirFromEnv();
94272
95258
  if (!agentDir)
94273
95259
  return null;
94274
- return join54(agentDir, "restart-pending.json");
95260
+ return join55(agentDir, "restart-pending.json");
94275
95261
  }
94276
95262
  function writeRestartMarker(marker) {
94277
95263
  const p = restartMarkerPath();
@@ -94463,7 +95449,7 @@ function _resetDockerReachableCache() {
94463
95449
  }
94464
95450
  function spawnSwitchroomDetached(args, onFailure) {
94465
95451
  const fullArgs = SWITCHROOM_CONFIG ? ["--config", SWITCHROOM_CONFIG, ...args] : args;
94466
- const logPath = join54(STATE_DIR, "detached-spawn.log");
95452
+ const logPath = join55(STATE_DIR, "detached-spawn.log");
94467
95453
  let outFd = null;
94468
95454
  try {
94469
95455
  mkdirSync40(STATE_DIR, { recursive: true });
@@ -94861,7 +95847,7 @@ bot.use(async (ctx, next) => {
94861
95847
  });
94862
95848
  function readRecentDenialsForAgent(agentName3, windowMs, limit) {
94863
95849
  try {
94864
- const auditPath = join54(homedir17(), ".switchroom", "vault-audit.log");
95850
+ const auditPath = join55(homedir18(), ".switchroom", "vault-audit.log");
94865
95851
  if (!existsSync50(auditPath))
94866
95852
  return [];
94867
95853
  const raw = readFileSync54(auditPath, "utf8");
@@ -94915,7 +95901,7 @@ async function buildAgentMetadata(agentName3) {
94915
95901
  try {
94916
95902
  const agentDir = resolveAgentDirFromEnv();
94917
95903
  if (agentDir) {
94918
- const raw = readFileSync54(join54(agentDir, ".claude", ".claude.json"), "utf8");
95904
+ const raw = readFileSync54(join55(agentDir, ".claude", ".claude.json"), "utf8");
94919
95905
  claudeJson = JSON.parse(raw);
94920
95906
  }
94921
95907
  } catch {}
@@ -95111,7 +96097,7 @@ function buildModelDeps(restartCtx) {
95111
96097
  try {
95112
96098
  const agentDir = resolveAgentDirFromEnv();
95113
96099
  if (agentDir) {
95114
- const local = await fetchQuota2({ claudeConfigDir: join54(agentDir, ".claude") });
96100
+ const local = await fetchQuota2({ claudeConfigDir: join55(agentDir, ".claude") });
95115
96101
  if (local.ok)
95116
96102
  return formatQuotaLine2(local.data);
95117
96103
  }
@@ -95583,7 +96569,7 @@ bot.command("restart", async (ctx) => {
95583
96569
  function flushAgentHandoff(agentDir) {
95584
96570
  let removed = 0;
95585
96571
  for (const fname of [".handoff.md", ".handoff-topic"]) {
95586
- const p = join54(agentDir, fname);
96572
+ const p = join55(agentDir, fname);
95587
96573
  try {
95588
96574
  if (existsSync50(p)) {
95589
96575
  unlinkSync24(p);
@@ -95641,7 +96627,7 @@ async function handleNewCommand(ctx) {
95641
96627
  writeRestartMarker({ chat_id: chatId, thread_id: threadId ?? null, ack_message_id: ackId, ts: Date.now() });
95642
96628
  if (agentDir != null) {
95643
96629
  try {
95644
- writeFileSync43(join54(agentDir, ".force-fresh-session"), `${kind} at ${new Date().toISOString()}
96630
+ writeFileSync43(join55(agentDir, ".force-fresh-session"), `${kind} at ${new Date().toISOString()}
95645
96631
  `, "utf8");
95646
96632
  } catch (err) {
95647
96633
  process.stderr.write(`telegram gateway: failed to write force-fresh marker: ${err}
@@ -96011,7 +96997,7 @@ var lockoutOps = {
96011
96997
  writeFileSync: (p, data, opts) => writeFileSync43(p, data, opts),
96012
96998
  existsSync: (p) => existsSync50(p),
96013
96999
  mkdirSync: (p, opts) => mkdirSync40(p, opts),
96014
- joinPath: (...parts) => join54(...parts)
97000
+ joinPath: (...parts) => join55(...parts)
96015
97001
  };
96016
97002
  var FLEET_FALLBACK_DEDUP_MS = 30000;
96017
97003
  function isAuthBrokerSocketReachable() {
@@ -96271,7 +97257,7 @@ async function runCreditWatch() {
96271
97257
  if (!agentDir)
96272
97258
  return;
96273
97259
  const agentName3 = getMyAgentName();
96274
- const claudeConfigDir = join54(agentDir, ".claude");
97260
+ const claudeConfigDir = join55(agentDir, ".claude");
96275
97261
  const stateDir = STATE_DIR;
96276
97262
  const reason = readClaudeJsonOverage(claudeConfigDir);
96277
97263
  const prev = loadCreditState(stateDir);
@@ -97283,7 +98269,7 @@ bot.command("usage", async (ctx) => {
97283
98269
  await switchroomReply(ctx, "**/usage:** cannot resolve agent dir.", { html: true });
97284
98270
  return;
97285
98271
  }
97286
- const result = await fetchQuota2({ claudeConfigDir: join54(agentDir, ".claude") });
98272
+ const result = await fetchQuota2({ claudeConfigDir: join55(agentDir, ".claude") });
97287
98273
  if (!result.ok) {
97288
98274
  await switchroomReply(ctx, `**/usage:** ${escapeHtmlForTg2(result.reason)}`, { html: true });
97289
98275
  return;
@@ -99171,6 +100157,10 @@ var didOneTimeSetup = false;
99171
100157
  process.stderr.write(`telegram gateway: blocked-approval boot reconcile failed: ${err.message}
99172
100158
  `);
99173
100159
  }
100160
+ maybeRedeliverUndeliveredAnswer().catch((err) => {
100161
+ process.stderr.write(`telegram gateway: crash-redelivery boot send errored: ${err.message}
100162
+ `);
100163
+ });
99174
100164
  try {
99175
100165
  const bootAccess = loadAccess();
99176
100166
  const chatSet = new Set(bootAccess.allowFrom);
@@ -99268,7 +100258,7 @@ var didOneTimeSetup = false;
99268
100258
  return;
99269
100259
  }
99270
100260
  })();
99271
- const resolvedAgentDirForBootCard = agentDir ?? join54(homedir17(), ".switchroom", "agents", agentSlug);
100261
+ const resolvedAgentDirForBootCard = agentDir ?? join55(homedir18(), ".switchroom", "agents", agentSlug);
99272
100262
  const handle = await startBootCard(chatId, threadId, botApiForCard, {
99273
100263
  agentName: agentDisplayName,
99274
100264
  agentSlug,
@@ -99282,8 +100272,8 @@ var didOneTimeSetup = false;
99282
100272
  probeQuotaViaBroker: (t) => probeQuotaForBootCard(agentSlug, t),
99283
100273
  tmuxSupervisor: process.env.SWITCHROOM_TMUX_SUPERVISOR === "1",
99284
100274
  dockerMode: process.env.SWITCHROOM_RUNTIME === "docker",
99285
- configSnapshotPath: join54(resolvedAgentDirForBootCard, ".config-snapshot.json"),
99286
- bootCardStatePath: join54(resolvedAgentDirForBootCard, ".boot-card-msgid.json"),
100275
+ configSnapshotPath: join55(resolvedAgentDirForBootCard, ".config-snapshot.json"),
100276
+ bootCardStatePath: join55(resolvedAgentDirForBootCard, ".boot-card-msgid.json"),
99287
100277
  floodStatePath: FLOOD_STATE_PATH,
99288
100278
  ...updateOutcomeLine ? { updateOutcomeLine } : {}
99289
100279
  }, ackMsgId);
@@ -99314,7 +100304,7 @@ var didOneTimeSetup = false;
99314
100304
  try {
99315
100305
  const smAgentDir = resolveAgentDirFromEnv();
99316
100306
  if (smAgentDir) {
99317
- const activePath = join54(smAgentDir, ".active-session-model");
100307
+ const activePath = join55(smAgentDir, ".active-session-model");
99318
100308
  if (existsSync50(activePath)) {
99319
100309
  try {
99320
100310
  const launched = readFileSync54(activePath, "utf8").trim();
@@ -99326,7 +100316,7 @@ var didOneTimeSetup = false;
99326
100316
  sessionModelSource.setOverride(launched.length > 0 && launched !== configured ? launched : null);
99327
100317
  } catch {}
99328
100318
  }
99329
- const activeEffortPath = join54(smAgentDir, ".active-session-effort");
100319
+ const activeEffortPath = join55(smAgentDir, ".active-session-effort");
99330
100320
  if (existsSync50(activeEffortPath)) {
99331
100321
  try {
99332
100322
  const launchedEffort = readFileSync54(activeEffortPath, "utf8").trim();
@@ -99334,7 +100324,7 @@ var didOneTimeSetup = false;
99334
100324
  sessionEffortOverride = launchedEffort.length > 0 && launchedEffort !== configuredEffort ? launchedEffort : null;
99335
100325
  } catch {}
99336
100326
  }
99337
- const alertPath = join54(smAgentDir, ".session-model-alert");
100327
+ const alertPath = join55(smAgentDir, ".session-model-alert");
99338
100328
  if (existsSync50(alertPath)) {
99339
100329
  let alertText = null;
99340
100330
  try {
@@ -99463,6 +100453,7 @@ var didOneTimeSetup = false;
99463
100453
  maxRows: workerFeedMaxRows,
99464
100454
  staleWorkerTtlMs: resolveInflightTerminalCapMs() + WORKER_FEED_STALE_TTL_MARGIN_MS,
99465
100455
  absoluteRowLifetimeCapMs: resolveInflightTerminalCapMs() * WORKER_FEED_ABSOLUTE_ROW_LIFETIME_CAP_MULTIPLE,
100456
+ groupMessageLifetimeCapMs: WORKER_FEED_GROUP_MESSAGE_LIFETIME_CAP_MS,
99466
100457
  reconcilePin: ({ feedKey, chatId, messageId }) => {
99467
100458
  if (!PIN_STATUS_WHILE_WORKING)
99468
100459
  return;
@@ -99516,7 +100507,7 @@ var didOneTimeSetup = false;
99516
100507
  `);
99517
100508
  }
99518
100509
  },
99519
- onFinish: ({ agentId, outcome, description, resultText, toolCount, durationMs, background: entryBackground }) => {
100510
+ onFinish: ({ agentId, outcome, description, resultText, toolCount, totalTokens, durationMs, background: entryBackground }) => {
99520
100511
  deferredDoneReactions.promote();
99521
100512
  let fleetChatId = "";
99522
100513
  try {
@@ -99544,6 +100535,7 @@ var didOneTimeSetup = false;
99544
100535
  description: dispatch.feedDescription,
99545
100536
  lastTool: null,
99546
100537
  toolCount,
100538
+ totalTokens,
99547
100539
  latestSummary: resultText,
99548
100540
  elapsedMs: durationMs,
99549
100541
  state: outcome === "failed" ? "failed" : "done",
@@ -99591,6 +100583,7 @@ var didOneTimeSetup = false;
99591
100583
  description: dispatch.feedDescription,
99592
100584
  lastTool: null,
99593
100585
  toolCount,
100586
+ totalTokens,
99594
100587
  latestSummary: resultText,
99595
100588
  elapsedMs: durationMs,
99596
100589
  state: outcome === "failed" ? "failed" : "done",
@@ -99604,6 +100597,7 @@ var didOneTimeSetup = false;
99604
100597
  description: dispatch.feedDescription,
99605
100598
  lastTool: null,
99606
100599
  toolCount,
100600
+ totalTokens,
99607
100601
  latestSummary: resultText,
99608
100602
  elapsedMs: durationMs,
99609
100603
  state: outcome === "failed" ? "failed" : "done",
@@ -99644,7 +100638,7 @@ var didOneTimeSetup = false;
99644
100638
  process.stderr.write(`telegram gateway: subagent-handback queued agent=${agentId} outcome=${outcome} chat=${decision.chatId} resultChars=${resultText.length}
99645
100639
  `);
99646
100640
  },
99647
- onProgress: ({ agentId, description, latestSummary, elapsedMs, prevBucketIdx, setBucketIdx, lastTool, toolCount, progressLine, model, skeleton }) => {
100641
+ onProgress: ({ agentId, description, latestSummary, elapsedMs, prevBucketIdx, setBucketIdx, lastTool, toolCount, totalTokens, progressLine, model, skeleton }) => {
99648
100642
  let fleetChatId = "";
99649
100643
  try {
99650
100644
  const fleets = progressDriver?.peekAllFleets() ?? [];
@@ -99681,7 +100675,8 @@ var didOneTimeSetup = false;
99681
100675
  latestSummary: stepLine,
99682
100676
  elapsedMs,
99683
100677
  state: "running",
99684
- model: feedModel
100678
+ model: feedModel,
100679
+ totalTokens
99685
100680
  }, wk.threadId);
99686
100681
  return;
99687
100682
  }
@@ -99742,7 +100737,8 @@ var didOneTimeSetup = false;
99742
100737
  latestSummary: stepLine,
99743
100738
  elapsedMs,
99744
100739
  state: "running",
99745
- model: feedModel
100740
+ model: feedModel,
100741
+ totalTokens
99746
100742
  }, wk.threadId);
99747
100743
  return;
99748
100744
  }