switchroom 0.18.7 → 0.18.8

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 +905 -758
  2. package/dist/host-control/main.js +1 -1
  3. package/package.json +1 -1
  4. package/profiles/_base/start.sh.hbs +111 -34
  5. package/skills/switchroom-runtime/SKILL.md +2 -0
  6. package/telegram-plugin/dist/gateway/gateway.js +1403 -657
  7. package/telegram-plugin/flood-circuit-breaker.ts +123 -0
  8. package/telegram-plugin/gateway/activity-card-store.ts +63 -18
  9. package/telegram-plugin/gateway/boot-card.ts +27 -0
  10. package/telegram-plugin/gateway/busy-ack.ts +106 -0
  11. package/telegram-plugin/gateway/gateway.ts +564 -85
  12. package/telegram-plugin/gateway/mental-model-propose-diff.ts +61 -5
  13. package/telegram-plugin/gateway/model-command.ts +23 -11
  14. package/telegram-plugin/gateway/session-model-file.ts +198 -0
  15. package/telegram-plugin/gateway/status-pin-store.ts +82 -22
  16. package/telegram-plugin/gateway/worker-pin-reaper.ts +114 -0
  17. package/telegram-plugin/hooks/hooks.json +10 -10
  18. package/telegram-plugin/hooks/run-hook.sh +84 -0
  19. package/telegram-plugin/model-unavailable.ts +26 -0
  20. package/telegram-plugin/pty-partial-handler.ts +39 -0
  21. package/telegram-plugin/render/rich-render.ts +79 -1
  22. package/telegram-plugin/retry-api-call.ts +62 -0
  23. package/telegram-plugin/shared/bot-runtime.ts +8 -1
  24. package/telegram-plugin/silence-poke.ts +14 -0
  25. package/telegram-plugin/stream-controller.ts +156 -38
  26. package/telegram-plugin/tests/activity-card-store.test.ts +47 -2
  27. package/telegram-plugin/tests/approval-card-restart-outcome.test.ts +218 -0
  28. package/telegram-plugin/tests/boot-card-flood-suppress.test.ts +111 -0
  29. package/telegram-plugin/tests/busy-ack-wiring.test.ts +118 -0
  30. package/telegram-plugin/tests/busy-ack.test.ts +121 -0
  31. package/telegram-plugin/tests/flood-circuit-breaker.test.ts +74 -0
  32. package/telegram-plugin/tests/gateway-session-model-relaunch.test.ts +177 -25
  33. package/telegram-plugin/tests/mental-model-name-entity-corruption.test.ts +119 -0
  34. package/telegram-plugin/tests/model-command.test.ts +2 -2
  35. package/telegram-plugin/tests/model-unavailable.test.ts +41 -0
  36. package/telegram-plugin/tests/pty-partial-handler.test.ts +56 -0
  37. package/telegram-plugin/tests/render/render-outbound-chunks.test.ts +98 -0
  38. package/telegram-plugin/tests/retry-api-call.test.ts +59 -0
  39. package/telegram-plugin/tests/run-hook-wrapper.test.ts +132 -0
  40. package/telegram-plugin/tests/session-model-file.test.ts +132 -0
  41. package/telegram-plugin/tests/slot-banner-boot-recovery.test.ts +3 -3
  42. package/telegram-plugin/tests/status-pin-boot-recovery.test.ts +3 -3
  43. package/telegram-plugin/tests/status-pin-store.test.ts +62 -6
  44. package/telegram-plugin/tests/stream-controller-chunk-cap.test.ts +122 -0
  45. package/telegram-plugin/tests/voice-send.test.ts +308 -0
  46. package/telegram-plugin/tests/worker-pin-reaper.test.ts +132 -0
  47. package/telegram-plugin/uat/scenarios/jtbd-deliberate-restart-resumes-dm.test.ts +118 -0
  48. package/telegram-plugin/uat/scenarios/jtbd-midflight-busy-ack-dm.test.ts +201 -0
  49. package/telegram-plugin/uat/scenarios/jtbd-worker-pin-lifecycle-dm.test.ts +208 -0
  50. package/telegram-plugin/uat/scenarios/vault-card-survives-gateway-restart-dm.test.ts +140 -0
  51. package/telegram-plugin/uat/scenarios/vault-deny-resumes-turn-dm.test.ts +84 -0
  52. package/telegram-plugin/uat/scenarios/vault-timeout-wakes-agent-dm.test.ts +91 -0
  53. package/telegram-plugin/voice-ondemand.ts +25 -1
  54. package/telegram-plugin/voice-send.ts +154 -0
@@ -6784,7 +6784,89 @@ function isBlockquoteLine(line) {
6784
6784
  function isHeadingLine(line) {
6785
6785
  return /^#{1,6}\s/.test(line.trimStart());
6786
6786
  }
6787
- var RICH_MESSAGE_MAX_CHARS = 32768;
6787
+ function hardSliceToCap(text, cap = RICH_MESSAGE_MAX_CHARS) {
6788
+ if (cap <= 0)
6789
+ return [text];
6790
+ if (text.length <= cap)
6791
+ return [text];
6792
+ const out = [];
6793
+ for (let i = 0;i < text.length; i += cap) {
6794
+ out.push(text.slice(i, i + cap));
6795
+ }
6796
+ return out;
6797
+ }
6798
+ function splitMarkdownChunks(text, maxLen = RICH_MESSAGE_MAX_CHARS) {
6799
+ if (text.length <= maxLen)
6800
+ return [text];
6801
+ const chunks = [];
6802
+ let rest = text;
6803
+ while (rest.length > 0) {
6804
+ if (rest.length <= maxLen) {
6805
+ chunks.push(rest);
6806
+ break;
6807
+ }
6808
+ let cut = maxLen;
6809
+ const paraIdx = rest.lastIndexOf(`
6810
+
6811
+ `, maxLen);
6812
+ const lineIdx = rest.lastIndexOf(`
6813
+ `, maxLen);
6814
+ const spaceIdx = rest.lastIndexOf(" ", maxLen);
6815
+ if (paraIdx > maxLen / 3) {
6816
+ cut = paraIdx;
6817
+ } else if (lineIdx > maxLen / 3) {
6818
+ cut = lineIdx;
6819
+ } else if (spaceIdx > 0) {
6820
+ cut = spaceIdx;
6821
+ }
6822
+ cut = backOffOpenFence(rest, cut);
6823
+ cut = backOffTableRow(rest, cut);
6824
+ if (cut <= 0) {
6825
+ const sliced = hardSliceToCap(rest, maxLen);
6826
+ chunks.push(stripBoundarySpacers(sliced[0], "trailing"));
6827
+ rest = stripBoundarySpacers(sliced.slice(1).join(""), "leading");
6828
+ continue;
6829
+ }
6830
+ chunks.push(stripBoundarySpacers(rest.slice(0, cut), "trailing"));
6831
+ rest = stripBoundarySpacers(rest.slice(cut), "leading");
6832
+ }
6833
+ return chunks.map((c) => stripBoundarySpacers(c, "trailing"));
6834
+ }
6835
+ function stripBoundarySpacers(chunk, side) {
6836
+ const sp = PARAGRAPH_SPACER;
6837
+ if (side === "leading") {
6838
+ return chunk.replace(new RegExp(`^(?:[ \\t]*${sp}?[ \\t]*\\n+)+`), "");
6839
+ }
6840
+ return chunk.replace(new RegExp(`(?:\\n+[ \\t]*${sp}?[ \\t]*)+$`), "");
6841
+ }
6842
+ function backOffOpenFence(text, cut) {
6843
+ if (cut <= 0 || cut >= text.length)
6844
+ return cut;
6845
+ const before = text.slice(0, cut);
6846
+ const fences = before.match(/^```/gm);
6847
+ if (fences == null || fences.length % 2 === 0)
6848
+ return cut;
6849
+ const lastFence = before.lastIndexOf("\n```");
6850
+ if (lastFence <= 0) {
6851
+ return 0;
6852
+ }
6853
+ return lastFence;
6854
+ }
6855
+ function backOffTableRow(text, cut) {
6856
+ if (cut <= 0 || cut >= text.length)
6857
+ return cut;
6858
+ const lineStart = text.lastIndexOf(`
6859
+ `, cut - 1) + 1;
6860
+ const nextNl = text.indexOf(`
6861
+ `, cut);
6862
+ const lineEnd = nextNl === -1 ? text.length : nextNl;
6863
+ const line = text.slice(lineStart, lineEnd);
6864
+ if (line.includes("|")) {
6865
+ return lineStart > 0 ? lineStart - 1 : 0;
6866
+ }
6867
+ return cut;
6868
+ }
6869
+ var RICH_MESSAGE_MAX_CHARS = 32768, PARAGRAPH_SPACER = "\u00a0";
6788
6870
 
6789
6871
  // text-voice-scrub.ts
6790
6872
  function enabled() {
@@ -6975,6 +7057,34 @@ var require_mod4 = __commonJS((exports) => {
6975
7057
  __exportStar(require_worker(), exports);
6976
7058
  });
6977
7059
 
7060
+ // flood-circuit-breaker.ts
7061
+ import { existsSync as existsSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3, mkdirSync as mkdirSync5 } from "node:fs";
7062
+ function floodWaitRemainingMs(state, now) {
7063
+ if (!state)
7064
+ return 0;
7065
+ return Math.max(0, state.untilTs - now);
7066
+ }
7067
+ function readFloodState(path) {
7068
+ try {
7069
+ if (!existsSync3(path))
7070
+ return null;
7071
+ const raw = JSON.parse(readFileSync3(path, "utf-8"));
7072
+ if (typeof raw.untilTs !== "number")
7073
+ return null;
7074
+ return {
7075
+ untilTs: raw.untilTs,
7076
+ retryAfterSec: typeof raw.retryAfterSec === "number" ? raw.retryAfterSec : 0,
7077
+ recordedTs: typeof raw.recordedTs === "number" ? raw.recordedTs : 0
7078
+ };
7079
+ } catch {
7080
+ return null;
7081
+ }
7082
+ }
7083
+ function suppressNonEssentialSendMs(path, now) {
7084
+ return floodWaitRemainingMs(readFloodState(path), now);
7085
+ }
7086
+ var init_flood_circuit_breaker = () => {};
7087
+
6978
7088
  // gateway/approval-card.ts
6979
7089
  function parseApprovalCallback(data) {
6980
7090
  if (!data.startsWith("apv:"))
@@ -17343,7 +17453,7 @@ __export(exports_client, {
17343
17453
  import * as net from "node:net";
17344
17454
  import { homedir as homedir3 } from "node:os";
17345
17455
  import { randomUUID as randomUUID3 } from "node:crypto";
17346
- import { join as join14 } from "node:path";
17456
+ import { join as join15 } from "node:path";
17347
17457
  function reviveDate(v) {
17348
17458
  if (v == null)
17349
17459
  return null;
@@ -17353,7 +17463,7 @@ function reviveDate(v) {
17353
17463
  return Number.isNaN(d.getTime()) ? null : d;
17354
17464
  }
17355
17465
  function operatorSocketPath(home = homedir3()) {
17356
- return join14(home, ".switchroom", "state", "auth-broker-operator", "sock");
17466
+ return join15(home, ".switchroom", "state", "auth-broker-operator", "sock");
17357
17467
  }
17358
17468
  function resolveAuthBrokerSocketPath(opts) {
17359
17469
  if (opts?.socket)
@@ -17670,11 +17780,11 @@ var init_flock = () => {};
17670
17780
  // ../src/vault/vault.ts
17671
17781
  import { randomBytes as randomBytes3, scryptSync, createCipheriv, createDecipheriv } from "node:crypto";
17672
17782
  import {
17673
- readFileSync as readFileSync10,
17783
+ readFileSync as readFileSync12,
17674
17784
  writeSync,
17675
- existsSync as existsSync7,
17785
+ existsSync as existsSync9,
17676
17786
  renameSync as renameSync4,
17677
- mkdirSync as mkdirSync10,
17787
+ mkdirSync as mkdirSync12,
17678
17788
  unlinkSync as unlinkSync7,
17679
17789
  fsyncSync,
17680
17790
  openSync,
@@ -17713,12 +17823,12 @@ function normalizeSecrets(raw) {
17713
17823
  return out;
17714
17824
  }
17715
17825
  function openVault(passphrase, vaultPath) {
17716
- if (!existsSync7(vaultPath)) {
17826
+ if (!existsSync9(vaultPath)) {
17717
17827
  throw new VaultError(`Vault file not found: ${vaultPath}`);
17718
17828
  }
17719
17829
  let vaultFile;
17720
17830
  try {
17721
- vaultFile = JSON.parse(readFileSync10(vaultPath, "utf8"));
17831
+ vaultFile = JSON.parse(readFileSync12(vaultPath, "utf8"));
17722
17832
  } catch {
17723
17833
  throw new VaultError(`Failed to read vault file: ${vaultPath}`);
17724
17834
  }
@@ -25473,7 +25583,7 @@ var init_schema = __esm(() => {
25473
25583
  });
25474
25584
 
25475
25585
  // ../src/config/paths.ts
25476
- import { existsSync as existsSync8 } from "node:fs";
25586
+ import { existsSync as existsSync10 } from "node:fs";
25477
25587
  import { resolve as resolve2 } from "node:path";
25478
25588
  function home() {
25479
25589
  return process.env.HOME ?? "/root";
@@ -25489,7 +25599,7 @@ function resolveStatePath2(fragment) {
25489
25599
  const h = home();
25490
25600
  const primary = resolve2(h, DEFAULT_STATE_DIR, fragment);
25491
25601
  const legacy = resolve2(h, LEGACY_STATE_DIR, fragment);
25492
- if (!existsSync8(primary) && existsSync8(legacy)) {
25602
+ if (!existsSync10(primary) && existsSync10(legacy)) {
25493
25603
  warnLegacyStateOnce(legacy);
25494
25604
  return legacy;
25495
25605
  }
@@ -25502,9 +25612,9 @@ function resolveDualPath(pathStr) {
25502
25612
  const absolute = resolve2(h, rest);
25503
25613
  if (rest.startsWith(`${DEFAULT_STATE_DIR}/`)) {
25504
25614
  const frag = rest.slice(DEFAULT_STATE_DIR.length + 1);
25505
- if (!existsSync8(absolute)) {
25615
+ if (!existsSync10(absolute)) {
25506
25616
  const legacy = resolve2(h, LEGACY_STATE_DIR, frag);
25507
- if (existsSync8(legacy)) {
25617
+ if (existsSync10(legacy)) {
25508
25618
  warnLegacyStateOnce(legacy);
25509
25619
  return legacy;
25510
25620
  }
@@ -25529,7 +25639,7 @@ var init_overlay_schema = __esm(() => {
25529
25639
  });
25530
25640
 
25531
25641
  // ../src/config/overlay-loader.ts
25532
- import { existsSync as existsSync9, readFileSync as readFileSync11, readdirSync as readdirSync2, statSync as statSync3 } from "node:fs";
25642
+ import { existsSync as existsSync11, readFileSync as readFileSync13, readdirSync as readdirSync2, statSync as statSync3 } from "node:fs";
25533
25643
  import { basename as basename4, resolve as resolve3 } from "node:path";
25534
25644
  function deriveOverlayTitle(raw, fileName) {
25535
25645
  const titleFromComment = raw.match(/^#[^\S\n]*name:[^\S\n]*(\S.*?)[^\S\n]*$/m)?.[1];
@@ -25545,7 +25655,7 @@ function overlayDirFor(agentName3, subdir) {
25545
25655
  return resolve3(base);
25546
25656
  }
25547
25657
  function listYamlFiles(dir) {
25548
- if (!existsSync9(dir))
25658
+ if (!existsSync11(dir))
25549
25659
  return [];
25550
25660
  let entries;
25551
25661
  try {
@@ -25593,7 +25703,7 @@ function applyAgentOverlays(config) {
25593
25703
  const merged = [...agentCfg.schedule ?? []];
25594
25704
  for (const file of files) {
25595
25705
  try {
25596
- const raw = readFileSync11(file, "utf-8");
25706
+ const raw = readFileSync13(file, "utf-8");
25597
25707
  const parsed = import_yaml.parse(raw);
25598
25708
  const doc = OverlayDocSchema.parse(parsed);
25599
25709
  const title = deriveOverlayTitle(raw, basename4(file));
@@ -25634,7 +25744,7 @@ function applyAgentOverlays(config) {
25634
25744
  const seen = new Set(merged);
25635
25745
  for (const file of skillFiles) {
25636
25746
  try {
25637
- const raw = readFileSync11(file, "utf-8");
25747
+ const raw = readFileSync13(file, "utf-8");
25638
25748
  const parsed = import_yaml.parse(raw);
25639
25749
  const doc = OverlayDocSchema.parse(parsed);
25640
25750
  for (const skillName of doc.skills ?? []) {
@@ -26080,7 +26190,7 @@ function validateNotionWorkspaceConfig(config) {
26080
26190
  }
26081
26191
 
26082
26192
  // ../src/config/loader.ts
26083
- import { readFileSync as readFileSync12, existsSync as existsSync10 } from "node:fs";
26193
+ import { readFileSync as readFileSync14, existsSync as existsSync12 } from "node:fs";
26084
26194
  import { homedir as homedir4 } from "node:os";
26085
26195
  import { resolve as resolve4 } from "node:path";
26086
26196
  function formatZodErrors(error) {
@@ -26147,7 +26257,7 @@ function findConfigFile(startDir) {
26147
26257
  resolve4(userDir, "clerk.yml")
26148
26258
  ].filter(Boolean);
26149
26259
  for (const path2 of searchPaths) {
26150
- if (existsSync10(path2)) {
26260
+ if (existsSync12(path2)) {
26151
26261
  return path2;
26152
26262
  }
26153
26263
  }
@@ -26155,12 +26265,12 @@ function findConfigFile(startDir) {
26155
26265
  }
26156
26266
  function loadConfig(configPath) {
26157
26267
  const filePath = configPath ?? findConfigFile();
26158
- if (!existsSync10(filePath)) {
26268
+ if (!existsSync12(filePath)) {
26159
26269
  throw new ConfigError(`Config file not found: ${filePath}`);
26160
26270
  }
26161
26271
  let raw;
26162
26272
  try {
26163
- raw = readFileSync12(filePath, "utf-8");
26273
+ raw = readFileSync14(filePath, "utf-8");
26164
26274
  } catch (err) {
26165
26275
  throw new ConfigError(`Failed to read config file: ${filePath}`, [
26166
26276
  ` ${err.message}`
@@ -26651,7 +26761,7 @@ function isDockerRuntime() {
26651
26761
  import * as net2 from "node:net";
26652
26762
  import * as fs from "node:fs";
26653
26763
  import { homedir as homedir5 } from "node:os";
26654
- import { join as join15 } from "node:path";
26764
+ import { join as join16 } from "node:path";
26655
26765
  function defaultBrokerSocketPath() {
26656
26766
  if (fs.existsSync(OPERATOR_SOCKET_PATH))
26657
26767
  return OPERATOR_SOCKET_PATH;
@@ -26660,8 +26770,8 @@ function defaultBrokerSocketPath() {
26660
26770
  return LEGACY_SOCKET_PATH;
26661
26771
  }
26662
26772
  function vaultTokenFilePath(agentSlug) {
26663
- const base = process.env.SWITCHROOM_AGENTS_DIR || join15(homedir5(), ".switchroom", "agents");
26664
- return join15(base, agentSlug, ".vault-token");
26773
+ const base = process.env.SWITCHROOM_AGENTS_DIR || join16(homedir5(), ".switchroom", "agents");
26774
+ return join16(base, agentSlug, ".vault-token");
26665
26775
  }
26666
26776
  function readVaultTokenFile(agentSlug) {
26667
26777
  const filePath = vaultTokenFilePath(agentSlug);
@@ -26816,22 +26926,22 @@ var DEFAULT_TIMEOUT_MS2 = 2000, LEGACY_SOCKET_PATH, OPERATOR_SOCKET_PATH;
26816
26926
  var init_client2 = __esm(() => {
26817
26927
  init_protocol2();
26818
26928
  init_peercred();
26819
- LEGACY_SOCKET_PATH = join15(homedir5(), ".switchroom", "vault-broker.sock");
26820
- OPERATOR_SOCKET_PATH = join15(homedir5(), ".switchroom", "broker-operator", "sock");
26929
+ LEGACY_SOCKET_PATH = join16(homedir5(), ".switchroom", "vault-broker.sock");
26930
+ OPERATOR_SOCKET_PATH = join16(homedir5(), ".switchroom", "broker-operator", "sock");
26821
26931
  });
26822
26932
 
26823
26933
  // ../src/vault/resolver.ts
26824
26934
  import {
26825
26935
  chmodSync as chmodSync2,
26826
26936
  closeSync as closeSync2,
26827
- mkdirSync as mkdirSync11,
26937
+ mkdirSync as mkdirSync13,
26828
26938
  mkdtempSync,
26829
26939
  openSync as openSync2,
26830
26940
  rmSync,
26831
26941
  statSync as statSync5,
26832
26942
  writeSync as writeSync2
26833
26943
  } from "node:fs";
26834
- import { join as join16 } from "node:path";
26944
+ import { join as join17 } from "node:path";
26835
26945
  import { tmpdir } from "node:os";
26836
26946
  import { constants as fsConstants } from "node:fs";
26837
26947
  function isVaultReference(value) {
@@ -26883,11 +26993,11 @@ function materializationRoot() {
26883
26993
  return cachedRoot;
26884
26994
  const xdg = process.env.XDG_RUNTIME_DIR;
26885
26995
  if (xdg) {
26886
- const base = join16(xdg, "switchroom", "vault");
26887
- mkdirSync11(base, { recursive: true, mode: 448 });
26888
- cachedRoot = mkdtempSync(join16(base, "run-"));
26996
+ const base = join17(xdg, "switchroom", "vault");
26997
+ mkdirSync13(base, { recursive: true, mode: 448 });
26998
+ cachedRoot = mkdtempSync(join17(base, "run-"));
26889
26999
  } else {
26890
- cachedRoot = mkdtempSync(join16(tmpdir(), "switchroom-vault-"));
27000
+ cachedRoot = mkdtempSync(join17(tmpdir(), "switchroom-vault-"));
26891
27001
  }
26892
27002
  chmodSync2(cachedRoot, 448);
26893
27003
  return cachedRoot;
@@ -26902,13 +27012,13 @@ function writeFileExclusive(filePath, content3) {
26902
27012
  }
26903
27013
  }
26904
27014
  function materializeFilesEntry(key, files) {
26905
- const dir = join16(materializationRoot(), key);
27015
+ const dir = join17(materializationRoot(), key);
26906
27016
  if (materializedDirs.has(dir)) {
26907
27017
  try {
26908
27018
  rmSync(dir, { recursive: true, force: true });
26909
27019
  } catch {}
26910
27020
  }
26911
- mkdirSync11(dir, { recursive: true, mode: 448 });
27021
+ mkdirSync13(dir, { recursive: true, mode: 448 });
26912
27022
  chmodSync2(dir, 448);
26913
27023
  const st = statSync5(dir);
26914
27024
  if (typeof process.getuid === "function" && st.uid !== process.getuid()) {
@@ -26918,7 +27028,7 @@ function materializeFilesEntry(key, files) {
26918
27028
  if (filename.includes("/") || filename.includes("\\") || filename === ".." || filename === "." || filename.includes("\x00")) {
26919
27029
  throw new Error(`Refusing to materialize vault file with unsafe name: ${filename}`);
26920
27030
  }
26921
- const filePath = join16(dir, filename);
27031
+ const filePath = join17(dir, filename);
26922
27032
  const content3 = encoding === "base64" ? Buffer.from(value, "base64") : value;
26923
27033
  writeFileExclusive(filePath, content3);
26924
27034
  }
@@ -29432,8 +29542,8 @@ __export(exports_history, {
29432
29542
  checkpointWal: () => checkpointWal,
29433
29543
  _resetForTests: () => _resetForTests
29434
29544
  });
29435
- import { chmodSync as chmodSync4, existsSync as existsSync17, mkdirSync as mkdirSync15 } from "fs";
29436
- import { join as join19 } from "path";
29545
+ import { chmodSync as chmodSync4, existsSync as existsSync19, mkdirSync as mkdirSync17 } from "fs";
29546
+ import { join as join20 } from "path";
29437
29547
  function loadDatabaseClass() {
29438
29548
  if (DatabaseClass != null)
29439
29549
  return DatabaseClass;
@@ -29455,8 +29565,8 @@ function initHistory(stateDir, retentionDays = 30) {
29455
29565
  if (db != null)
29456
29566
  return;
29457
29567
  const Database = loadDatabaseClass();
29458
- mkdirSync15(stateDir, { recursive: true, mode: 448 });
29459
- const path2 = join19(stateDir, "history.db");
29568
+ mkdirSync17(stateDir, { recursive: true, mode: 448 });
29569
+ const path2 = join20(stateDir, "history.db");
29460
29570
  dbPath = path2;
29461
29571
  db = new Database(path2, { create: true });
29462
29572
  db.exec("PRAGMA journal_mode = WAL");
@@ -29494,7 +29604,7 @@ function initHistory(stateDir, retentionDays = 30) {
29494
29604
  }
29495
29605
  for (const suffix of ["", "-shm", "-wal"]) {
29496
29606
  const f = path2 + suffix;
29497
- if (existsSync17(f)) {
29607
+ if (existsSync19(f)) {
29498
29608
  try {
29499
29609
  chmodSync4(f, 420);
29500
29610
  } catch {}
@@ -29519,7 +29629,7 @@ function checkpointWal() {
29519
29629
  if (dbPath) {
29520
29630
  for (const suffix of ["-shm", "-wal"]) {
29521
29631
  const f = dbPath + suffix;
29522
- if (existsSync17(f)) {
29632
+ if (existsSync19(f)) {
29523
29633
  try {
29524
29634
  chmodSync4(f, 420);
29525
29635
  } catch {}
@@ -29686,14 +29796,14 @@ var init_history = __esm(() => {
29686
29796
  });
29687
29797
 
29688
29798
  // quota-check.ts
29689
- import { readFileSync as readFileSync17, existsSync as existsSync18 } from "fs";
29690
- import { join as join20 } from "path";
29799
+ import { readFileSync as readFileSync19, existsSync as existsSync20 } from "fs";
29800
+ import { join as join21 } from "path";
29691
29801
  function readOauthToken(claudeConfigDir) {
29692
- const tokenFile = join20(claudeConfigDir, ".oauth-token");
29693
- if (!existsSync18(tokenFile))
29802
+ const tokenFile = join21(claudeConfigDir, ".oauth-token");
29803
+ if (!existsSync20(tokenFile))
29694
29804
  return null;
29695
29805
  try {
29696
- const raw = readFileSync17(tokenFile, "utf-8").trim();
29806
+ const raw = readFileSync19(tokenFile, "utf-8").trim();
29697
29807
  return raw.length > 0 ? raw : null;
29698
29808
  } catch {
29699
29809
  return null;
@@ -32777,7 +32887,7 @@ var require_util = __commonJS((exports2) => {
32777
32887
  return path2;
32778
32888
  }
32779
32889
  exports2.normalize = normalize;
32780
- function join25(aRoot, aPath) {
32890
+ function join27(aRoot, aPath) {
32781
32891
  if (aRoot === "") {
32782
32892
  aRoot = ".";
32783
32893
  }
@@ -32809,7 +32919,7 @@ var require_util = __commonJS((exports2) => {
32809
32919
  }
32810
32920
  return joined;
32811
32921
  }
32812
- exports2.join = join25;
32922
+ exports2.join = join27;
32813
32923
  exports2.isAbsolute = function(aPath) {
32814
32924
  return aPath.charAt(0) === "/" || urlRegexp.test(aPath);
32815
32925
  };
@@ -32982,7 +33092,7 @@ var require_util = __commonJS((exports2) => {
32982
33092
  parsed.path = parsed.path.substring(0, index2 + 1);
32983
33093
  }
32984
33094
  }
32985
- sourceURL = join25(urlGenerate(parsed), sourceURL);
33095
+ sourceURL = join27(urlGenerate(parsed), sourceURL);
32986
33096
  }
32987
33097
  return normalize(sourceURL);
32988
33098
  }
@@ -35587,19 +35697,19 @@ function renderAuthLine(state4, agentName3, now = Date.now()) {
35587
35697
  }
35588
35698
 
35589
35699
  // gateway/quota-cache.ts
35590
- import { existsSync as existsSync33, readFileSync as readFileSync34, writeFileSync as writeFileSync26, mkdirSync as mkdirSync25 } from "fs";
35591
- import { join as join36, dirname as dirname9 } from "path";
35700
+ import { existsSync as existsSync35, readFileSync as readFileSync37, writeFileSync as writeFileSync29, mkdirSync as mkdirSync27 } from "fs";
35701
+ import { join as join38, dirname as dirname10 } from "path";
35592
35702
  function defaultCachePath() {
35593
- return process.env.SWITCHROOM_QUOTA_CACHE_PATH ?? join36(process.env.HOME ?? "/tmp", ".switchroom", "quota-cache.json");
35703
+ return process.env.SWITCHROOM_QUOTA_CACHE_PATH ?? join38(process.env.HOME ?? "/tmp", ".switchroom", "quota-cache.json");
35594
35704
  }
35595
35705
  function readQuotaCache(opts = {}) {
35596
35706
  const path2 = opts.path ?? defaultCachePath();
35597
35707
  const now = opts.now ?? Date.now();
35598
- if (!existsSync33(path2))
35708
+ if (!existsSync35(path2))
35599
35709
  return null;
35600
35710
  let entry;
35601
35711
  try {
35602
- entry = JSON.parse(readFileSync34(path2, "utf8"));
35712
+ entry = JSON.parse(readFileSync37(path2, "utf8"));
35603
35713
  } catch {
35604
35714
  return null;
35605
35715
  }
@@ -35625,8 +35735,8 @@ function writeQuotaCache(result, opts = {}) {
35625
35735
  result
35626
35736
  };
35627
35737
  try {
35628
- mkdirSync25(dirname9(path2), { recursive: true });
35629
- writeFileSync26(path2, JSON.stringify(entry, null, 2), { mode: 384 });
35738
+ mkdirSync27(dirname10(path2), { recursive: true });
35739
+ writeFileSync29(path2, JSON.stringify(entry, null, 2), { mode: 384 });
35630
35740
  } catch {}
35631
35741
  }
35632
35742
  var DEFAULT_TTL_MS4, RATE_LIMIT_TTL_MS;
@@ -35636,8 +35746,8 @@ var init_quota_cache = __esm(() => {
35636
35746
  });
35637
35747
 
35638
35748
  // gateway/boot-probes.ts
35639
- import { readFileSync as readFileSync35, readdirSync as readdirSync8, existsSync as existsSync34 } from "fs";
35640
- import { join as join37 } from "path";
35749
+ import { readFileSync as readFileSync38, readdirSync as readdirSync8, existsSync as existsSync36 } from "fs";
35750
+ import { join as join39 } from "path";
35641
35751
  import { execFile as execFileCb } from "child_process";
35642
35752
  import { promisify } from "util";
35643
35753
  async function withTimeout(label, p, timeoutMs = PROBE_TIMEOUT_MS) {
@@ -35679,11 +35789,11 @@ function mapPlan(billingType, hasExtra) {
35679
35789
  }
35680
35790
  async function probeAccount(agentDir) {
35681
35791
  return withTimeout("Account", (async () => {
35682
- const claudeDir = join37(agentDir, ".claude");
35683
- const claudeJsonPath = join37(claudeDir, ".claude.json");
35792
+ const claudeDir = join39(agentDir, ".claude");
35793
+ const claudeJsonPath = join39(claudeDir, ".claude.json");
35684
35794
  let cfg = {};
35685
35795
  try {
35686
- const raw = readFileSync35(claudeJsonPath, "utf8");
35796
+ const raw = readFileSync38(claudeJsonPath, "utf8");
35687
35797
  cfg = JSON.parse(raw);
35688
35798
  } catch {
35689
35799
  return { status: "fail", label: "Account", detail: "no .claude.json" };
@@ -35701,12 +35811,12 @@ async function probeAccount(agentDir) {
35701
35811
  let tokenStr = "";
35702
35812
  let status = "ok";
35703
35813
  for (const candidate of [
35704
- join37(claudeDir, ".oauth-token.meta.json"),
35705
- join37(claudeDir, "accounts", "default", ".oauth-token.meta.json")
35814
+ join39(claudeDir, ".oauth-token.meta.json"),
35815
+ join39(claudeDir, "accounts", "default", ".oauth-token.meta.json")
35706
35816
  ]) {
35707
- if (existsSync34(candidate)) {
35817
+ if (existsSync36(candidate)) {
35708
35818
  try {
35709
- const meta = JSON.parse(readFileSync35(candidate, "utf8"));
35819
+ const meta = JSON.parse(readFileSync38(candidate, "utf8"));
35710
35820
  if (meta.expiresAt) {
35711
35821
  tokenStr = " \u00b7 " + formatDaysFromNow(meta.expiresAt);
35712
35822
  const daysLeft = Math.round((meta.expiresAt - Date.now()) / 86400000);
@@ -35881,9 +35991,9 @@ async function resolveTmuxSupervisorPid(agentName3, execFileImpl) {
35881
35991
  if (!cgroup)
35882
35992
  return null;
35883
35993
  const procsPath = `/sys/fs/cgroup${cgroup}/cgroup.procs`;
35884
- if (!existsSync34(procsPath))
35994
+ if (!existsSync36(procsPath))
35885
35995
  return null;
35886
- const pidsRaw = readFileSync35(procsPath, "utf-8");
35996
+ const pidsRaw = readFileSync38(procsPath, "utf-8");
35887
35997
  const pids = pidsRaw.split(`
35888
35998
  `).map((s) => s.trim()).filter(Boolean);
35889
35999
  if (pids.length === 0)
@@ -35896,7 +36006,7 @@ async function resolveTmuxSupervisorPid(agentName3, execFileImpl) {
35896
36006
  let rss = 0;
35897
36007
  let comm = "";
35898
36008
  try {
35899
- const status = readFileSync35(`/proc/${pid}/status`, "utf-8");
36009
+ const status = readFileSync38(`/proc/${pid}/status`, "utf-8");
35900
36010
  const rssLine = status.split(`
35901
36011
  `).find((l) => l.startsWith("VmRSS:"));
35902
36012
  if (rssLine) {
@@ -35908,7 +36018,7 @@ async function resolveTmuxSupervisorPid(agentName3, execFileImpl) {
35908
36018
  continue;
35909
36019
  }
35910
36020
  try {
35911
- comm = readFileSync35(`/proc/${pid}/comm`, "utf-8").trim();
36021
+ comm = readFileSync38(`/proc/${pid}/comm`, "utf-8").trim();
35912
36022
  } catch {}
35913
36023
  candidates.push({ pid, rss, comm });
35914
36024
  }
@@ -36088,9 +36198,9 @@ async function probeQuota(claudeConfigDir, _agentDir, fetchImpl = fetch, opts =
36088
36198
  let claudeDirForProbe = null;
36089
36199
  for (const candidate of [
36090
36200
  claudeConfigDir,
36091
- join37(claudeConfigDir, "accounts", "default")
36201
+ join39(claudeConfigDir, "accounts", "default")
36092
36202
  ]) {
36093
- if (existsSync34(join37(candidate, ".oauth-token"))) {
36203
+ if (existsSync36(join39(candidate, ".oauth-token"))) {
36094
36204
  claudeDirForProbe = candidate;
36095
36205
  break;
36096
36206
  }
@@ -36155,7 +36265,7 @@ async function probeHindsight(bankName, fetchImpl = fetch) {
36155
36265
  }
36156
36266
  function readContainerBootTimeMsForProbe() {
36157
36267
  try {
36158
- const stat1 = readFileSync35("/proc/1/stat", "utf8");
36268
+ const stat1 = readFileSync38("/proc/1/stat", "utf8");
36159
36269
  const lastParen = stat1.lastIndexOf(")");
36160
36270
  if (lastParen < 0)
36161
36271
  return null;
@@ -36163,7 +36273,7 @@ function readContainerBootTimeMsForProbe() {
36163
36273
  const starttimeTicks = Number(after[19]);
36164
36274
  if (!Number.isFinite(starttimeTicks))
36165
36275
  return null;
36166
- const procStat = readFileSync35("/proc/stat", "utf8");
36276
+ const procStat = readFileSync38("/proc/stat", "utf8");
36167
36277
  const btimeLine = procStat.split(`
36168
36278
  `).find((l) => l.startsWith("btime "));
36169
36279
  if (!btimeLine)
@@ -36261,7 +36371,7 @@ async function probeUds(label, socketPath, opts = {}) {
36261
36371
  }
36262
36372
  return withTimeout(label, (async () => {
36263
36373
  if (!opts.connectImpl) {
36264
- if (!existsSync34(socketPath)) {
36374
+ if (!existsSync36(socketPath)) {
36265
36375
  return {
36266
36376
  status: "fail",
36267
36377
  label,
@@ -36325,7 +36435,7 @@ async function probeSkills(agentDir, opts = {}) {
36325
36435
  return withTimeout("Skills", (async () => {
36326
36436
  const fs2 = opts.fs ?? realSkillsFs;
36327
36437
  const max = opts.maxNamesShown ?? 3;
36328
- const skillsDir = join37(agentDir, ".claude", "skills");
36438
+ const skillsDir = join39(agentDir, ".claude", "skills");
36329
36439
  if (!fs2.exists(skillsDir)) {
36330
36440
  return { status: "ok", label: "Skills", detail: "no skills dir" };
36331
36441
  }
@@ -36340,17 +36450,17 @@ async function probeSkills(agentDir, opts = {}) {
36340
36450
  }
36341
36451
  const dangling = [];
36342
36452
  for (const name of entries) {
36343
- const skillPath = join37(skillsDir, name);
36453
+ const skillPath = join39(skillsDir, name);
36344
36454
  if (!fs2.exists(skillPath)) {
36345
36455
  dangling.push(name);
36346
36456
  continue;
36347
36457
  }
36348
- const skillMd = join37(skillPath, "SKILL.md");
36458
+ const skillMd = join39(skillPath, "SKILL.md");
36349
36459
  if (!fs2.exists(skillMd) && !fs2.exists(skillPath + ".md")) {
36350
36460
  continue;
36351
36461
  }
36352
36462
  }
36353
- const overlayDir = opts.overlaySkillsDir ?? join37(agentDir, "skills.d");
36463
+ const overlayDir = opts.overlaySkillsDir ?? join39(agentDir, "skills.d");
36354
36464
  const overlaySlugs = new Set;
36355
36465
  if (fs2.exists(overlayDir)) {
36356
36466
  let overlayEntries = [];
@@ -36392,8 +36502,8 @@ function renderBucketedSkills(switchroom, agent) {
36392
36502
  }
36393
36503
  async function probeConnections(agentDir, opts = {}) {
36394
36504
  return withTimeout("Connections", (async () => {
36395
- const path2 = join37(agentDir, ".claude", "connection-health.json");
36396
- const read = opts.readFileImpl ?? ((p) => readFileSync35(p, "utf8"));
36505
+ const path2 = join39(agentDir, ".claude", "connection-health.json");
36506
+ const read = opts.readFileImpl ?? ((p) => readFileSync38(p, "utf8"));
36397
36507
  let issues = [];
36398
36508
  try {
36399
36509
  const parsed = JSON.parse(read(path2));
@@ -36424,25 +36534,25 @@ var init_boot_probes = __esm(() => {
36424
36534
  execFile = promisify(execFileCb);
36425
36535
  realProcFs = {
36426
36536
  readdir: (p) => readdirSync8(p),
36427
- readFile: (p) => readFileSync35(p, "utf-8")
36537
+ readFile: (p) => readFileSync38(p, "utf-8")
36428
36538
  };
36429
36539
  realSchedulerFs = {
36430
- readFile: (p) => readFileSync35(p, "utf-8"),
36540
+ readFile: (p) => readFileSync38(p, "utf-8"),
36431
36541
  mtimeMs: (p) => {
36432
36542
  const { statSync: statSync11 } = __require("fs");
36433
36543
  return statSync11(p).mtimeMs;
36434
36544
  },
36435
- exists: (p) => existsSync34(p)
36545
+ exists: (p) => existsSync36(p)
36436
36546
  };
36437
36547
  realSkillsFs = {
36438
36548
  readdir: (p) => readdirSync8(p),
36439
- exists: (p) => existsSync34(p)
36549
+ exists: (p) => existsSync36(p)
36440
36550
  };
36441
36551
  });
36442
36552
 
36443
36553
  // gateway/boot-issue-cache.ts
36444
- import { existsSync as existsSync35, readFileSync as readFileSync36, writeFileSync as writeFileSync27, mkdirSync as mkdirSync26, renameSync as renameSync12 } from "fs";
36445
- import { dirname as dirname10 } from "path";
36554
+ import { existsSync as existsSync37, readFileSync as readFileSync39, writeFileSync as writeFileSync30, mkdirSync as mkdirSync28, renameSync as renameSync13 } from "fs";
36555
+ import { dirname as dirname11 } from "path";
36446
36556
  function fingerprintProbe(key, r) {
36447
36557
  if (r.status === "ok")
36448
36558
  return `${key}:ok`;
@@ -36521,11 +36631,11 @@ function diffProbes(probes, cache, opts = {}) {
36521
36631
  return out;
36522
36632
  }
36523
36633
  function loadCache(path2, now = Date.now) {
36524
- if (!existsSync35(path2))
36634
+ if (!existsSync37(path2))
36525
36635
  return { ...EMPTY_CACHE, probes: {} };
36526
36636
  let raw;
36527
36637
  try {
36528
- raw = readFileSync36(path2, "utf-8");
36638
+ raw = readFileSync39(path2, "utf-8");
36529
36639
  } catch {
36530
36640
  return { ...EMPTY_CACHE, probes: {} };
36531
36641
  }
@@ -36534,7 +36644,7 @@ function loadCache(path2, now = Date.now) {
36534
36644
  parsed = JSON.parse(raw);
36535
36645
  } catch {
36536
36646
  try {
36537
- renameSync12(path2, `${path2}.corrupt-${now()}`);
36647
+ renameSync13(path2, `${path2}.corrupt-${now()}`);
36538
36648
  } catch {}
36539
36649
  return { ...EMPTY_CACHE, probes: {} };
36540
36650
  }
@@ -36568,10 +36678,10 @@ function applyAndSave(path2, cache, diff) {
36568
36678
  }
36569
36679
  }
36570
36680
  try {
36571
- mkdirSync26(dirname10(path2), { recursive: true });
36681
+ mkdirSync28(dirname11(path2), { recursive: true });
36572
36682
  const tmp = `${path2}.tmp`;
36573
- writeFileSync27(tmp, JSON.stringify(next), { mode: 384 });
36574
- renameSync12(tmp, path2);
36683
+ writeFileSync30(tmp, JSON.stringify(next), { mode: 384 });
36684
+ renameSync13(tmp, path2);
36575
36685
  } catch {}
36576
36686
  return next;
36577
36687
  }
@@ -36584,8 +36694,8 @@ var init_boot_issue_cache = __esm(() => {
36584
36694
 
36585
36695
  // gateway/config-snapshot.ts
36586
36696
  import { createHash as createHash2 } from "crypto";
36587
- import { existsSync as existsSync36, readFileSync as readFileSync37, writeFileSync as writeFileSync28, mkdirSync as mkdirSync27, renameSync as renameSync13 } from "fs";
36588
- import { dirname as dirname11 } from "path";
36697
+ import { existsSync as existsSync38, readFileSync as readFileSync40, writeFileSync as writeFileSync31, mkdirSync as mkdirSync29, renameSync as renameSync14 } from "fs";
36698
+ import { dirname as dirname12 } from "path";
36589
36699
  function hashStringArray(items) {
36590
36700
  if (!items || items.length === 0)
36591
36701
  return null;
@@ -36649,11 +36759,11 @@ function renderConfigChangeDim(dim) {
36649
36759
  }
36650
36760
  }
36651
36761
  function loadSnapshot(path2, now = Date.now) {
36652
- if (!existsSync36(path2))
36762
+ if (!existsSync38(path2))
36653
36763
  return null;
36654
36764
  let raw;
36655
36765
  try {
36656
- raw = readFileSync37(path2, "utf-8");
36766
+ raw = readFileSync40(path2, "utf-8");
36657
36767
  } catch {
36658
36768
  return null;
36659
36769
  }
@@ -36662,7 +36772,7 @@ function loadSnapshot(path2, now = Date.now) {
36662
36772
  parsed = JSON.parse(raw);
36663
36773
  } catch {
36664
36774
  try {
36665
- renameSync13(path2, `${path2}.corrupt-${now()}`);
36775
+ renameSync14(path2, `${path2}.corrupt-${now()}`);
36666
36776
  } catch {}
36667
36777
  return null;
36668
36778
  }
@@ -36683,10 +36793,10 @@ function loadSnapshot(path2, now = Date.now) {
36683
36793
  }
36684
36794
  function persistSnapshot(path2, snapshot) {
36685
36795
  try {
36686
- mkdirSync27(dirname11(path2), { recursive: true });
36796
+ mkdirSync29(dirname12(path2), { recursive: true });
36687
36797
  const tmp = `${path2}.tmp`;
36688
- writeFileSync28(tmp, JSON.stringify(snapshot), { mode: 384 });
36689
- renameSync13(tmp, path2);
36798
+ writeFileSync31(tmp, JSON.stringify(snapshot), { mode: 384 });
36799
+ renameSync14(tmp, path2);
36690
36800
  } catch {}
36691
36801
  }
36692
36802
  var init_config_snapshot = __esm(() => {
@@ -36694,13 +36804,13 @@ var init_config_snapshot = __esm(() => {
36694
36804
  });
36695
36805
 
36696
36806
  // gateway/boot-card-msgid.ts
36697
- import { readFileSync as readFileSync38, writeFileSync as writeFileSync29 } from "node:fs";
36807
+ import { readFileSync as readFileSync41, writeFileSync as writeFileSync32 } from "node:fs";
36698
36808
  function bootCardChatKey(chatId, threadId) {
36699
36809
  return `${chatId}:${threadId ?? ""}`;
36700
36810
  }
36701
36811
  function readStore(path2) {
36702
36812
  try {
36703
- const parsed = JSON.parse(readFileSync38(path2, "utf8"));
36813
+ const parsed = JSON.parse(readFileSync41(path2, "utf8"));
36704
36814
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
36705
36815
  return parsed;
36706
36816
  }
@@ -36719,7 +36829,7 @@ function saveBootCardMsgId(path2, chatKey3, messageId) {
36719
36829
  if (store2[chatKey3] === messageId)
36720
36830
  return;
36721
36831
  store2[chatKey3] = messageId;
36722
- writeFileSync29(path2, JSON.stringify(store2), "utf8");
36832
+ writeFileSync32(path2, JSON.stringify(store2), "utf8");
36723
36833
  } catch {}
36724
36834
  }
36725
36835
  var init_boot_card_msgid = () => {};
@@ -36734,7 +36844,7 @@ __export(exports_boot_card, {
36734
36844
  renderBootCard: () => renderBootCard,
36735
36845
  renderAccountRows: () => renderAuthLine
36736
36846
  });
36737
- import { join as join38 } from "path";
36847
+ import { join as join40 } from "path";
36738
36848
  function resolvePersonaName(slug, loadConfig3) {
36739
36849
  try {
36740
36850
  const config = loadConfig3 ? loadConfig3() : loadConfig();
@@ -36825,7 +36935,7 @@ function renderBootCard(opts) {
36825
36935
  return stackCardLines(flatLines);
36826
36936
  }
36827
36937
  async function runAllProbes(opts) {
36828
- const claudeDir = join38(opts.agentDir, ".claude");
36938
+ const claudeDir = join40(opts.agentDir, ".claude");
36829
36939
  const probes = {};
36830
36940
  const slug = opts.agentSlug ?? opts.agentName;
36831
36941
  await Promise.allSettled([
@@ -36866,6 +36976,15 @@ async function startBootCard(chatId, threadId, bot, opts, ackMessageId, log) {
36866
36976
  const logger2 = log ?? ((l) => process.stderr.write(l));
36867
36977
  const setTimeoutFn = opts.setTimeoutImpl ?? setTimeout;
36868
36978
  const settleMs = opts.settleWindowMs ?? SETTLE_WINDOW_MS;
36979
+ if (opts.floodStatePath != null) {
36980
+ const now = (opts.nowMs ?? Date.now)();
36981
+ const remainingMs = suppressNonEssentialSendMs(opts.floodStatePath, now);
36982
+ if (remainingMs > 0) {
36983
+ logger2(`telegram gateway: boot-card: SUPPRESSED \u2014 Telegram flood-wait active for ~${Math.round(remainingMs / 1000)}s; ` + `not posting a restart card into the open ban window (issue #2923)
36984
+ `);
36985
+ return { messageId: -1, complete: () => {} };
36986
+ }
36987
+ }
36869
36988
  const ackText = renderBootCard({
36870
36989
  agentName: opts.agentName,
36871
36990
  agentSlug: opts.agentSlug,
@@ -37079,6 +37198,7 @@ var init_boot_card = __esm(() => {
37079
37198
  init_boot_issue_cache();
37080
37199
  init_config_snapshot();
37081
37200
  init_boot_card_msgid();
37201
+ init_flood_circuit_breaker();
37082
37202
  init_loader();
37083
37203
  init_merge();
37084
37204
  DOT = {
@@ -37614,7 +37734,7 @@ __export(exports_materialize_bot_token, {
37614
37734
  materializeBotToken: () => materializeBotToken,
37615
37735
  BotTokenMaterializeError: () => BotTokenMaterializeError
37616
37736
  });
37617
- import { existsSync as existsSync44 } from "node:fs";
37737
+ import { existsSync as existsSync46 } from "node:fs";
37618
37738
  function pickConfiguredToken(config, agentName3) {
37619
37739
  if (agentName3) {
37620
37740
  const agent = config.agents?.[agentName3];
@@ -37628,7 +37748,7 @@ function tryDirectVaultRead3(ref, config, passphrase) {
37628
37748
  if (!passphrase)
37629
37749
  return null;
37630
37750
  const vaultPath = resolvePath(config.vault?.path ?? "~/.switchroom/vault.enc");
37631
- if (!existsSync44(vaultPath))
37751
+ if (!existsSync46(vaultPath))
37632
37752
  return null;
37633
37753
  try {
37634
37754
  const secrets = openVault(passphrase, vaultPath);
@@ -37720,7 +37840,7 @@ __export(exports_tmux, {
37720
37840
  captureAgentPane: () => captureAgentPane
37721
37841
  });
37722
37842
  import { execFileSync as execFileSync5 } from "node:child_process";
37723
- import { mkdirSync as mkdirSync34, readdirSync as readdirSync10, statSync as statSync15, unlinkSync as unlinkSync19, writeFileSync as writeFileSync36 } from "node:fs";
37843
+ import { mkdirSync as mkdirSync36, readdirSync as readdirSync10, statSync as statSync15, unlinkSync as unlinkSync19, writeFileSync as writeFileSync39 } from "node:fs";
37724
37844
  import { resolve as resolve10 } from "node:path";
37725
37845
  function captureAgentPane(opts) {
37726
37846
  const { agentName: agentName3, agentDir, reason } = opts;
@@ -37732,7 +37852,7 @@ function captureAgentPane(opts) {
37732
37852
  const reasonSlug = sanitizeReason(reason);
37733
37853
  const outPath = resolve10(outDir, `${ts}-${reasonSlug}.txt`);
37734
37854
  try {
37735
- mkdirSync34(outDir, { recursive: true, mode: 493 });
37855
+ mkdirSync36(outDir, { recursive: true, mode: 493 });
37736
37856
  } catch (err) {
37737
37857
  const msg = `mkdir crash-reports failed: ${err.message}`;
37738
37858
  console.error(`[tmux-capture] ${agentName3}: ${msg}`);
@@ -37766,7 +37886,7 @@ function captureAgentPane(opts) {
37766
37886
  ` + `
37767
37887
  `;
37768
37888
  try {
37769
- writeFileSync36(outPath, Buffer.concat([Buffer.from(header, "utf8"), body]), {
37889
+ writeFileSync39(outPath, Buffer.concat([Buffer.from(header, "utf8"), body]), {
37770
37890
  mode: 420
37771
37891
  });
37772
37892
  } catch (err) {
@@ -38470,23 +38590,23 @@ var import_runner3 = __toESM(require_mod3(), 1);
38470
38590
  import { randomBytes as randomBytes8, createHash as createHash3 } from "crypto";
38471
38591
  import { execFileSync as execFileSync6, execSync as execSync2, spawn } from "child_process";
38472
38592
  import {
38473
- readFileSync as readFileSync47,
38474
- writeFileSync as writeFileSync37,
38475
- mkdirSync as mkdirSync35,
38593
+ readFileSync as readFileSync50,
38594
+ writeFileSync as writeFileSync40,
38595
+ mkdirSync as mkdirSync37,
38476
38596
  readdirSync as readdirSync11,
38477
- rmSync as rmSync4,
38597
+ rmSync as rmSync5,
38478
38598
  statSync as statSync16,
38479
- renameSync as renameSync15,
38599
+ renameSync as renameSync16,
38480
38600
  realpathSync as realpathSync4,
38481
38601
  chmodSync as chmodSync8,
38482
38602
  openSync as openSync9,
38483
38603
  closeSync as closeSync9,
38484
- existsSync as existsSync45,
38604
+ existsSync as existsSync47,
38485
38605
  unlinkSync as unlinkSync20,
38486
38606
  appendFileSync as appendFileSync6
38487
38607
  } from "fs";
38488
38608
  import { homedir as homedir16 } from "os";
38489
- import { join as join49, extname, sep as sep3, basename as basename12 } from "path";
38609
+ import { join as join51, extname, sep as sep3, basename as basename12 } from "path";
38490
38610
 
38491
38611
  // plugin-logger.ts
38492
38612
  import { appendFileSync, mkdirSync, renameSync, statSync, existsSync } from "fs";
@@ -38776,6 +38896,28 @@ function resolveSafeBoundaryEnabled(configured) {
38776
38896
  return configured !== false;
38777
38897
  }
38778
38898
 
38899
+ // gateway/busy-ack.ts
38900
+ var BUSY_ACK_STEP_AGE_THRESHOLD_MS = 12000;
38901
+ function shouldPostBusyAck(input) {
38902
+ if (input.gateDecision !== "buffer-until-idle" && input.gateDecision !== "steer")
38903
+ return false;
38904
+ if (!input.midToolCall)
38905
+ return false;
38906
+ if (input.stepAgeMs == null || input.stepAgeMs < BUSY_ACK_STEP_AGE_THRESHOLD_MS)
38907
+ return false;
38908
+ if (input.alreadyAcked)
38909
+ return false;
38910
+ return true;
38911
+ }
38912
+ function formatBusyAckText(input) {
38913
+ const name = input.toolName ?? "a long-running step";
38914
+ const activity = input.toolLabel != null && input.toolLabel.length > 0 ? `${name}: ${input.toolLabel}` : name;
38915
+ if (input.gateDecision === "steer") {
38916
+ return `\u23f3 Steer noted \u2014 currently inside \`${activity}\`; I'll fold it in when this step finishes.`;
38917
+ }
38918
+ return `\u23f3 Queued \u2014 currently inside \`${activity}\`; I'll answer when this step finishes.`;
38919
+ }
38920
+
38779
38921
  // sticker-aliases.ts
38780
38922
  function looksLikeFileId(s) {
38781
38923
  return /^[A-Za-z0-9_-]{10,200}$/.test(s);
@@ -39229,12 +39371,13 @@ class VoiceOnDemandCache {
39229
39371
  this.flush();
39230
39372
  return null;
39231
39373
  }
39232
- const { text, voice, speed, filePath } = entry;
39374
+ const { text, voice, speed, filePath, telegramFileId } = entry;
39233
39375
  return {
39234
39376
  text,
39235
39377
  speed,
39236
39378
  ...voice !== undefined ? { voice } : {},
39237
- ...filePath !== undefined ? { filePath } : {}
39379
+ ...filePath !== undefined ? { filePath } : {},
39380
+ ...telegramFileId !== undefined ? { telegramFileId } : {}
39238
39381
  };
39239
39382
  }
39240
39383
  setFilePath(token, filePath) {
@@ -39244,6 +39387,15 @@ class VoiceOnDemandCache {
39244
39387
  entry.filePath = filePath;
39245
39388
  this.flush();
39246
39389
  }
39390
+ setTelegramFileId(token, fileId) {
39391
+ if (fileId.length === 0)
39392
+ return;
39393
+ const entry = this.store.get(token);
39394
+ if (entry == null || entry.expiresAt <= this.now())
39395
+ return;
39396
+ entry.telegramFileId = fileId;
39397
+ this.flush();
39398
+ }
39247
39399
  prune(tokens) {
39248
39400
  let changed = false;
39249
39401
  for (const token of tokens) {
@@ -39332,6 +39484,49 @@ function mayInjectListenButton(rawKeyboard) {
39332
39484
  return !rawKeyboard.some((row) => Array.isArray(row) && row.length > 0);
39333
39485
  }
39334
39486
 
39487
+ // voice-send.ts
39488
+ function extractVoiceFileId(sent) {
39489
+ const id = sent?.voice?.file_id;
39490
+ return typeof id === "string" && id.length > 0 ? id : undefined;
39491
+ }
39492
+ function isHttp400Error(err) {
39493
+ if (err == null || typeof err !== "object")
39494
+ return false;
39495
+ const e = err;
39496
+ return e.error_code === 400;
39497
+ }
39498
+ async function sendVoiceReusingFileId(deps) {
39499
+ const shouldReupload = deps.isInvalidFileIdError ?? isHttp400Error;
39500
+ if (deps.fileId != null && deps.fileId.length > 0) {
39501
+ try {
39502
+ const sent = await deps.sendByFileId(deps.fileId);
39503
+ const fresh = extractVoiceFileId(sent);
39504
+ if (fresh != null)
39505
+ deps.onFileId(fresh);
39506
+ return { ok: true, path: "file_id", refreshed: false };
39507
+ } catch (err) {
39508
+ if (!shouldReupload(err)) {
39509
+ return { ok: false, reason: "send-failed", error: err };
39510
+ }
39511
+ deps.log?.(`voice-ondemand: stored file_id rejected (${String(err?.description ?? err?.message ?? err)}) \u2014 re-uploading from disk
39512
+ `);
39513
+ }
39514
+ }
39515
+ const audio = await deps.loadAudio();
39516
+ if (audio == null)
39517
+ return { ok: false, reason: "no-audio" };
39518
+ const refreshed = deps.fileId != null && deps.fileId.length > 0;
39519
+ try {
39520
+ const sent = await deps.sendByUpload(audio);
39521
+ const fresh = extractVoiceFileId(sent);
39522
+ if (fresh != null)
39523
+ deps.onFileId(fresh);
39524
+ return { ok: true, path: "upload", refreshed };
39525
+ } catch (err) {
39526
+ return { ok: false, reason: "send-failed", error: err };
39527
+ }
39528
+ }
39529
+
39335
39530
  // voice-presynth.ts
39336
39531
  import { readdirSync, statSync as statSync2, unlinkSync, mkdirSync as mkdirSync3, writeFileSync as writeFileSync2, renameSync as renameSync3 } from "fs";
39337
39532
  import { join as join2 } from "path";
@@ -41954,6 +42149,7 @@ import { AsyncLocalStorage } from "async_hooks";
41954
42149
  var import_grammy = __toESM(require_mod2(), 1);
41955
42150
 
41956
42151
  // shared/bot-runtime.ts
42152
+ init_flood_circuit_breaker();
41957
42153
  var tgPostTagStore = new AsyncLocalStorage;
41958
42154
  function escapeHtmlForTg(text) {
41959
42155
  return text.replace(/([\\`*_~=\[\]|])/g, "\\$1");
@@ -41981,13 +42177,13 @@ function renderVaultRequestAccessCard(req) {
41981
42177
  }
41982
42178
 
41983
42179
  // gateway/permission-card-store.ts
41984
- import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, unlinkSync as unlinkSync2 } from "node:fs";
42180
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync4, unlinkSync as unlinkSync2 } from "node:fs";
41985
42181
  import { join as join4 } from "node:path";
41986
42182
  function createPermissionCardStore(stateDir) {
41987
42183
  const filePath = join4(stateDir, "pending-perm-cards.json");
41988
42184
  function read() {
41989
42185
  try {
41990
- const raw = readFileSync3(filePath, "utf-8");
42186
+ const raw = readFileSync4(filePath, "utf-8");
41991
42187
  const parsed = JSON.parse(raw);
41992
42188
  return Array.isArray(parsed) ? parsed : [];
41993
42189
  } catch {
@@ -41996,7 +42192,7 @@ function createPermissionCardStore(stateDir) {
41996
42192
  }
41997
42193
  function write(entries) {
41998
42194
  try {
41999
- writeFileSync3(filePath, JSON.stringify(entries), { encoding: "utf-8", mode: 384 });
42195
+ writeFileSync4(filePath, JSON.stringify(entries), { encoding: "utf-8", mode: 384 });
42000
42196
  } catch (err) {
42001
42197
  process.stderr.write(`telegram gateway: permission-card-store write failed: ${err.message}
42002
42198
  `);
@@ -42032,13 +42228,13 @@ function createPermissionCardStore(stateDir) {
42032
42228
  }
42033
42229
 
42034
42230
  // gateway/pending-card-store.ts
42035
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync4, unlinkSync as unlinkSync3, chmodSync } from "node:fs";
42231
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync5, unlinkSync as unlinkSync3, chmodSync } from "node:fs";
42036
42232
  import { join as join5 } from "node:path";
42037
42233
  function createPendingCardStore(stateDir) {
42038
42234
  const filePath = join5(stateDir, "pending-approval-cards.json");
42039
42235
  function read() {
42040
42236
  try {
42041
- const raw = readFileSync4(filePath, "utf-8");
42237
+ const raw = readFileSync5(filePath, "utf-8");
42042
42238
  const parsed = JSON.parse(raw);
42043
42239
  return Array.isArray(parsed) ? parsed : [];
42044
42240
  } catch {
@@ -42047,7 +42243,7 @@ function createPendingCardStore(stateDir) {
42047
42243
  }
42048
42244
  function write(entries) {
42049
42245
  try {
42050
- writeFileSync4(filePath, JSON.stringify(entries), { encoding: "utf-8", mode: 384 });
42246
+ writeFileSync5(filePath, JSON.stringify(entries), { encoding: "utf-8", mode: 384 });
42051
42247
  chmodSync(filePath, 384);
42052
42248
  } catch (err) {
42053
42249
  process.stderr.write(`telegram gateway: pending-card-store write failed: ${err.message}
@@ -42216,7 +42412,7 @@ function distinctRequestIds(cards) {
42216
42412
  }
42217
42413
 
42218
42414
  // gateway/missed-approvals-store.ts
42219
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync5, unlinkSync as unlinkSync4 } from "node:fs";
42415
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync6, unlinkSync as unlinkSync4 } from "node:fs";
42220
42416
  import { join as join6 } from "node:path";
42221
42417
  var MAX_PENDING = 50;
42222
42418
  var MAX_DELIVERED = 20;
@@ -42224,7 +42420,7 @@ function createMissedApprovalsStore(stateDir) {
42224
42420
  const filePath = join6(stateDir, "missed-approvals.json");
42225
42421
  function read() {
42226
42422
  try {
42227
- const raw = readFileSync5(filePath, "utf-8");
42423
+ const raw = readFileSync6(filePath, "utf-8");
42228
42424
  const parsed = JSON.parse(raw);
42229
42425
  return {
42230
42426
  pending: Array.isArray(parsed?.pending) ? parsed.pending : [],
@@ -42236,7 +42432,7 @@ function createMissedApprovalsStore(stateDir) {
42236
42432
  }
42237
42433
  function write(f) {
42238
42434
  try {
42239
- writeFileSync5(filePath, JSON.stringify(f), { encoding: "utf-8", mode: 384 });
42435
+ writeFileSync6(filePath, JSON.stringify(f), { encoding: "utf-8", mode: 384 });
42240
42436
  } catch (err) {
42241
42437
  process.stderr.write(`telegram gateway: missed-approvals-store write failed: ${err.message}
42242
42438
  `);
@@ -42293,7 +42489,7 @@ function createMissedApprovalsStore(stateDir) {
42293
42489
  }
42294
42490
 
42295
42491
  // gateway/always-allow-persist-queue.ts
42296
- import { readFileSync as readFileSync6, writeFileSync as writeFileSync6, unlinkSync as unlinkSync5 } from "node:fs";
42492
+ import { readFileSync as readFileSync7, writeFileSync as writeFileSync7, unlinkSync as unlinkSync5 } from "node:fs";
42297
42493
  import { join as join7 } from "node:path";
42298
42494
  var MAX_ATTEMPTS = 5;
42299
42495
  var MAX_AGE_MS = 24 * 60 * 60 * 1000;
@@ -42313,7 +42509,7 @@ function computeBackoffMs(attempts, retryAfterMs) {
42313
42509
  }
42314
42510
  return exp;
42315
42511
  }
42316
- function createAlwaysAllowPersistQueue(stateDir, writeFileSyncFn = writeFileSync6) {
42512
+ function createAlwaysAllowPersistQueue(stateDir, writeFileSyncFn = writeFileSync7) {
42317
42513
  const filePath = join7(stateDir, "always-allow-persist-queue.json");
42318
42514
  let lock = Promise.resolve();
42319
42515
  function withLock(fn) {
@@ -42323,7 +42519,7 @@ function createAlwaysAllowPersistQueue(stateDir, writeFileSyncFn = writeFileSync
42323
42519
  }
42324
42520
  function read() {
42325
42521
  try {
42326
- const raw = readFileSync6(filePath, "utf-8");
42522
+ const raw = readFileSync7(filePath, "utf-8");
42327
42523
  const parsed = JSON.parse(raw);
42328
42524
  return { entries: Array.isArray(parsed?.entries) ? parsed.entries : [] };
42329
42525
  } catch {
@@ -52753,6 +52949,7 @@ function renderSafe(doc, source, maxLen = RICH_MESSAGE_MAX_CHARS) {
52753
52949
  }
52754
52950
 
52755
52951
  // render/rich-render.ts
52952
+ var PLAIN_TEXT_MAX_CHARS = 4096;
52756
52953
  function parseRichRenderEnabled(raw) {
52757
52954
  if (raw == null)
52758
52955
  return false;
@@ -52765,10 +52962,27 @@ function richRenderEnabled(env = process.env) {
52765
52962
  function renderOutbound(text4, maxLen = RICH_MESSAGE_MAX_CHARS) {
52766
52963
  return renderSafe(parse2(text4), text4, maxLen);
52767
52964
  }
52768
- function maybeRenderOutbound(text4, env = process.env, maxLen = RICH_MESSAGE_MAX_CHARS) {
52769
- if (!richRenderEnabled(env))
52770
- return { text: text4, mode: "markdown", degradations: [] };
52771
- return renderOutbound(text4, maxLen);
52965
+ function renderOutboundChunks(text4, env = process.env, maxLen = RICH_MESSAGE_MAX_CHARS, plainMax = PLAIN_TEXT_MAX_CHARS) {
52966
+ if (!richRenderEnabled(env)) {
52967
+ return [{ text: text4, mode: "markdown", degradations: [] }];
52968
+ }
52969
+ const whole = renderOutbound(text4, maxLen);
52970
+ if (whole.mode === "markdown" && whole.text.length <= maxLen)
52971
+ return [whole];
52972
+ if (whole.mode === "plain" && whole.text.length <= plainMax)
52973
+ return [whole];
52974
+ const out = [];
52975
+ for (const rawPiece of splitMarkdownChunks(text4, maxLen)) {
52976
+ const rendered = renderOutbound(rawPiece, maxLen);
52977
+ if (rendered.mode === "markdown" && rendered.text.length <= maxLen) {
52978
+ out.push(rendered);
52979
+ continue;
52980
+ }
52981
+ for (const plainPiece of splitMarkdownChunks(rendered.text, plainMax)) {
52982
+ out.push({ text: plainPiece, mode: "plain", degradations: rendered.degradations });
52983
+ }
52984
+ }
52985
+ return out.length > 0 ? out : [whole];
52772
52986
  }
52773
52987
 
52774
52988
  // stream-controller.ts
@@ -52809,50 +53023,103 @@ function createStreamController(cfg) {
52809
53023
  ...protectContent === true ? { protect_content: true } : {},
52810
53024
  ...cfg.disableNotification === true ? { disable_notification: true } : {}
52811
53025
  };
52812
- const doSend = (text4, opts) => {
53026
+ const renderPieces = (text4) => {
52813
53027
  if (literalText)
52814
- return bot.api.sendMessage(chatId, text4, opts);
52815
- const rendered = maybeRenderOutbound(text4);
52816
- if (rendered.mode === "plain")
52817
- return bot.api.sendMessage(chatId, rendered.text, opts);
53028
+ return [{ text: text4, rich: false }];
53029
+ return renderOutboundChunks(text4).map((r) => ({ text: r.text, rich: r.mode !== "plain" }));
53030
+ };
53031
+ const sendPiece = (piece, opts) => {
53032
+ if (!piece.rich)
53033
+ return bot.api.sendMessage(chatId, piece.text, opts);
52818
53034
  const richOpts = { ...opts };
52819
53035
  delete richOpts.link_preview_options;
52820
- return bot.api.sendRichMessage(chatId, richMessage(rendered.text), richOpts);
52821
- };
52822
- const doEdit = (id, text4, opts) => {
52823
- if (literalText)
52824
- return bot.api.editMessageText(chatId, id, text4, opts);
52825
- const rendered = maybeRenderOutbound(text4);
52826
- if (rendered.mode === "plain")
52827
- return bot.api.editMessageText(chatId, id, rendered.text, opts);
52828
- return bot.api.editMessageText(chatId, id, richMessage(rendered.text), opts);
53036
+ return bot.api.sendRichMessage(chatId, richMessage(piece.text), richOpts);
53037
+ };
53038
+ const editPiece = (id, piece, opts) => {
53039
+ if (!piece.rich)
53040
+ return bot.api.editMessageText(chatId, id, piece.text, opts);
53041
+ return bot.api.editMessageText(chatId, id, richMessage(piece.text), opts);
53042
+ };
53043
+ const tailIds = [];
53044
+ const tailLastText = [];
53045
+ const upsertTail = async (ti, piece) => {
53046
+ const existingId = tailIds[ti];
53047
+ if (existingId != null) {
53048
+ if (tailLastText[ti] === piece.text)
53049
+ return;
53050
+ try {
53051
+ await retry(() => editPiece(existingId, piece, baseOpts), { threadId, chat_id: chatId });
53052
+ tailLastText[ti] = piece.text;
53053
+ onEdit?.(existingId, piece.text.length);
53054
+ } catch (err) {
53055
+ if (!literalText && piece.rich && isParseEntitiesError(err)) {
53056
+ warn?.(`stream-controller: tail-piece #${ti + 1} edit parse-entities rejected \u2014 retrying same id=${existingId} as plain text (${err instanceof Error ? err.message : String(err)})`);
53057
+ await retry(() => bot.api.editMessageText(chatId, existingId, piece.text, baseOpts), { threadId, chat_id: chatId });
53058
+ tailLastText[ti] = piece.text;
53059
+ onEdit?.(existingId, piece.text.length);
53060
+ } else {
53061
+ warn?.(`stream-controller: tail-piece #${ti + 1} edit FAILED (id=${existingId}) \u2014 partial delivery, this piece may be stale (${err instanceof Error ? err.message : String(err)})`);
53062
+ }
53063
+ }
53064
+ return;
53065
+ }
53066
+ try {
53067
+ const sent = await retry(() => sendPiece(piece, sendOpts), { threadId, chat_id: chatId });
53068
+ tailIds[ti] = sent.message_id;
53069
+ tailLastText[ti] = piece.text;
53070
+ onSend?.(sent.message_id, piece.text.length);
53071
+ } catch (err) {
53072
+ if (!literalText && piece.rich && isParseEntitiesError(err)) {
53073
+ warn?.(`stream-controller: tail-piece #${ti + 1} send parse-entities rejected \u2014 sending as plain text (${err instanceof Error ? err.message : String(err)})`);
53074
+ const sent = await retry(() => bot.api.sendMessage(chatId, piece.text, sendOpts), { threadId, chat_id: chatId });
53075
+ tailIds[ti] = sent.message_id;
53076
+ tailLastText[ti] = piece.text;
53077
+ onSend?.(sent.message_id, piece.text.length);
53078
+ } else {
53079
+ warn?.(`stream-controller: tail-piece #${ti + 1} send FAILED \u2014 partial delivery, this and later pieces may be missing this flush (${err instanceof Error ? err.message : String(err)})`);
53080
+ }
53081
+ }
52829
53082
  };
52830
53083
  return createDraftStream(async (text4) => {
53084
+ const pieces = renderPieces(text4);
53085
+ const head = pieces[0];
53086
+ let anchorId;
52831
53087
  try {
52832
- const sent = await retry(() => doSend(text4, sendOpts), { threadId, chat_id: chatId });
52833
- onSend?.(sent.message_id, text4.length);
52834
- return sent.message_id;
53088
+ const sent = await retry(() => sendPiece(head, sendOpts), { threadId, chat_id: chatId });
53089
+ anchorId = sent.message_id;
52835
53090
  } catch (err) {
52836
- if (!literalText && isParseEntitiesError(err)) {
53091
+ if (!literalText && head.rich && isParseEntitiesError(err)) {
52837
53092
  warn?.(`stream-controller: send parse-entities rejected \u2014 retrying once as plain text (${err instanceof Error ? err.message : String(err)})`);
52838
- const sent = await retry(() => bot.api.sendMessage(chatId, text4, sendOpts), { threadId, chat_id: chatId });
52839
- onSend?.(sent.message_id, text4.length);
52840
- return sent.message_id;
53093
+ const fallbackBody = pieces.length === 1 ? text4 : head.text;
53094
+ const sent = await retry(() => bot.api.sendMessage(chatId, fallbackBody, sendOpts), { threadId, chat_id: chatId });
53095
+ anchorId = sent.message_id;
53096
+ } else {
53097
+ throw err;
52841
53098
  }
52842
- throw err;
52843
53099
  }
53100
+ onSend?.(anchorId, head.text.length);
53101
+ for (let pi = 1;pi < pieces.length; pi++) {
53102
+ await upsertTail(pi - 1, pieces[pi]);
53103
+ }
53104
+ return anchorId;
52844
53105
  }, async (id, text4) => {
53106
+ const pieces = renderPieces(text4);
53107
+ const head = pieces[0];
52845
53108
  try {
52846
- await retry(() => doEdit(id, text4, baseOpts), { threadId, chat_id: chatId });
52847
- onEdit?.(id, text4.length);
53109
+ await retry(() => editPiece(id, head, baseOpts), { threadId, chat_id: chatId });
53110
+ onEdit?.(id, head.text.length);
52848
53111
  } catch (err) {
52849
- if (!literalText && isParseEntitiesError(err)) {
53112
+ if (!literalText && head.rich && isParseEntitiesError(err)) {
52850
53113
  warn?.(`stream-controller: edit parse-entities rejected \u2014 retrying same id=${id} as plain text (${err instanceof Error ? err.message : String(err)})`);
52851
- await retry(() => bot.api.editMessageText(chatId, id, text4, baseOpts), { threadId, chat_id: chatId });
52852
- onEdit?.(id, text4.length);
52853
- return;
53114
+ const fallbackBody = pieces.length === 1 ? text4 : head.text;
53115
+ await retry(() => bot.api.editMessageText(chatId, id, fallbackBody, baseOpts), { threadId, chat_id: chatId });
53116
+ onEdit?.(id, head.text.length);
53117
+ } else {
53118
+ throw err;
52854
53119
  }
52855
- throw err;
53120
+ }
53121
+ for (let pi = 1;pi < pieces.length; pi++) {
53122
+ await upsertTail(pi - 1, pieces[pi]);
52856
53123
  }
52857
53124
  }, {
52858
53125
  ...throttleMs != null ? { throttleMs } : {},
@@ -52866,6 +53133,12 @@ function createStreamController(cfg) {
52866
53133
  }
52867
53134
 
52868
53135
  // pty-partial-handler.ts
53136
+ function looksLikeRawApiError(text4) {
53137
+ if (typeof text4 !== "string" || text4.length === 0)
53138
+ return false;
53139
+ const lower = text4.toLowerCase();
53140
+ return lower.includes("api error:") || lower.includes('"type":"error"') || lower.includes("'type': 'error'") || lower.includes("rate_limit_error") || lower.includes("overloaded_error") || lower.includes('"is_error":true') || / b'\{/.test(text4);
53141
+ }
52869
53142
  function streamKey(chatId, threadId) {
52870
53143
  const t = threadId == null || threadId === 0 ? "_" : String(threadId);
52871
53144
  return `${chatId}:${t}`;
@@ -52898,6 +53171,8 @@ function handlePtyPartialPure(text4, state, deps) {
52898
53171
  });
52899
53172
  if (suppressed)
52900
53173
  return "suppressed";
53174
+ if (looksLikeRawApiError(text4))
53175
+ return "error-suppressed";
52901
53176
  if (state.lastPtyPreviewByChat.get(sKey) === text4)
52902
53177
  return "dedup-skip";
52903
53178
  const isFirst = !state.lastPtyPreviewByChat.has(sKey);
@@ -52977,12 +53252,22 @@ function createChatLock() {
52977
53252
 
52978
53253
  // retry-api-call.ts
52979
53254
  var import_grammy5 = __toESM(require_mod2(), 1);
53255
+ function isLocalResourceError(err) {
53256
+ const code2 = err?.code;
53257
+ if (typeof code2 === "string" && ["ENOSPC", "EDQUOT", "EIO", "ENOMEM"].includes(code2)) {
53258
+ return true;
53259
+ }
53260
+ const msg = err instanceof Error ? err.message : String(err ?? "");
53261
+ return /\b(ENOSPC|EDQUOT|EIO|ENOMEM)\b/.test(msg) || /no space left on device/i.test(msg) || /disk quota exceeded/i.test(msg);
53262
+ }
53263
+ var LOCAL_RESOURCE_EXHAUSTED = "LOCAL_RESOURCE_EXHAUSTED";
52980
53264
  var DEFAULT_SLEEP = (ms) => new Promise((r) => setTimeout(r, ms));
52981
53265
  function createRetryApiCall2(config = {}) {
52982
53266
  const maxRetries = config.maxRetries ?? 3;
52983
53267
  const sleep = config.sleep ?? DEFAULT_SLEEP;
52984
53268
  const observer = config.observer;
52985
53269
  const log = config.log;
53270
+ const onFloodWait = config.onFloodWait;
52986
53271
  return async function retryApiCall(fn, opts) {
52987
53272
  for (let attempt = 0;attempt < maxRetries; attempt++) {
52988
53273
  try {
@@ -52991,9 +53276,18 @@ function createRetryApiCall2(config = {}) {
52991
53276
  const isGrammyErr = err instanceof import_grammy5.GrammyError;
52992
53277
  const msg = err instanceof Error ? err.message : String(err);
52993
53278
  const desc = isGrammyErr ? err.description : msg;
53279
+ if (isLocalResourceError(err)) {
53280
+ log?.(`telegram gateway: LOCAL resource exhaustion (${err.code ?? "disk/mem"}) \u2014 ` + `not retrying the send (would feed a flood ban); surfacing degraded state
53281
+ `);
53282
+ observer?.onGiveUp?.({ attempts: attempt + 1, error: err });
53283
+ throw Object.assign(new Error(LOCAL_RESOURCE_EXHAUSTED), { original: err });
53284
+ }
52994
53285
  if (isGrammyErr && err.error_code === 429) {
52995
53286
  const retryAfter = Number(err.parameters?.retry_after ?? 5);
52996
53287
  const delayMs = retryAfter * 1000;
53288
+ try {
53289
+ onFloodWait?.(retryAfter);
53290
+ } catch {}
52997
53291
  log?.(`telegram gateway: 429 rate limited, waiting ${retryAfter}s
52998
53292
  `);
52999
53293
  observer?.onRetry?.({ attempt, reason: "flood_wait", delayMs });
@@ -53082,6 +53376,7 @@ var import_grammy6 = __toESM(require_mod2(), 1);
53082
53376
  var import_runner2 = __toESM(require_mod4(), 1);
53083
53377
  import { createHash } from "crypto";
53084
53378
  import { AsyncLocalStorage as AsyncLocalStorage2 } from "async_hooks";
53379
+ init_flood_circuit_breaker();
53085
53380
  var tgPostTagStore2 = new AsyncLocalStorage2;
53086
53381
  function _getTgPostTags() {
53087
53382
  return tgPostTagStore2.getStore();
@@ -53125,8 +53420,50 @@ function installTgPostLogger(bot) {
53125
53420
  });
53126
53421
  }
53127
53422
 
53423
+ // flood-circuit-breaker.ts
53424
+ import { existsSync as existsSync4, readFileSync as readFileSync8, writeFileSync as writeFileSync8, mkdirSync as mkdirSync6 } from "node:fs";
53425
+ import { dirname as dirname3, join as join8 } from "node:path";
53426
+ var FLOOD_STATE_FILE = "flood-wait.json";
53427
+ function floodStatePath(stateDir) {
53428
+ return join8(stateDir, FLOOD_STATE_FILE);
53429
+ }
53430
+ function computeFloodWait(prior, retryAfterSec, now) {
53431
+ const candidate = now + Math.max(0, retryAfterSec) * 1000;
53432
+ const untilTs = prior && prior.untilTs > candidate ? prior.untilTs : candidate;
53433
+ return { untilTs, retryAfterSec, recordedTs: now };
53434
+ }
53435
+ function readFloodState2(path2) {
53436
+ try {
53437
+ if (!existsSync4(path2))
53438
+ return null;
53439
+ const raw = JSON.parse(readFileSync8(path2, "utf-8"));
53440
+ if (typeof raw.untilTs !== "number")
53441
+ return null;
53442
+ return {
53443
+ untilTs: raw.untilTs,
53444
+ retryAfterSec: typeof raw.retryAfterSec === "number" ? raw.retryAfterSec : 0,
53445
+ recordedTs: typeof raw.recordedTs === "number" ? raw.recordedTs : 0
53446
+ };
53447
+ } catch {
53448
+ return null;
53449
+ }
53450
+ }
53451
+ function writeFloodState(path2, state) {
53452
+ try {
53453
+ mkdirSync6(dirname3(path2), { recursive: true });
53454
+ writeFileSync8(path2, JSON.stringify(state), { mode: 384 });
53455
+ } catch {}
53456
+ }
53457
+ function makeFloodWaitRecorder2(path2, now = Date.now) {
53458
+ return (retryAfterSec) => {
53459
+ const t = now();
53460
+ const next = computeFloodWait(readFloodState2(path2), retryAfterSec, t);
53461
+ writeFloodState(path2, next);
53462
+ };
53463
+ }
53464
+
53128
53465
  // attachment-path.ts
53129
- import { join as join8, basename as basename3, resolve, sep } from "node:path";
53466
+ import { join as join9, basename as basename3, resolve, sep } from "node:path";
53130
53467
  function sanitizeExtension(ext) {
53131
53468
  if (ext == null)
53132
53469
  return "bin";
@@ -53149,7 +53486,7 @@ function buildAttachmentPath(input) {
53149
53486
  const ext = extractExtension(input.telegramFilePath);
53150
53487
  const uid = sanitizeUniqueId(input.fileUniqueId);
53151
53488
  const filename = `${input.now}-${uid}.${ext}`;
53152
- return join8(input.inboxDir, filename);
53489
+ return join9(input.inboxDir, filename);
53153
53490
  }
53154
53491
  function assertInsideInbox(inboxDir, candidatePath) {
53155
53492
  const inboxReal = resolve(inboxDir);
@@ -53236,7 +53573,7 @@ function clear(key) {
53236
53573
  }
53237
53574
 
53238
53575
  // ../node_modules/.bun/posthog-node@5.29.2/node_modules/posthog-node/dist/extensions/error-tracking/modifiers/module.node.mjs
53239
- import { dirname as dirname3, posix, sep as sep2 } from "path";
53576
+ import { dirname as dirname4, posix, sep as sep2 } from "path";
53240
53577
  function createModulerModifier() {
53241
53578
  const getModuleFromFileName = createGetModuleFromFilename();
53242
53579
  return async (frames) => {
@@ -53245,7 +53582,7 @@ function createModulerModifier() {
53245
53582
  return frames;
53246
53583
  };
53247
53584
  }
53248
- function createGetModuleFromFilename(basePath = process.argv[1] ? dirname3(process.argv[1]) : process.cwd(), isWindows = sep2 === "\\") {
53585
+ function createGetModuleFromFilename(basePath = process.argv[1] ? dirname4(process.argv[1]) : process.cwd(), isWindows = sep2 === "\\") {
53249
53586
  const normalizedBase = isWindows ? normalizeWindowsPath(basePath) : basePath;
53250
53587
  return (filename) => {
53251
53588
  if (!filename)
@@ -57676,8 +58013,8 @@ class PostHog extends PostHogBackendClient {
57676
58013
  }
57677
58014
 
57678
58015
  // analytics-posthog.ts
57679
- import { existsSync as existsSync3, mkdirSync as mkdirSync5, readFileSync as readFileSync7, writeFileSync as writeFileSync7 } from "node:fs";
57680
- import { dirname as dirname4, join as join9 } from "node:path";
58016
+ import { existsSync as existsSync5, mkdirSync as mkdirSync7, readFileSync as readFileSync9, writeFileSync as writeFileSync9 } from "node:fs";
58017
+ import { dirname as dirname5, join as join10 } from "node:path";
57681
58018
  import { randomUUID } from "node:crypto";
57682
58019
  var DEFAULT_KEY = "phc_qKY87cKWZm6ZyCtk7LcRd2cU8Sg42u7Ywhui5stYCegd";
57683
58020
  var DEFAULT_HOST = "https://us.i.posthog.com";
@@ -57703,10 +58040,10 @@ function getDistinctId() {
57703
58040
  cachedDistinctId = envId.trim();
57704
58041
  return cachedDistinctId;
57705
58042
  }
57706
- const fallbackPath = join9(process.env.SWITCHROOM_RUNTIME_STATE_DIR ?? "/state/agent", "analytics-id");
58043
+ const fallbackPath = join10(process.env.SWITCHROOM_RUNTIME_STATE_DIR ?? "/state/agent", "analytics-id");
57707
58044
  try {
57708
- if (existsSync3(fallbackPath)) {
57709
- const existing = readFileSync7(fallbackPath, "utf-8").trim();
58045
+ if (existsSync5(fallbackPath)) {
58046
+ const existing = readFileSync9(fallbackPath, "utf-8").trim();
57710
58047
  if (existing) {
57711
58048
  cachedDistinctId = existing;
57712
58049
  return existing;
@@ -57716,8 +58053,8 @@ function getDistinctId() {
57716
58053
  const id = randomUUID();
57717
58054
  cachedDistinctId = id;
57718
58055
  try {
57719
- mkdirSync5(dirname4(fallbackPath), { recursive: true });
57720
- writeFileSync7(fallbackPath, id, "utf-8");
58056
+ mkdirSync7(dirname5(fallbackPath), { recursive: true });
58057
+ writeFileSync9(fallbackPath, id, "utf-8");
57721
58058
  } catch {}
57722
58059
  return id;
57723
58060
  }
@@ -57788,12 +58125,12 @@ function installGlobalErrorHandlers() {
57788
58125
  }
57789
58126
 
57790
58127
  // runtime-metrics.ts
57791
- import { mkdirSync as mkdirSync7, appendFileSync as appendFileSync3 } from "node:fs";
57792
- import { dirname as dirname6, join as join11 } from "node:path";
58128
+ import { mkdirSync as mkdirSync9, appendFileSync as appendFileSync3 } from "node:fs";
58129
+ import { dirname as dirname7, join as join12 } from "node:path";
57793
58130
 
57794
58131
  // analytics-posthog.ts
57795
- import { existsSync as existsSync4, mkdirSync as mkdirSync6, readFileSync as readFileSync8, writeFileSync as writeFileSync8 } from "node:fs";
57796
- import { dirname as dirname5, join as join10 } from "node:path";
58132
+ import { existsSync as existsSync6, mkdirSync as mkdirSync8, readFileSync as readFileSync10, writeFileSync as writeFileSync10 } from "node:fs";
58133
+ import { dirname as dirname6, join as join11 } from "node:path";
57797
58134
  import { randomUUID as randomUUID2 } from "node:crypto";
57798
58135
  var DEFAULT_KEY2 = "phc_qKY87cKWZm6ZyCtk7LcRd2cU8Sg42u7Ywhui5stYCegd";
57799
58136
  var DEFAULT_HOST2 = "https://us.i.posthog.com";
@@ -57818,10 +58155,10 @@ function getDistinctId2() {
57818
58155
  cachedDistinctId2 = envId.trim();
57819
58156
  return cachedDistinctId2;
57820
58157
  }
57821
- const fallbackPath = join10(process.env.SWITCHROOM_RUNTIME_STATE_DIR ?? "/state/agent", "analytics-id");
58158
+ const fallbackPath = join11(process.env.SWITCHROOM_RUNTIME_STATE_DIR ?? "/state/agent", "analytics-id");
57822
58159
  try {
57823
- if (existsSync4(fallbackPath)) {
57824
- const existing = readFileSync8(fallbackPath, "utf-8").trim();
58160
+ if (existsSync6(fallbackPath)) {
58161
+ const existing = readFileSync10(fallbackPath, "utf-8").trim();
57825
58162
  if (existing) {
57826
58163
  cachedDistinctId2 = existing;
57827
58164
  return existing;
@@ -57831,8 +58168,8 @@ function getDistinctId2() {
57831
58168
  const id = randomUUID2();
57832
58169
  cachedDistinctId2 = id;
57833
58170
  try {
57834
- mkdirSync6(dirname5(fallbackPath), { recursive: true });
57835
- writeFileSync8(fallbackPath, id, "utf-8");
58171
+ mkdirSync8(dirname6(fallbackPath), { recursive: true });
58172
+ writeFileSync10(fallbackPath, id, "utf-8");
57836
58173
  } catch {}
57837
58174
  return id;
57838
58175
  }
@@ -57881,12 +58218,12 @@ function resolveJsonlPath() {
57881
58218
  if (override && override.trim() !== "")
57882
58219
  return override.trim();
57883
58220
  const base = process.env.SWITCHROOM_RUNTIME_STATE_DIR ?? "/state/agent";
57884
- return join11(base, "runtime-metrics.jsonl");
58221
+ return join12(base, "runtime-metrics.jsonl");
57885
58222
  }
57886
58223
  function appendJsonl(line) {
57887
58224
  const path2 = resolveJsonlPath();
57888
58225
  try {
57889
- mkdirSync7(dirname6(path2), { recursive: true });
58226
+ mkdirSync9(dirname7(path2), { recursive: true });
57890
58227
  appendFileSync3(path2, line + `
57891
58228
  `, "utf-8");
57892
58229
  } catch (err) {
@@ -58101,6 +58438,13 @@ function formatFrameworkFallbackText(fallbackKind, silenceMs, inFlightTools = []
58101
58438
  }
58102
58439
  return null;
58103
58440
  }
58441
+ function longestInFlightTool(key, now) {
58442
+ const s = state2.get(key);
58443
+ if (s == null)
58444
+ return null;
58445
+ const snaps = snapshotInFlight(s, now);
58446
+ return snaps.length > 0 ? snaps[0] : null;
58447
+ }
58104
58448
  function snapshotInFlight(s, now) {
58105
58449
  return Array.from(s.inFlightTools.values()).sort((a, b) => a.startedAt - b.startedAt).map((t) => ({ name: t.name, label: t.label, durationMs: now - t.startedAt }));
58106
58450
  }
@@ -58401,8 +58745,8 @@ _still working (${minutes}m) \u00b7 message me anytime, I'll keep you posted_`;
58401
58745
  }
58402
58746
 
58403
58747
  // silent-end.ts
58404
- import { existsSync as existsSync6, readFileSync as readFileSync9, writeFileSync as writeFileSync9, unlinkSync as unlinkSync6, mkdirSync as mkdirSync8 } from "node:fs";
58405
- import { dirname as dirname7, join as join12 } from "node:path";
58748
+ import { existsSync as existsSync8, readFileSync as readFileSync11, writeFileSync as writeFileSync11, unlinkSync as unlinkSync6, mkdirSync as mkdirSync10 } from "node:fs";
58749
+ import { dirname as dirname8, join as join13 } from "node:path";
58406
58750
  import { homedir as homedir2 } from "node:os";
58407
58751
  var SILENT_END_MAX_RETRIES = 2;
58408
58752
  var SILENT_END_STALE_RECORD_MAX_AGE_MS = 30 * 60000;
@@ -58417,10 +58761,10 @@ function resolveStateDir(deps) {
58417
58761
  if (env != null && env !== "")
58418
58762
  return env;
58419
58763
  const home = process.env.HOME ?? homedir2();
58420
- return join12(home, ".claude", "channels", "telegram");
58764
+ return join13(home, ".claude", "channels", "telegram");
58421
58765
  }
58422
58766
  function resolveStatePath(deps) {
58423
- return join12(resolveStateDir(deps), "silent-end-pending.json");
58767
+ return join13(resolveStateDir(deps), "silent-end-pending.json");
58424
58768
  }
58425
58769
  function emitLog(deps, line) {
58426
58770
  if (deps?.log != null)
@@ -58432,8 +58776,8 @@ function writeSilentEndState(args, deps) {
58432
58776
  const statePath = resolveStatePath(deps);
58433
58777
  let retryCount = 0;
58434
58778
  try {
58435
- if (existsSync6(statePath)) {
58436
- const prev = JSON.parse(readFileSync9(statePath, "utf8"));
58779
+ if (existsSync8(statePath)) {
58780
+ const prev = JSON.parse(readFileSync11(statePath, "utf8"));
58437
58781
  if (prev.turnKey === args.turnKey && typeof prev.retryCount === "number") {
58438
58782
  retryCount = prev.retryCount;
58439
58783
  }
@@ -58449,8 +58793,8 @@ function writeSilentEndState(args, deps) {
58449
58793
  timestamp: Date.now()
58450
58794
  };
58451
58795
  try {
58452
- mkdirSync8(dirname7(statePath), { recursive: true });
58453
- writeFileSync9(statePath, JSON.stringify(state3), "utf8");
58796
+ mkdirSync10(dirname8(statePath), { recursive: true });
58797
+ writeFileSync11(statePath, JSON.stringify(state3), "utf8");
58454
58798
  emitLog(deps, `silent-end: wrote state file turnKey=${args.turnKey} retryCount=${retryCount}
58455
58799
  `);
58456
58800
  } catch (err) {
@@ -58460,10 +58804,10 @@ function writeSilentEndState(args, deps) {
58460
58804
  }
58461
58805
  function clearSilentEndState(turnKey, deps) {
58462
58806
  const statePath = resolveStatePath(deps);
58463
- if (!existsSync6(statePath))
58807
+ if (!existsSync8(statePath))
58464
58808
  return;
58465
58809
  try {
58466
- const prev = JSON.parse(readFileSync9(statePath, "utf8"));
58810
+ const prev = JSON.parse(readFileSync11(statePath, "utf8"));
58467
58811
  if (prev.turnKey != null && prev.turnKey !== turnKey)
58468
58812
  return;
58469
58813
  unlinkSync6(statePath);
@@ -58473,10 +58817,10 @@ function clearSilentEndState(turnKey, deps) {
58473
58817
  }
58474
58818
  function readSilentEndState(deps) {
58475
58819
  const statePath = resolveStatePath(deps);
58476
- if (!existsSync6(statePath))
58820
+ if (!existsSync8(statePath))
58477
58821
  return null;
58478
58822
  try {
58479
- return JSON.parse(readFileSync9(statePath, "utf8"));
58823
+ return JSON.parse(readFileSync11(statePath, "utf8"));
58480
58824
  } catch {
58481
58825
  return null;
58482
58826
  }
@@ -58937,18 +59281,18 @@ async function gatewayStartupRetry(fn, opts = {}) {
58937
59281
  }
58938
59282
 
58939
59283
  // gateway/quarantine.ts
58940
- import { mkdirSync as mkdirSync9, writeFileSync as writeFileSync10 } from "node:fs";
58941
- import { join as join13 } from "node:path";
59284
+ import { mkdirSync as mkdirSync11, writeFileSync as writeFileSync12 } from "node:fs";
59285
+ import { join as join14 } from "node:path";
58942
59286
  var QUARANTINE_FILENAME = "quarantine.json";
58943
59287
  function writeQuarantineMarker(telegramStateDir, reason, detail, nowFn = Date.now) {
58944
- mkdirSync9(telegramStateDir, { recursive: true, mode: 448 });
59288
+ mkdirSync11(telegramStateDir, { recursive: true, mode: 448 });
58945
59289
  const marker = {
58946
59290
  v: 1,
58947
59291
  reason,
58948
59292
  ts: nowFn(),
58949
59293
  detail
58950
59294
  };
58951
- writeFileSync10(join13(telegramStateDir, QUARANTINE_FILENAME), JSON.stringify(marker) + `
59295
+ writeFileSync12(join14(telegramStateDir, QUARANTINE_FILENAME), JSON.stringify(marker) + `
58952
59296
  `, "utf-8");
58953
59297
  }
58954
59298
 
@@ -59928,7 +60272,7 @@ init_protocol();
59928
60272
  import * as net3 from "node:net";
59929
60273
  import { homedir as homedir6 } from "node:os";
59930
60274
  import { randomUUID as randomUUID4 } from "node:crypto";
59931
- import { join as join17 } from "node:path";
60275
+ import { join as join18 } from "node:path";
59932
60276
  var DEFAULT_TIMEOUT_MS3 = 5000;
59933
60277
  function reviveDate2(v) {
59934
60278
  if (v == null)
@@ -59939,7 +60283,7 @@ function reviveDate2(v) {
59939
60283
  return Number.isNaN(d.getTime()) ? null : d;
59940
60284
  }
59941
60285
  function operatorSocketPath2(home2 = homedir6()) {
59942
- return join17(home2, ".switchroom", "state", "auth-broker-operator", "sock");
60286
+ return join18(home2, ".switchroom", "state", "auth-broker-operator", "sock");
59943
60287
  }
59944
60288
  function resolveAuthBrokerSocketPath2(opts) {
59945
60289
  if (opts?.socket)
@@ -60244,13 +60588,13 @@ class AuthBrokerClient2 {
60244
60588
  init_loader();
60245
60589
  init_resolver();
60246
60590
  init_vault();
60247
- import { existsSync as existsSync12 } from "node:fs";
60591
+ import { existsSync as existsSync14 } from "node:fs";
60248
60592
  var DEFAULT_VOICE_API_KEY_REF = "vault:openai/api-key";
60249
60593
  function tryDirectVaultRead(ref, config, passphrase) {
60250
60594
  if (!passphrase)
60251
60595
  return null;
60252
60596
  const vaultPath = resolvePath(config.vault?.path ?? "~/.switchroom/vault.enc");
60253
- if (!existsSync12(vaultPath))
60597
+ if (!existsSync14(vaultPath))
60254
60598
  return null;
60255
60599
  try {
60256
60600
  const secrets = openVault(passphrase, vaultPath);
@@ -60306,14 +60650,14 @@ async function materializeVoiceKey(opts = {}, logger2 = (line) => process.stderr
60306
60650
  init_loader();
60307
60651
  init_resolver();
60308
60652
  init_vault();
60309
- import { existsSync as existsSync13 } from "node:fs";
60653
+ import { existsSync as existsSync15 } from "node:fs";
60310
60654
  var VOICE_SIDECAR_TOKEN_KEY = "voice/sidecar-token";
60311
60655
  var DEFAULT_VOICE_SIDECAR_TOKEN_REF = `vault:${VOICE_SIDECAR_TOKEN_KEY}`;
60312
60656
  function tryDirectVaultRead2(ref, config, passphrase) {
60313
60657
  if (!passphrase)
60314
60658
  return null;
60315
60659
  const vaultPath = resolvePath(config.vault?.path ?? "~/.switchroom/vault.enc");
60316
- if (!existsSync13(vaultPath))
60660
+ if (!existsSync15(vaultPath))
60317
60661
  return null;
60318
60662
  try {
60319
60663
  const secrets = openVault(passphrase, vaultPath);
@@ -60366,17 +60710,17 @@ async function materializeSidecarToken(opts = {}, logger2 = (line) => process.st
60366
60710
  }
60367
60711
 
60368
60712
  // ../src/setup/host-capabilities.ts
60369
- import { existsSync as existsSync14, mkdirSync as mkdirSync12, readFileSync as readFileSync14, writeFileSync as writeFileSync11 } from "node:fs";
60713
+ import { existsSync as existsSync16, mkdirSync as mkdirSync14, readFileSync as readFileSync16, writeFileSync as writeFileSync13 } from "node:fs";
60370
60714
  init_paths();
60371
60715
  function hostCapabilitiesPath() {
60372
60716
  return resolveStatePath2("host-capabilities.json");
60373
60717
  }
60374
60718
  function loadHostCapabilities() {
60375
60719
  const path2 = hostCapabilitiesPath();
60376
- if (!existsSync14(path2))
60720
+ if (!existsSync16(path2))
60377
60721
  return null;
60378
60722
  try {
60379
- const parsed = JSON.parse(readFileSync14(path2, "utf-8"));
60723
+ const parsed = JSON.parse(readFileSync16(path2, "utf-8"));
60380
60724
  if (parsed && typeof parsed === "object" && "voice" in parsed && typeof parsed.voice === "object") {
60381
60725
  return parsed;
60382
60726
  }
@@ -60474,18 +60818,18 @@ function resolveExhaustUntil(resetAtMs, now = Date.now()) {
60474
60818
 
60475
60819
  // gateway/auth-add-flow.ts
60476
60820
  import { execFileSync } from "node:child_process";
60477
- import { existsSync as existsSync16, mkdirSync as mkdirSync14, readFileSync as readFileSync16, readdirSync as readdirSync4, rmSync as rmSync3, statSync as statSync7, writeFileSync as writeFileSync13 } from "node:fs";
60821
+ import { existsSync as existsSync18, mkdirSync as mkdirSync16, readFileSync as readFileSync18, readdirSync as readdirSync4, rmSync as rmSync3, statSync as statSync7, writeFileSync as writeFileSync15 } from "node:fs";
60478
60822
  import { homedir as homedir7 } from "node:os";
60479
- import { join as join18 } from "node:path";
60823
+ import { join as join19 } from "node:path";
60480
60824
  import { randomBytes as randomBytes4 } from "node:crypto";
60481
60825
 
60482
60826
  // ../src/auth/manager.ts
60483
60827
  import {
60484
- readFileSync as readFileSync15,
60828
+ readFileSync as readFileSync17,
60485
60829
  readdirSync as readdirSync3,
60486
- existsSync as existsSync15,
60487
- writeFileSync as writeFileSync12,
60488
- mkdirSync as mkdirSync13,
60830
+ existsSync as existsSync17,
60831
+ writeFileSync as writeFileSync14,
60832
+ mkdirSync as mkdirSync15,
60489
60833
  mkdtempSync as mkdtempSync2,
60490
60834
  rmSync as rmSync2,
60491
60835
  chmodSync as chmodSync3,
@@ -60510,9 +60854,9 @@ function parseSetupTokenUrl(output) {
60510
60854
  }
60511
60855
  function readTokenFromCredentialsFile(credentialsFilePath) {
60512
60856
  try {
60513
- if (!existsSync15(credentialsFilePath))
60857
+ if (!existsSync17(credentialsFilePath))
60514
60858
  return null;
60515
- const raw = readFileSync15(credentialsFilePath, "utf-8");
60859
+ const raw = readFileSync17(credentialsFilePath, "utf-8");
60516
60860
  const parsed = JSON.parse(raw);
60517
60861
  const token = parsed?.claudeAiOauth?.accessToken;
60518
60862
  if (typeof token !== "string")
@@ -60572,7 +60916,7 @@ function makeAuthAddTmuxOps(tmuxBin = "tmux") {
60572
60916
  var pendingAuthAddFlows = new Map;
60573
60917
  function pickScratchDir(label, home2 = homedir7()) {
60574
60918
  const suffix = randomBytes4(8).toString("hex");
60575
- return join18(home2, ".switchroom", "accounts", ".in-progress", `${label}-${suffix}`);
60919
+ return join19(home2, ".switchroom", "accounts", ".in-progress", `${label}-${suffix}`);
60576
60920
  }
60577
60921
  function cleanScratchDir(scratchDir) {
60578
60922
  try {
@@ -60581,8 +60925,8 @@ function cleanScratchDir(scratchDir) {
60581
60925
  }
60582
60926
  var AUTH_TMUX_SESSION_FILE = ".auth-tmux-session";
60583
60927
  function sweepOrphanSessions(home2, tmux, nowMs2 = Date.now()) {
60584
- const inProgressDir = join18(home2, ".switchroom", "accounts", ".in-progress");
60585
- if (!existsSync16(inProgressDir))
60928
+ const inProgressDir = join19(home2, ".switchroom", "accounts", ".in-progress");
60929
+ if (!existsSync18(inProgressDir))
60586
60930
  return;
60587
60931
  let entries;
60588
60932
  try {
@@ -60592,16 +60936,16 @@ function sweepOrphanSessions(home2, tmux, nowMs2 = Date.now()) {
60592
60936
  }
60593
60937
  const tenMinMs = 10 * 60000;
60594
60938
  for (const entry of entries) {
60595
- const dir = join18(inProgressDir, entry);
60596
- const sessionFile = join18(dir, AUTH_TMUX_SESSION_FILE);
60597
- if (!existsSync16(sessionFile))
60939
+ const dir = join19(inProgressDir, entry);
60940
+ const sessionFile = join19(dir, AUTH_TMUX_SESSION_FILE);
60941
+ if (!existsSync18(sessionFile))
60598
60942
  continue;
60599
60943
  let fileContents;
60600
60944
  let fileMtime;
60601
60945
  try {
60602
60946
  const stat = statSync7(sessionFile);
60603
60947
  fileMtime = stat.mtimeMs;
60604
- fileContents = readFileSync16(sessionFile, "utf8").trim();
60948
+ fileContents = readFileSync18(sessionFile, "utf8").trim();
60605
60949
  } catch {
60606
60950
  continue;
60607
60951
  }
@@ -60627,13 +60971,13 @@ async function startAccountAuthSession(label, opts = {}) {
60627
60971
  const tmux = opts.tmuxOps ?? makeAuthAddTmuxOps(opts.tmuxBin);
60628
60972
  const binary = opts.claudeBinary ?? "claude";
60629
60973
  const scratchDir = pickScratchDir(label, home2);
60630
- mkdirSync14(scratchDir, { recursive: true, mode: 448 });
60974
+ mkdirSync16(scratchDir, { recursive: true, mode: 448 });
60631
60975
  sweepOrphanSessions(home2, tmux);
60632
60976
  const hexSuffix = scratchDir.slice(scratchDir.lastIndexOf("-") + 1);
60633
60977
  const tmuxSocket = `switchroom-${agentName3}`;
60634
60978
  const tmuxSession = `auth-add-${label}-${hexSuffix}`.slice(0, 64);
60635
60979
  try {
60636
- writeFileSync13(join18(scratchDir, AUTH_TMUX_SESSION_FILE), `${tmuxSocket}
60980
+ writeFileSync15(join19(scratchDir, AUTH_TMUX_SESSION_FILE), `${tmuxSocket}
60637
60981
  ${tmuxSession}`, "utf8");
60638
60982
  } catch {}
60639
60983
  const sessionEnv = {
@@ -60682,7 +61026,7 @@ async function submitAccountAuthCode(flow3, code2, opts = {}) {
60682
61026
  const pollIntervalMs = opts.pollIntervalMs ?? 250;
60683
61027
  const pollTimeoutMs = opts.pollTimeoutMs ?? 300000;
60684
61028
  const tmux = opts.tmuxOps ?? makeAuthAddTmuxOps();
60685
- const credentialsPath = join18(flow3.scratchDir, ".credentials.json");
61029
+ const credentialsPath = join19(flow3.scratchDir, ".credentials.json");
60686
61030
  try {
60687
61031
  tmux.send(flow3.tmuxSocket, flow3.tmuxSession, code2);
60688
61032
  } catch (err) {
@@ -60692,11 +61036,11 @@ async function submitAccountAuthCode(flow3, code2, opts = {}) {
60692
61036
  const deadline = Date.now() + pollTimeoutMs;
60693
61037
  while (Date.now() < deadline) {
60694
61038
  await new Promise((r) => setTimeout(r, pollIntervalMs));
60695
- if (existsSync16(credentialsPath)) {
61039
+ if (existsSync18(credentialsPath)) {
60696
61040
  const token = readTokenFromCredentialsFile(credentialsPath);
60697
61041
  if (token) {
60698
61042
  try {
60699
- const raw = readFileSync16(credentialsPath, "utf-8");
61043
+ const raw = readFileSync18(credentialsPath, "utf-8");
60700
61044
  const parsed = JSON.parse(raw);
60701
61045
  if (parsed.claudeAiOauth?.accessToken) {
60702
61046
  tmux.killSession(flow3.tmuxSocket, flow3.tmuxSession);
@@ -60706,7 +61050,7 @@ async function submitAccountAuthCode(flow3, code2, opts = {}) {
60706
61050
  }
60707
61051
  }
60708
61052
  if (!tmux.hasSession(flow3.tmuxSocket, flow3.tmuxSession)) {
60709
- if (!existsSync16(credentialsPath)) {
61053
+ if (!existsSync18(credentialsPath)) {
60710
61054
  cleanScratchDir(flow3.scratchDir);
60711
61055
  throw new Error("claude setup-token exited without writing credentials \u2014 the code may be invalid or expired");
60712
61056
  }
@@ -61060,6 +61404,20 @@ function detectModelUnavailable(stderr) {
61060
61404
  return null;
61061
61405
  const sample = stderr.length > 16384 ? stderr.slice(0, 16384) : stderr;
61062
61406
  const lower = sample.toLowerCase();
61407
+ const transientUpstreamSignals = [
61408
+ "not your usage limit",
61409
+ "not your account",
61410
+ "not your account's",
61411
+ "temporarily limiting requests",
61412
+ "temporarily rate",
61413
+ "server is temporarily",
61414
+ "would exceed your account\u2019s rate limit",
61415
+ "would exceed your account's rate limit"
61416
+ ];
61417
+ if (transientUpstreamSignals.some((s) => lower.includes(s))) {
61418
+ const resetAt = parseResetTime(sample);
61419
+ return resetAt !== undefined ? { kind: "overload", resetAt, raw: stderr } : { kind: "overload", raw: stderr };
61420
+ }
61063
61421
  const quotaSignals = [
61064
61422
  "out of extra usage",
61065
61423
  "extra usage",
@@ -61599,7 +61957,7 @@ function hardenCardBreaks2(text4) {
61599
61957
  }
61600
61958
  return restore2(pieces.join(""));
61601
61959
  }
61602
- var PARAGRAPH_SPACER = "\u00a0";
61960
+ var PARAGRAPH_SPACER2 = "\u00a0";
61603
61961
  function addParagraphSpacers(text4) {
61604
61962
  if (!text4.includes(`
61605
61963
 
@@ -61611,7 +61969,7 @@ function addParagraphSpacers(text4) {
61611
61969
 
61612
61970
  `))
61613
61971
  return restore2(masked);
61614
- const spacerLine = PARAGRAPH_SPACER;
61972
+ const spacerLine = PARAGRAPH_SPACER2;
61615
61973
  const isBlankLine = (line) => /^[ \t\r\f\v]*$/.test(line);
61616
61974
  const asciiTrim = (line) => line.replace(/^[ \t\r\f\v]+|[ \t\r\f\v]+$/g, "");
61617
61975
  const blockKind = (line) => {
@@ -61848,7 +62206,7 @@ function shouldPromoteBreak(prev, next, placeholder) {
61848
62206
  }
61849
62207
  return false;
61850
62208
  }
61851
- function hardSliceToCap(text4, cap = RICH_MESSAGE_MAX_CHARS2) {
62209
+ function hardSliceToCap2(text4, cap = RICH_MESSAGE_MAX_CHARS2) {
61852
62210
  if (cap <= 0)
61853
62211
  return [text4];
61854
62212
  if (text4.length <= cap)
@@ -61859,7 +62217,7 @@ function hardSliceToCap(text4, cap = RICH_MESSAGE_MAX_CHARS2) {
61859
62217
  }
61860
62218
  return out;
61861
62219
  }
61862
- function splitMarkdownChunks(text4, maxLen = RICH_MESSAGE_MAX_CHARS2) {
62220
+ function splitMarkdownChunks2(text4, maxLen = RICH_MESSAGE_MAX_CHARS2) {
61863
62221
  if (text4.length <= maxLen)
61864
62222
  return [text4];
61865
62223
  const chunks = [];
@@ -61883,27 +62241,27 @@ function splitMarkdownChunks(text4, maxLen = RICH_MESSAGE_MAX_CHARS2) {
61883
62241
  } else if (spaceIdx > 0) {
61884
62242
  cut = spaceIdx;
61885
62243
  }
61886
- cut = backOffOpenFence(rest, cut);
61887
- cut = backOffTableRow(rest, cut);
62244
+ cut = backOffOpenFence2(rest, cut);
62245
+ cut = backOffTableRow2(rest, cut);
61888
62246
  if (cut <= 0) {
61889
- const sliced = hardSliceToCap(rest, maxLen);
61890
- chunks.push(stripBoundarySpacers(sliced[0], "trailing"));
61891
- rest = stripBoundarySpacers(sliced.slice(1).join(""), "leading");
62247
+ const sliced = hardSliceToCap2(rest, maxLen);
62248
+ chunks.push(stripBoundarySpacers2(sliced[0], "trailing"));
62249
+ rest = stripBoundarySpacers2(sliced.slice(1).join(""), "leading");
61892
62250
  continue;
61893
62251
  }
61894
- chunks.push(stripBoundarySpacers(rest.slice(0, cut), "trailing"));
61895
- rest = stripBoundarySpacers(rest.slice(cut), "leading");
62252
+ chunks.push(stripBoundarySpacers2(rest.slice(0, cut), "trailing"));
62253
+ rest = stripBoundarySpacers2(rest.slice(cut), "leading");
61896
62254
  }
61897
- return chunks.map((c) => stripBoundarySpacers(c, "trailing"));
62255
+ return chunks.map((c) => stripBoundarySpacers2(c, "trailing"));
61898
62256
  }
61899
- function stripBoundarySpacers(chunk2, side) {
61900
- const sp = PARAGRAPH_SPACER;
62257
+ function stripBoundarySpacers2(chunk2, side) {
62258
+ const sp = PARAGRAPH_SPACER2;
61901
62259
  if (side === "leading") {
61902
62260
  return chunk2.replace(new RegExp(`^(?:[ \\t]*${sp}?[ \\t]*\\n+)+`), "");
61903
62261
  }
61904
62262
  return chunk2.replace(new RegExp(`(?:\\n+[ \\t]*${sp}?[ \\t]*)+$`), "");
61905
62263
  }
61906
- function backOffOpenFence(text4, cut) {
62264
+ function backOffOpenFence2(text4, cut) {
61907
62265
  if (cut <= 0 || cut >= text4.length)
61908
62266
  return cut;
61909
62267
  const before = text4.slice(0, cut);
@@ -61916,7 +62274,7 @@ function backOffOpenFence(text4, cut) {
61916
62274
  }
61917
62275
  return lastFence;
61918
62276
  }
61919
- function backOffTableRow(text4, cut) {
62277
+ function backOffTableRow2(text4, cut) {
61920
62278
  if (cut <= 0 || cut >= text4.length)
61921
62279
  return cut;
61922
62280
  const lineStart2 = text4.lastIndexOf(`
@@ -62588,28 +62946,28 @@ function isTurnFlushSafetyEnabled(env = process.env) {
62588
62946
  }
62589
62947
 
62590
62948
  // agent-dir.ts
62591
- import { dirname as dirname8 } from "node:path";
62949
+ import { dirname as dirname9 } from "node:path";
62592
62950
  function resolveAgentDirFromEnv() {
62593
62951
  const state3 = process.env.TELEGRAM_STATE_DIR;
62594
62952
  if (!state3 || state3.trim().length === 0)
62595
62953
  return null;
62596
- return dirname8(state3);
62954
+ return dirname9(state3);
62597
62955
  }
62598
62956
 
62599
62957
  // active-reactions.ts
62600
- import { readFileSync as readFileSync18, writeFileSync as writeFileSync14, renameSync as renameSync5, existsSync as existsSync19, unlinkSync as unlinkSync8 } from "node:fs";
62601
- import { join as join21 } from "node:path";
62958
+ import { readFileSync as readFileSync20, writeFileSync as writeFileSync16, renameSync as renameSync5, existsSync as existsSync21, unlinkSync as unlinkSync8 } from "node:fs";
62959
+ import { join as join22 } from "node:path";
62602
62960
  var ACTIVE_REACTIONS_FILENAME = ".active-reactions.json";
62603
62961
  function reactionsPath(agentDir) {
62604
- return join21(agentDir, ACTIVE_REACTIONS_FILENAME);
62962
+ return join22(agentDir, ACTIVE_REACTIONS_FILENAME);
62605
62963
  }
62606
62964
  function readActiveReactions(agentDir) {
62607
62965
  const p = reactionsPath(agentDir);
62608
- if (!existsSync19(p))
62966
+ if (!existsSync21(p))
62609
62967
  return [];
62610
62968
  let raw;
62611
62969
  try {
62612
- raw = readFileSync18(p, "utf-8");
62970
+ raw = readFileSync20(p, "utf-8");
62613
62971
  } catch {
62614
62972
  return [];
62615
62973
  }
@@ -62641,7 +62999,7 @@ function writeActiveReactions(agentDir, reactions) {
62641
62999
  }
62642
63000
  const tmp = `${p}.tmp-${process.pid}-${Date.now()}`;
62643
63001
  try {
62644
- writeFileSync14(tmp, JSON.stringify(reactions) + `
63002
+ writeFileSync16(tmp, JSON.stringify(reactions) + `
62645
63003
  `, "utf-8");
62646
63004
  renameSync5(tmp, p);
62647
63005
  } catch {}
@@ -62665,19 +63023,19 @@ function clearActiveReactions(agentDir) {
62665
63023
  }
62666
63024
 
62667
63025
  // active-reactions.ts
62668
- import { readFileSync as readFileSync19, writeFileSync as writeFileSync15, renameSync as renameSync6, existsSync as existsSync20, unlinkSync as unlinkSync9 } from "node:fs";
62669
- import { join as join22 } from "node:path";
63026
+ import { readFileSync as readFileSync21, writeFileSync as writeFileSync17, renameSync as renameSync6, existsSync as existsSync22, unlinkSync as unlinkSync9 } from "node:fs";
63027
+ import { join as join23 } from "node:path";
62670
63028
  var ACTIVE_REACTIONS_FILENAME2 = ".active-reactions.json";
62671
63029
  function reactionsPath2(agentDir) {
62672
- return join22(agentDir, ACTIVE_REACTIONS_FILENAME2);
63030
+ return join23(agentDir, ACTIVE_REACTIONS_FILENAME2);
62673
63031
  }
62674
63032
  function readActiveReactions2(agentDir) {
62675
63033
  const p = reactionsPath2(agentDir);
62676
- if (!existsSync20(p))
63034
+ if (!existsSync22(p))
62677
63035
  return [];
62678
63036
  let raw;
62679
63037
  try {
62680
- raw = readFileSync19(p, "utf-8");
63038
+ raw = readFileSync21(p, "utf-8");
62681
63039
  } catch {
62682
63040
  return [];
62683
63041
  }
@@ -63584,17 +63942,17 @@ async function approvalRecord(args, opts) {
63584
63942
  }
63585
63943
 
63586
63944
  // quota-check.ts
63587
- import { readFileSync as readFileSync20, existsSync as existsSync21 } from "fs";
63588
- import { join as join23 } from "path";
63945
+ import { readFileSync as readFileSync22, existsSync as existsSync23 } from "fs";
63946
+ import { join as join24 } from "path";
63589
63947
  var OAUTH_BETA2 = "oauth-2025-04-20";
63590
63948
  var DEFAULT_USER_AGENT2 = "claude-cli/1.0.0 (external, cli)";
63591
63949
  var DEFAULT_PROBE_MODEL2 = "claude-haiku-4-5-20251001";
63592
63950
  function readOauthToken2(claudeConfigDir) {
63593
- const tokenFile = join23(claudeConfigDir, ".oauth-token");
63594
- if (!existsSync21(tokenFile))
63951
+ const tokenFile = join24(claudeConfigDir, ".oauth-token");
63952
+ if (!existsSync23(tokenFile))
63595
63953
  return null;
63596
63954
  try {
63597
- const raw = readFileSync20(tokenFile, "utf-8").trim();
63955
+ const raw = readFileSync22(tokenFile, "utf-8").trim();
63598
63956
  return raw.length > 0 ? raw : null;
63599
63957
  } catch {
63600
63958
  return null;
@@ -64329,7 +64687,7 @@ function parseModelCommand(text4) {
64329
64687
  }
64330
64688
  return { kind: "set", model: arg };
64331
64689
  }
64332
- var PERSIST_NOTE = "_Session-only \u2014 lasts until restart. To persist, set `model:` in switchroom.yaml and restart._";
64690
+ var PERSIST_NOTE = "_Sticky across switchroom-managed relaunches (`/new`, watchdog recovery); reverts on `/restart`, agent restart, crash, or external container restart. `/model default` clears it. To persist, set `model:` in switchroom.yaml._";
64333
64691
  function helpText2(deps, reason) {
64334
64692
  const srAliasExamples = Object.keys(SR_MODEL_ALIASES).map((a) => `\`${a}\``).join(" \u00b7 ");
64335
64693
  const lines = [];
@@ -64414,7 +64772,7 @@ async function handleModelCommand(parsed, deps) {
64414
64772
  return {
64415
64773
  text: [
64416
64774
  `Switching to \`${deps.escapeHtml(model)}\` \u2014 restarting session (~30s).`,
64417
- "_Session-only \u2014 reverts to the configured default on the next restart._"
64775
+ PERSIST_NOTE
64418
64776
  ].join(`
64419
64777
  `),
64420
64778
  html: true
@@ -64714,7 +65072,8 @@ async function handleModelMenuCallback(data, deps) {
64714
65072
  }
64715
65073
  return {
64716
65074
  answer: `Switching to ${friendlyName} \u2014 restarting (~30s)`,
64717
- reply: await menuWithBannerStatic(deps, `\uD83D\uDD04 Switching session to **${deps.escapeHtml(friendlyName)}** \u2014 restarting (~30s). _Session-only; reverts to the configured default on the next restart._`),
65075
+ reply: await menuWithBannerStatic(deps, `\uD83D\uDD04 Switching session to **${deps.escapeHtml(friendlyName)}** \u2014 restarting (~30s).
65076
+ ${PERSIST_NOTE}`),
64718
65077
  selectedModel: srName
64719
65078
  };
64720
65079
  }
@@ -64756,11 +65115,13 @@ async function handleModelMenuCallback(data, deps) {
64756
65115
  }
64757
65116
  const token = canonicalClaudeToken(target.label);
64758
65117
  const selectedModel = sessionModelFromConfirmation(result.confirmation) ?? token ?? undefined;
65118
+ const clearedDefault = token == null && /^default\b/i.test(target.label.trim());
64759
65119
  return {
64760
65120
  answer: deps.escapeHtml(result.confirmation),
64761
65121
  reply: await menuWithBanner(deps, `\u2705 ${deps.escapeHtml(result.confirmation)}`),
64762
65122
  ...selectedModel ? { selectedModel } : {},
64763
- ...token ? { selectedModelToken: token } : {}
65123
+ ...token ? { selectedModelToken: token } : {},
65124
+ ...clearedDefault ? { clearedDefault: true } : {}
64764
65125
  };
64765
65126
  }
64766
65127
  function isSrToClaudeTransition(prevModel, nextModel) {
@@ -64818,6 +65179,87 @@ async function menuWithBannerStatic(deps, banner) {
64818
65179
  };
64819
65180
  }
64820
65181
 
65182
+ // gateway/session-model-file.ts
65183
+ import { readFileSync as readFileSync23, writeFileSync as writeFileSync18, renameSync as renameSync7, rmSync as rmSync4 } from "node:fs";
65184
+ import { join as join25 } from "node:path";
65185
+
65186
+ // gateway/model-command.ts
65187
+ var MODEL_ARG_RE2 = /^[A-Za-z0-9][A-Za-z0-9._/\[\]-]{0,99}$/;
65188
+ function isValidModelArg2(arg) {
65189
+ return MODEL_ARG_RE2.test(arg);
65190
+ }
65191
+
65192
+ // gateway/session-model-file.ts
65193
+ var SESSION_MODEL_FILE = ".session-model";
65194
+ var RELAUNCH_MODEL_INTENT_FILE = ".relaunch-model-intent";
65195
+ var CONFIGURED_DEFAULT_MODEL_FILE = ".configured-default-model";
65196
+ var REVERT_RESTART_REASONS = new Set(["inline-button-restart"]);
65197
+ function intentForRestartReason(reason) {
65198
+ return REVERT_RESTART_REASONS.has(reason) ? "revert" : "keep";
65199
+ }
65200
+ function atomicWrite(path2, content3) {
65201
+ const tmp = `${path2}.tmp-${process.pid}-${Date.now()}`;
65202
+ writeFileSync18(tmp, content3, "utf8");
65203
+ renameSync7(tmp, path2);
65204
+ }
65205
+ function serializeSessionModel(rec) {
65206
+ return `${JSON.stringify({
65207
+ model: rec.model,
65208
+ configuredDefaultAtWrite: rec.configuredDefaultAtWrite,
65209
+ ts: rec.ts
65210
+ })}
65211
+ `;
65212
+ }
65213
+ function writeSessionModelFile(agentDir, model, configuredDefaultAtWrite) {
65214
+ if (!isValidModelArg2(model)) {
65215
+ throw new Error(`refusing to persist non-canonical session model token: ${JSON.stringify(model)}`);
65216
+ }
65217
+ atomicWrite(join25(agentDir, SESSION_MODEL_FILE), serializeSessionModel({ model, configuredDefaultAtWrite, ts: Date.now() }));
65218
+ }
65219
+ function readSessionModelFileRaw(agentDir) {
65220
+ try {
65221
+ return readFileSync23(join25(agentDir, SESSION_MODEL_FILE), "utf8");
65222
+ } catch {
65223
+ return null;
65224
+ }
65225
+ }
65226
+ function clearSessionModelFile(agentDir) {
65227
+ try {
65228
+ rmSync4(join25(agentDir, SESSION_MODEL_FILE), { force: true });
65229
+ } catch {}
65230
+ }
65231
+ function restoreSessionModelFileRaw(agentDir, raw) {
65232
+ if (raw == null) {
65233
+ clearSessionModelFile(agentDir);
65234
+ return;
65235
+ }
65236
+ try {
65237
+ atomicWrite(join25(agentDir, SESSION_MODEL_FILE), raw);
65238
+ } catch {}
65239
+ }
65240
+ function writeRelaunchModelIntent(agentDir, intent, reason) {
65241
+ try {
65242
+ atomicWrite(join25(agentDir, RELAUNCH_MODEL_INTENT_FILE), `${JSON.stringify({ intent, reason, ts: Date.now() })}
65243
+ `);
65244
+ } catch (err) {
65245
+ process.stderr.write(`telegram gateway: relaunch-model-intent write failed (boot will revert): ${err?.message ?? String(err)}
65246
+ `);
65247
+ }
65248
+ }
65249
+ function clearRelaunchModelIntent(agentDir) {
65250
+ try {
65251
+ rmSync4(join25(agentDir, RELAUNCH_MODEL_INTENT_FILE), { force: true });
65252
+ } catch {}
65253
+ }
65254
+ function readConfiguredDefaultModel(agentDir) {
65255
+ try {
65256
+ const v = readFileSync23(join25(agentDir, CONFIGURED_DEFAULT_MODEL_FILE), "utf8").trim();
65257
+ return v.length > 0 ? v : null;
65258
+ } catch {
65259
+ return null;
65260
+ }
65261
+ }
65262
+
64821
65263
  // ../src/agents/model-picker.ts
64822
65264
  var HEADER_RE = /Select model/;
64823
65265
  var OPTION_RE = /^\s*(\u276f)?\s*(\d+)\.\s+(.*)$/;
@@ -65029,7 +65471,7 @@ function extractConfirmation(pane) {
65029
65471
  }
65030
65472
 
65031
65473
  // ../src/agents/scaffold.ts
65032
- import { join as join26, resolve as resolve6 } from "node:path";
65474
+ import { join as join28, resolve as resolve6 } from "node:path";
65033
65475
  init_schema();
65034
65476
 
65035
65477
  // ../src/config/users.ts
@@ -65047,7 +65489,7 @@ var CONTAINER_DEFAULT_UTC_ZONES = new Set([
65047
65489
  ]);
65048
65490
 
65049
65491
  // ../src/cli/agent-config.ts
65050
- import { join as join24 } from "node:path";
65492
+ import { join as join26 } from "node:path";
65051
65493
  import { homedir as homedir8 } from "node:os";
65052
65494
 
65053
65495
  // ../src/cli/helpers.ts
@@ -65067,12 +65509,12 @@ var WEBKITE_VAULT_KEYS = new Set([
65067
65509
  init_overlay_loader();
65068
65510
 
65069
65511
  // ../src/cli/agent-config.ts
65070
- var AUDIT_ROOT = join24(homedir8(), ".switchroom", "audit");
65512
+ var AUDIT_ROOT = join26(homedir8(), ".switchroom", "audit");
65071
65513
 
65072
65514
  // ../src/agents/profiles.ts
65073
65515
  var import_handlebars = __toESM(require_lib2(), 1);
65074
- import { readFileSync as readFileSync21, writeFileSync as writeFileSync16, existsSync as existsSync22, readdirSync as readdirSync5, statSync as statSync8, copyFileSync, mkdirSync as mkdirSync16, realpathSync as realpathSync2 } from "node:fs";
65075
- import { resolve as resolve5, join as join25, sep as pathSep } from "node:path";
65516
+ import { readFileSync as readFileSync24, writeFileSync as writeFileSync19, existsSync as existsSync24, readdirSync as readdirSync5, statSync as statSync8, copyFileSync, mkdirSync as mkdirSync18, realpathSync as realpathSync2 } from "node:fs";
65517
+ import { resolve as resolve5, join as join27, sep as pathSep } from "node:path";
65076
65518
  var PROFILES_ROOT = resolve5(import.meta.dirname, "../../profiles");
65077
65519
  import_handlebars.default.registerHelper("json", (value) => {
65078
65520
  return new import_handlebars.default.SafeString(JSON.stringify(value, null, 2));
@@ -65083,9 +65525,9 @@ import_handlebars.default.registerHelper("isNumber", (value) => {
65083
65525
  var SHARED_FRAGMENTS_DIR = resolve5(PROFILES_ROOT, "_shared");
65084
65526
  var SHARED_FRAGMENTS = ["vault-protocol", "agent-self-service", "execution-discipline", "reply-discipline"];
65085
65527
  for (const name of SHARED_FRAGMENTS) {
65086
- const fragPath = join25(SHARED_FRAGMENTS_DIR, `${name}.md.hbs`);
65087
- if (existsSync22(fragPath)) {
65088
- import_handlebars.default.registerPartial(name, readFileSync21(fragPath, "utf-8"));
65528
+ const fragPath = join27(SHARED_FRAGMENTS_DIR, `${name}.md.hbs`);
65529
+ if (existsSync24(fragPath)) {
65530
+ import_handlebars.default.registerPartial(name, readFileSync24(fragPath, "utf-8"));
65089
65531
  }
65090
65532
  }
65091
65533
 
@@ -65377,7 +65819,7 @@ init_paths();
65377
65819
  init_overlay_loader();
65378
65820
  init_merge();
65379
65821
  var import_yaml3 = __toESM(require_dist(), 1);
65380
- import { readFileSync as readFileSync22, existsSync as existsSync23 } from "node:fs";
65822
+ import { readFileSync as readFileSync25, existsSync as existsSync25 } from "node:fs";
65381
65823
  import { homedir as homedir9 } from "node:os";
65382
65824
  import { resolve as resolve7 } from "node:path";
65383
65825
 
@@ -65453,7 +65895,7 @@ function findConfigFile2(startDir) {
65453
65895
  resolve7(userDir, "clerk.yml")
65454
65896
  ].filter(Boolean);
65455
65897
  for (const path2 of searchPaths) {
65456
- if (existsSync23(path2)) {
65898
+ if (existsSync25(path2)) {
65457
65899
  return path2;
65458
65900
  }
65459
65901
  }
@@ -65461,12 +65903,12 @@ function findConfigFile2(startDir) {
65461
65903
  }
65462
65904
  function loadConfig2(configPath) {
65463
65905
  const filePath = configPath ?? findConfigFile2();
65464
- if (!existsSync23(filePath)) {
65906
+ if (!existsSync25(filePath)) {
65465
65907
  throw new ConfigError2(`Config file not found: ${filePath}`);
65466
65908
  }
65467
65909
  let raw;
65468
65910
  try {
65469
- raw = readFileSync22(filePath, "utf-8");
65911
+ raw = readFileSync25(filePath, "utf-8");
65470
65912
  } catch (err) {
65471
65913
  throw new ConfigError2(`Failed to read config file: ${filePath}`, [
65472
65914
  ` ${err.message}`
@@ -65969,15 +66411,15 @@ function topicForRecipient(args) {
65969
66411
  }
65970
66412
 
65971
66413
  // ../src/agents/perf.ts
65972
- import { existsSync as existsSync24, readFileSync as readFileSync23 } from "node:fs";
66414
+ import { existsSync as existsSync26, readFileSync as readFileSync26 } from "node:fs";
65973
66415
  function readTurnUsages(jsonlPath, lastN) {
65974
- if (!existsSync24(jsonlPath))
66416
+ if (!existsSync26(jsonlPath))
65975
66417
  return [];
65976
66418
  if (lastN <= 0)
65977
66419
  return [];
65978
66420
  let raw;
65979
66421
  try {
65980
- raw = readFileSync23(jsonlPath, "utf-8");
66422
+ raw = readFileSync26(jsonlPath, "utf-8");
65981
66423
  } catch {
65982
66424
  return [];
65983
66425
  }
@@ -66068,8 +66510,8 @@ function numField(obj, key) {
66068
66510
  }
66069
66511
 
66070
66512
  // gateway/context-occupancy.ts
66071
- import { mkdirSync as mkdirSync17, writeFileSync as writeFileSync17 } from "node:fs";
66072
- import { join as join27 } from "node:path";
66513
+ import { mkdirSync as mkdirSync19, writeFileSync as writeFileSync20 } from "node:fs";
66514
+ import { join as join29 } from "node:path";
66073
66515
  var CONTEXT_OCCUPANCY_FILENAME = "context-occupancy.json";
66074
66516
  var TIGHT_FRACTION = 0.8;
66075
66517
  function buildContextOccupancy(occupancy, cap, now) {
@@ -66092,9 +66534,9 @@ function buildContextOccupancy(occupancy, cap, now) {
66092
66534
  }
66093
66535
  function writeContextOccupancySnapshot(stateDir, snapshot, deps) {
66094
66536
  try {
66095
- const path2 = join27(stateDir, CONTEXT_OCCUPANCY_FILENAME);
66096
- (deps?.mkdir ?? ((p, o) => mkdirSync17(p, o)))(stateDir, { recursive: true });
66097
- (deps?.writeFile ?? ((p, d) => writeFileSync17(p, d)))(path2, JSON.stringify(snapshot, null, 2) + `
66537
+ const path2 = join29(stateDir, CONTEXT_OCCUPANCY_FILENAME);
66538
+ (deps?.mkdir ?? ((p, o) => mkdirSync19(p, o)))(stateDir, { recursive: true });
66539
+ (deps?.writeFile ?? ((p, d) => writeFileSync20(p, d)))(path2, JSON.stringify(snapshot, null, 2) + `
66098
66540
  `);
66099
66541
  } catch {}
66100
66542
  }
@@ -66177,7 +66619,7 @@ function nextCompactNotify(state3, ev) {
66177
66619
  }
66178
66620
 
66179
66621
  // gateway/hostd-dispatch.ts
66180
- import { existsSync as existsSync25 } from "node:fs";
66622
+ import { existsSync as existsSync27 } from "node:fs";
66181
66623
  import { randomBytes as randomBytes5 } from "node:crypto";
66182
66624
 
66183
66625
  // ../src/host-control/client.ts
@@ -66502,13 +66944,13 @@ function hostdSocketPath(agentName3) {
66502
66944
  function hostdWillBeUsed(agentName3) {
66503
66945
  if (!isHostdEnabled())
66504
66946
  return false;
66505
- return existsSync25(hostdSocketPath(agentName3));
66947
+ return existsSync27(hostdSocketPath(agentName3));
66506
66948
  }
66507
66949
  async function tryHostdDispatch(agentName3, req, timeoutMs = 5000) {
66508
66950
  if (!isHostdEnabled())
66509
66951
  return "not-configured";
66510
66952
  const sockPath = hostdSocketPath(agentName3);
66511
- if (!existsSync25(sockPath))
66953
+ if (!existsSync27(sockPath))
66512
66954
  return "not-configured";
66513
66955
  try {
66514
66956
  return await hostdRequest({ socketPath: sockPath, timeoutMs }, req);
@@ -66532,7 +66974,7 @@ async function hostdGetStatusOnce(agentName3, targetRequestId) {
66532
66974
  if (!isHostdEnabled())
66533
66975
  return "not-configured";
66534
66976
  const sockPath = hostdSocketPath(agentName3);
66535
- if (!existsSync25(sockPath))
66977
+ if (!existsSync27(sockPath))
66536
66978
  return "not-configured";
66537
66979
  try {
66538
66980
  const resp = await hostdRequest({ socketPath: sockPath, timeoutMs: 3000 }, {
@@ -66553,7 +66995,7 @@ async function pollHostdStatus(agentName3, targetRequestId, opts) {
66553
66995
  if (!isHostdEnabled())
66554
66996
  return "not-configured";
66555
66997
  const sockPath = hostdSocketPath(agentName3);
66556
- if (!existsSync25(sockPath))
66998
+ if (!existsSync27(sockPath))
66557
66999
  return "not-configured";
66558
67000
  const now = opts.now ?? Date.now;
66559
67001
  const sleep2 = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
@@ -66632,7 +67074,7 @@ function shouldSweepChatAtBoot(chatId) {
66632
67074
 
66633
67075
  // gateway/webhook-ingest-server.ts
66634
67076
  import net4 from "node:net";
66635
- import { chmodSync as chmodSync5, existsSync as existsSync26, unlinkSync as unlinkSync10 } from "node:fs";
67077
+ import { chmodSync as chmodSync5, existsSync as existsSync28, unlinkSync as unlinkSync10 } from "node:fs";
66636
67078
  var MAX_REQUEST_BYTES = 1024 * 1024;
66637
67079
  function fdOf(conn) {
66638
67080
  const handle = conn._handle;
@@ -66644,7 +67086,7 @@ function startWebhookIngestServer(opts) {
66644
67086
  const log = opts.log ?? ((s) => process.stderr.write(s));
66645
67087
  const allowed = new Set(opts.allowedUids);
66646
67088
  try {
66647
- if (existsSync26(opts.socketPath))
67089
+ if (existsSync28(opts.socketPath))
66648
67090
  unlinkSync10(opts.socketPath);
66649
67091
  } catch (err) {
66650
67092
  log(`webhook-ingest-server: could not unlink stale socket: ${err.message}
@@ -66737,7 +67179,7 @@ function startWebhookIngestServer(opts) {
66737
67179
  server.close();
66738
67180
  } catch {}
66739
67181
  try {
66740
- if (existsSync26(opts.socketPath))
67182
+ if (existsSync28(opts.socketPath))
66741
67183
  unlinkSync10(opts.socketPath);
66742
67184
  } catch {}
66743
67185
  }
@@ -66745,20 +67187,20 @@ function startWebhookIngestServer(opts) {
66745
67187
  }
66746
67188
 
66747
67189
  // ../src/web/webhook-gateway-record.ts
66748
- import { appendFileSync as appendFileSync5, mkdirSync as mkdirSync20 } from "fs";
66749
- import { join as join30 } from "path";
67190
+ import { appendFileSync as appendFileSync5, mkdirSync as mkdirSync22 } from "fs";
67191
+ import { join as join32 } from "path";
66750
67192
  import { homedir as homedir11 } from "os";
66751
67193
 
66752
67194
  // ../src/web/webhook-handler.ts
66753
- import { appendFileSync as appendFileSync4, existsSync as existsSync27, mkdirSync as mkdirSync18, readFileSync as readFileSync24, writeFileSync as writeFileSync18 } from "fs";
66754
- import { join as join28 } from "path";
67195
+ import { appendFileSync as appendFileSync4, existsSync as existsSync29, mkdirSync as mkdirSync20, readFileSync as readFileSync27, writeFileSync as writeFileSync21 } from "fs";
67196
+ import { join as join30 } from "path";
66755
67197
  var DEDUP_MAX = 1000;
66756
67198
  var DEDUP_TTL_MS = 24 * 60 * 60 * 1000;
66757
67199
  function loadDedupFile(path2) {
66758
67200
  try {
66759
- if (!existsSync27(path2))
67201
+ if (!existsSync29(path2))
66760
67202
  return {};
66761
- const raw = JSON.parse(readFileSync24(path2, "utf-8"));
67203
+ const raw = JSON.parse(readFileSync27(path2, "utf-8"));
66762
67204
  return typeof raw.deliveries === "object" && raw.deliveries !== null ? raw.deliveries : {};
66763
67205
  } catch {
66764
67206
  return {};
@@ -66772,7 +67214,7 @@ function saveDedupFile(path2, deliveries, now) {
66772
67214
  }
66773
67215
  const sorted = Object.entries(pruned).sort((a, b) => b[1] - a[1]).slice(0, DEDUP_MAX);
66774
67216
  const final = Object.fromEntries(sorted);
66775
- writeFileSync18(path2, JSON.stringify({ deliveries: final }), {
67217
+ writeFileSync21(path2, JSON.stringify({ deliveries: final }), {
66776
67218
  mode: 384
66777
67219
  });
66778
67220
  }
@@ -66780,8 +67222,8 @@ var agentDedupCache = new Map;
66780
67222
  function createFileDedupStore(resolveAgentDir) {
66781
67223
  return {
66782
67224
  check(agent, deliveryId, now) {
66783
- const telegramDir = join28(resolveAgentDir(agent), "telegram");
66784
- const filePath = join28(telegramDir, "webhook-dedup.json");
67225
+ const telegramDir = join30(resolveAgentDir(agent), "telegram");
67226
+ const filePath = join30(telegramDir, "webhook-dedup.json");
66785
67227
  if (!agentDedupCache.has(agent)) {
66786
67228
  agentDedupCache.set(agent, loadDedupFile(filePath));
66787
67229
  }
@@ -66791,7 +67233,7 @@ function createFileDedupStore(resolveAgentDir) {
66791
67233
  }
66792
67234
  deliveries[deliveryId] = now;
66793
67235
  try {
66794
- mkdirSync18(telegramDir, { recursive: true });
67236
+ mkdirSync20(telegramDir, { recursive: true });
66795
67237
  saveDedupFile(filePath, deliveries, now);
66796
67238
  } catch {}
66797
67239
  return;
@@ -66802,8 +67244,8 @@ var tokenBuckets = new Map;
66802
67244
  var throttleIssueWindow = new Map;
66803
67245
 
66804
67246
  // ../src/web/webhook-dispatch.ts
66805
- import { existsSync as existsSync28, mkdirSync as mkdirSync19, readFileSync as readFileSync25, writeFileSync as writeFileSync19 } from "fs";
66806
- import { join as join29 } from "path";
67247
+ import { existsSync as existsSync30, mkdirSync as mkdirSync21, readFileSync as readFileSync28, writeFileSync as writeFileSync22 } from "fs";
67248
+ import { join as join31 } from "path";
66807
67249
  import { homedir as homedir10 } from "os";
66808
67250
 
66809
67251
  // ../src/agent-scheduler/ipc-client.ts
@@ -67102,9 +67544,9 @@ function cooldownKey(source, eventType, repo, number, ruleIndex) {
67102
67544
  }
67103
67545
  function loadCooldownFile(path2) {
67104
67546
  try {
67105
- if (!existsSync28(path2))
67547
+ if (!existsSync30(path2))
67106
67548
  return {};
67107
- const raw = JSON.parse(readFileSync25(path2, "utf-8"));
67549
+ const raw = JSON.parse(readFileSync28(path2, "utf-8"));
67108
67550
  return typeof raw.dispatches === "object" && raw.dispatches !== null ? raw.dispatches : {};
67109
67551
  } catch {
67110
67552
  return {};
@@ -67112,7 +67554,7 @@ function loadCooldownFile(path2) {
67112
67554
  }
67113
67555
  function saveCooldownFile(path2, dispatches) {
67114
67556
  try {
67115
- writeFileSync19(path2, JSON.stringify({ dispatches }), {
67557
+ writeFileSync22(path2, JSON.stringify({ dispatches }), {
67116
67558
  mode: 384
67117
67559
  });
67118
67560
  } catch {}
@@ -67123,8 +67565,8 @@ function createFileCooldownStore(resolveAgentDir) {
67123
67565
  isCoolingDown(agent, key, cooldownMs, now) {
67124
67566
  if (cooldownMs <= 0)
67125
67567
  return false;
67126
- const telegramDir = join29(resolveAgentDir(agent), "telegram");
67127
- const filePath = join29(telegramDir, "webhook-cooldown.json");
67568
+ const telegramDir = join31(resolveAgentDir(agent), "telegram");
67569
+ const filePath = join31(telegramDir, "webhook-cooldown.json");
67128
67570
  if (!cache.has(agent)) {
67129
67571
  cache.set(agent, loadCooldownFile(filePath));
67130
67572
  }
@@ -67135,7 +67577,7 @@ function createFileCooldownStore(resolveAgentDir) {
67135
67577
  }
67136
67578
  dispatches[key] = now;
67137
67579
  try {
67138
- mkdirSync19(telegramDir, { recursive: true });
67580
+ mkdirSync21(telegramDir, { recursive: true });
67139
67581
  saveCooldownFile(filePath, dispatches);
67140
67582
  } catch {}
67141
67583
  return false;
@@ -67181,9 +67623,9 @@ async function defaultInject(socketPath, agentName3, inbound) {
67181
67623
  }
67182
67624
  function injectWebhookInbound(agent, prompt, ctx, deps = {}) {
67183
67625
  const log = deps.log ?? ((s) => process.stderr.write(s));
67184
- const resolveAgentDir = deps.resolveAgentDir ?? ((a) => join29(homedir10(), ".switchroom", "agents", a));
67626
+ const resolveAgentDir = deps.resolveAgentDir ?? ((a) => join31(homedir10(), ".switchroom", "agents", a));
67185
67627
  const now = (deps.now ?? Date.now)();
67186
- const socketPath = join29(resolveAgentDir(agent), "telegram", "gateway.sock");
67628
+ const socketPath = join31(resolveAgentDir(agent), "telegram", "gateway.sock");
67187
67629
  const inbound = {
67188
67630
  type: "inbound",
67189
67631
  chatId: ctx.chatId,
@@ -67255,7 +67697,7 @@ function evaluateDispatch(args, deps = {}) {
67255
67697
  const log = deps.log ?? ((s) => process.stderr.write(s));
67256
67698
  const now = (deps.now ?? Date.now)();
67257
67699
  const nowDate = deps.nowDate ?? (() => new Date(now));
67258
- const resolveAgentDir = deps.resolveAgentDir ?? ((a) => join29(homedir10(), ".switchroom", "agents", a));
67700
+ const resolveAgentDir = deps.resolveAgentDir ?? ((a) => join31(homedir10(), ".switchroom", "agents", a));
67259
67701
  const cooldownStore = deps.cooldownStore ?? createFileCooldownStore(resolveAgentDir);
67260
67702
  if (!DISPATCH_SOURCES.includes(args.source))
67261
67703
  return 0;
@@ -67333,10 +67775,10 @@ var CONSOLIDATION_COMPLETED_EVENT = "consolidation.completed";
67333
67775
  function recordWebhookEvent(rec, deps = {}) {
67334
67776
  const log = deps.log ?? ((s) => process.stderr.write(s));
67335
67777
  const now = rec.ts || (deps.now ?? Date.now)();
67336
- const resolveAgentDir = deps.resolveAgentDir ?? ((a) => join30(homedir11(), ".switchroom", "agents", a));
67778
+ const resolveAgentDir = deps.resolveAgentDir ?? ((a) => join32(homedir11(), ".switchroom", "agents", a));
67337
67779
  const dedupStore = deps.dedupStore ?? createFileDedupStore(resolveAgentDir);
67338
67780
  const agent = rec.agent;
67339
- const telegramDir = join30(resolveAgentDir(agent), "telegram");
67781
+ const telegramDir = join32(resolveAgentDir(agent), "telegram");
67340
67782
  if (rec.source === "github" && rec.delivery_id) {
67341
67783
  const originalTs = dedupStore.check(agent, rec.delivery_id, now);
67342
67784
  if (originalTs !== undefined) {
@@ -67345,9 +67787,9 @@ function recordWebhookEvent(rec, deps = {}) {
67345
67787
  return { status: "deduped", ts: originalTs };
67346
67788
  }
67347
67789
  }
67348
- const logPath = join30(telegramDir, "webhook-events.jsonl");
67790
+ const logPath = join32(telegramDir, "webhook-events.jsonl");
67349
67791
  try {
67350
- mkdirSync20(telegramDir, { recursive: true });
67792
+ mkdirSync22(telegramDir, { recursive: true });
67351
67793
  const record = {
67352
67794
  ts: now,
67353
67795
  source: rec.source,
@@ -67456,7 +67898,7 @@ function recordWebhookEvent(rec, deps = {}) {
67456
67898
  }
67457
67899
 
67458
67900
  // gateway/ipc-server.ts
67459
- import { renameSync as renameSync7, unlinkSync as unlinkSync11, chmodSync as chmodSync6 } from "fs";
67901
+ import { renameSync as renameSync8, unlinkSync as unlinkSync11, chmodSync as chmodSync6 } from "fs";
67460
67902
  var MAX_BUFFER_SIZE = 1024 * 1024;
67461
67903
  var VALID_OPERATOR_KINDS = new Set([
67462
67904
  "credentials-expired",
@@ -67655,7 +68097,7 @@ function createIpcServer(options) {
67655
68097
  heartbeatTimeoutMs = 30000
67656
68098
  } = options;
67657
68099
  try {
67658
- renameSync7(socketPath, socketPath + ".bak");
68100
+ renameSync8(socketPath, socketPath + ".bak");
67659
68101
  } catch {}
67660
68102
  try {
67661
68103
  unlinkSync11(socketPath + ".bak");
@@ -68032,7 +68474,7 @@ function createIpcServer(options) {
68032
68474
  clientBySocketId.clear();
68033
68475
  server.stop(true);
68034
68476
  try {
68035
- renameSync7(socketPath, socketPath + ".bak");
68477
+ renameSync8(socketPath, socketPath + ".bak");
68036
68478
  } catch {}
68037
68479
  }
68038
68480
  };
@@ -68856,11 +69298,12 @@ function persistObligations(path2, fs2, snapshot, log = (l) => process.stderr.wr
68856
69298
  }
68857
69299
 
68858
69300
  // gateway/status-pin-store.ts
69301
+ var BOOT_UNPIN_MAX_ATTEMPTS = 5;
68859
69302
  function isPinRow(x) {
68860
69303
  if (x == null || typeof x !== "object")
68861
69304
  return false;
68862
69305
  const o = x;
68863
- return typeof o.pinKey === "string" && o.pinKey.length > 0 && typeof o.chatId === "string" && o.chatId.length > 0 && typeof o.messageId === "number" && (o.pending === undefined || typeof o.pending === "boolean");
69306
+ return typeof o.pinKey === "string" && o.pinKey.length > 0 && typeof o.chatId === "string" && o.chatId.length > 0 && typeof o.messageId === "number" && (o.pending === undefined || typeof o.pending === "boolean") && (o.expiresAt === undefined || typeof o.expiresAt === "number") && (o.attempts === undefined || typeof o.attempts === "number");
68864
69307
  }
68865
69308
  function loadStatusPins(path2, fs2) {
68866
69309
  if (!fs2.existsSync(path2))
@@ -68904,21 +69347,38 @@ function pinnedMessageIsOurs(tracked, chatId, pinnedMessageId) {
68904
69347
  }
68905
69348
  async function runStatusPinBootCleanup(args) {
68906
69349
  const log = args.log ?? ((l) => process.stderr.write(l));
69350
+ const now = args.now ?? Date.now();
68907
69351
  const persisted = loadStatusPins(args.path, args.fs);
68908
69352
  if (persisted.length === 0)
68909
- return { cleared: 0, total: 0 };
69353
+ return { cleared: 0, retained: 0, kept: 0, total: 0 };
68910
69354
  let cleared = 0;
69355
+ let retained = 0;
69356
+ let kept = 0;
69357
+ const next = [];
68911
69358
  for (const pin of persisted) {
69359
+ if (pin.expiresAt != null && pin.expiresAt > now) {
69360
+ next.push(pin);
69361
+ kept++;
69362
+ continue;
69363
+ }
68912
69364
  try {
68913
69365
  await args.unpin(pin.chatId, pin.messageId);
68914
69366
  cleared++;
68915
69367
  } catch (err) {
68916
- log(`status-pin-store: boot cleanup unpin failed ` + `(chat=${pin.chatId} msg=${pin.messageId}): ${err.message}
69368
+ const attempts = (pin.attempts ?? 0) + 1;
69369
+ log(`status-pin-store: boot cleanup unpin failed ` + `(chat=${pin.chatId} msg=${pin.messageId} attempt=${attempts}): ` + `${err.message}
68917
69370
  `);
69371
+ if (attempts < BOOT_UNPIN_MAX_ATTEMPTS) {
69372
+ next.push({ ...pin, attempts });
69373
+ retained++;
69374
+ } else {
69375
+ log(`status-pin-store: boot cleanup FORFEITING pin after ` + `${attempts} failed unpin attempts ` + `(key=${pin.pinKey} chat=${pin.chatId} msg=${pin.messageId}) \u2014 ` + `will not retry again
69376
+ `);
69377
+ }
68918
69378
  }
68919
69379
  }
68920
- persistStatusPins(args.path, args.fs, [], log);
68921
- return { cleared, total: persisted.length };
69380
+ persistStatusPins(args.path, args.fs, next, log);
69381
+ return { cleared, retained, kept, total: persisted.length };
68922
69382
  }
68923
69383
  var storeLockTails = new Map;
68924
69384
  function withStoreLock(path2, fn) {
@@ -68937,6 +69397,11 @@ function applyStatusPinRow(path2, fs2, pinKey, row, log) {
68937
69397
  const next = row == null ? others : [...others, row];
68938
69398
  persistStatusPins(path2, fs2, next, log);
68939
69399
  }
69400
+ function mutateStatusPinRow(path2, fs2, pinKey, row, log = (l) => process.stderr.write(l)) {
69401
+ return withStoreLock(path2, async () => {
69402
+ applyStatusPinRow(path2, fs2, pinKey, row, log);
69403
+ });
69404
+ }
68940
69405
  function reconcileAndPersistStatusPin(args) {
68941
69406
  const { path: path2, fs: fs2, pinKey, chatId, op } = args;
68942
69407
  const log = args.log ?? ((l) => process.stderr.write(l));
@@ -68958,11 +69423,12 @@ function reconcileAndPersistStatusPin(args) {
68958
69423
  }
68959
69424
 
68960
69425
  // gateway/activity-card-store.ts
69426
+ var BOOT_UNPIN_MAX_ATTEMPTS2 = 5;
68961
69427
  function isCardRow(x) {
68962
69428
  if (x == null || typeof x !== "object")
68963
69429
  return false;
68964
69430
  const o = x;
68965
- return typeof o.turnKey === "string" && o.turnKey.length > 0 && typeof o.chatId === "string" && o.chatId.length > 0 && (o.threadId === null || typeof o.threadId === "number") && typeof o.activityMessageId === "number" && typeof o.startedAt === "number" && (o.pinned === undefined || typeof o.pinned === "boolean");
69431
+ return typeof o.turnKey === "string" && o.turnKey.length > 0 && typeof o.chatId === "string" && o.chatId.length > 0 && (o.threadId === null || typeof o.threadId === "number") && typeof o.activityMessageId === "number" && typeof o.startedAt === "number" && (o.pinned === undefined || typeof o.pinned === "boolean") && (o.finalizeAttempted === undefined || typeof o.finalizeAttempted === "boolean") && (o.unpinAttempts === undefined || typeof o.unpinAttempts === "number");
68966
69432
  }
68967
69433
  function loadActivityCards(path2, fs2) {
68968
69434
  if (!fs2.existsSync(path2))
@@ -69021,23 +69487,32 @@ async function runActivityCardBootReaper(args) {
69021
69487
  let unpinned = 0;
69022
69488
  for (const record of persisted) {
69023
69489
  clearActivityCardRecord(args.path, args.fs, record.turnKey, record.activityMessageId, log);
69024
- try {
69025
- const res = await args.finalizeCard(record);
69026
- if (res != null)
69027
- finalized++;
69028
- else
69029
- vanished++;
69030
- } catch (err) {
69031
- log(`activity-card-store: boot reaper finalize failed ` + `(chat=${record.chatId} msg=${record.activityMessageId}): ` + `${err.message}
69490
+ if (!record.finalizeAttempted) {
69491
+ try {
69492
+ const res = await args.finalizeCard(record);
69493
+ if (res != null)
69494
+ finalized++;
69495
+ else
69496
+ vanished++;
69497
+ } catch (err) {
69498
+ log(`activity-card-store: boot reaper finalize failed ` + `(chat=${record.chatId} msg=${record.activityMessageId}): ` + `${err.message}
69032
69499
  `);
69500
+ }
69033
69501
  }
69034
69502
  if (record.pinned) {
69035
69503
  try {
69036
69504
  await args.unpinCard(record);
69037
69505
  unpinned++;
69038
69506
  } catch (err) {
69039
- log(`activity-card-store: boot reaper unpin failed ` + `(chat=${record.chatId} msg=${record.activityMessageId}): ` + `${err.message}
69507
+ const attempts = (record.unpinAttempts ?? 0) + 1;
69508
+ log(`activity-card-store: boot reaper unpin failed ` + `(chat=${record.chatId} msg=${record.activityMessageId} ` + `attempt=${attempts}): ${err.message}
69040
69509
  `);
69510
+ if (attempts < BOOT_UNPIN_MAX_ATTEMPTS2) {
69511
+ writeActivityCardRecord(args.path, args.fs, { ...record, finalizeAttempted: true, unpinAttempts: attempts }, log);
69512
+ } else {
69513
+ log(`activity-card-store: boot reaper FORFEITING card unpin after ` + `${attempts} failed attempts ` + `(chat=${record.chatId} msg=${record.activityMessageId}) \u2014 ` + `will not retry again
69514
+ `);
69515
+ }
69041
69516
  }
69042
69517
  }
69043
69518
  }
@@ -69084,6 +69559,37 @@ function restartOrphanCardFinalizeText(startedAt) {
69084
69559
  return `\u26a0\ufe0f Interrupted by a gateway restart${elapsed} \u2014 this turn did not finish.`;
69085
69560
  }
69086
69561
 
69562
+ // gateway/worker-pin-reaper.ts
69563
+ var WORKER_PIN_TTL_MS_DEFAULT = 6 * 60 * 60000;
69564
+ var WORKER_PIN_KEY_PREFIX = "wk:";
69565
+ function workerAgentIdOfPinKey(pinKey) {
69566
+ if (!pinKey.startsWith(WORKER_PIN_KEY_PREFIX))
69567
+ return null;
69568
+ const agentId = pinKey.slice(WORKER_PIN_KEY_PREFIX.length);
69569
+ return agentId.length > 0 ? agentId : null;
69570
+ }
69571
+ function decideWorkerPinReaps(args) {
69572
+ const reaps = [];
69573
+ for (const pin of args.pins) {
69574
+ const agentId = workerAgentIdOfPinKey(pin.pinKey);
69575
+ if (agentId == null)
69576
+ continue;
69577
+ if (pin.chatId.length === 0)
69578
+ continue;
69579
+ const status = args.statusOf(agentId);
69580
+ if (status === "terminal") {
69581
+ reaps.push({ ...pin, reason: "terminal" });
69582
+ continue;
69583
+ }
69584
+ if (status === "running")
69585
+ continue;
69586
+ if (args.now - pin.pinnedAt >= args.ttlMs) {
69587
+ reaps.push({ ...pin, reason: "ttl" });
69588
+ }
69589
+ }
69590
+ return reaps;
69591
+ }
69592
+
69087
69593
  // gateway/with-deadline.ts
69088
69594
  function withDeadline(p, ms, timeoutMessage) {
69089
69595
  p.catch(() => {});
@@ -70504,26 +71010,26 @@ var import_yaml4 = __toESM(require_dist(), 1);
70504
71010
 
70505
71011
  // ../src/web/config-diff.ts
70506
71012
  import {
70507
- mkdirSync as mkdirSync21,
71013
+ mkdirSync as mkdirSync23,
70508
71014
  mkdtempSync as mkdtemp,
70509
71015
  rmSync as rmrf,
70510
- writeFileSync as writeFileSync20
71016
+ writeFileSync as writeFileSync23
70511
71017
  } from "node:fs";
70512
71018
  import { spawnSync } from "node:child_process";
70513
71019
  import { tmpdir as tmpdir2 } from "node:os";
70514
- import { join as join31 } from "node:path";
71020
+ import { join as join33 } from "node:path";
70515
71021
 
70516
71022
  class ConfigDiffError extends Error {
70517
71023
  }
70518
71024
  function generateUnifiedDiff(before, after, name = "switchroom.yaml", gitBin = "git") {
70519
71025
  if (before === after)
70520
71026
  return "";
70521
- const dir = mkdtemp(join31(tmpdir2(), "switchroom-config-diff-"));
71027
+ const dir = mkdtemp(join33(tmpdir2(), "switchroom-config-diff-"));
70522
71028
  try {
70523
- mkdirSync21(join31(dir, "cur"), { recursive: true });
70524
- mkdirSync21(join31(dir, "new"), { recursive: true });
70525
- writeFileSync20(join31(dir, "cur", name), before);
70526
- writeFileSync20(join31(dir, "new", name), after);
71029
+ mkdirSync23(join33(dir, "cur"), { recursive: true });
71030
+ mkdirSync23(join33(dir, "new"), { recursive: true });
71031
+ writeFileSync23(join33(dir, "cur", name), before);
71032
+ writeFileSync23(join33(dir, "new", name), after);
70527
71033
  const r = spawnSync(gitBin, ["diff", "--no-index", "--no-color", "--", `cur/${name}`, `new/${name}`], { cwd: dir, encoding: "utf-8", timeout: 1e4 });
70528
71034
  if (r.status === 0)
70529
71035
  return "";
@@ -70554,6 +71060,9 @@ function generateUnifiedDiff(before, after, name = "switchroom.yaml", gitBin = "
70554
71060
  }
70555
71061
 
70556
71062
  // gateway/mental-model-propose-diff.ts
71063
+ function decodeCanonicalEntities(s) {
71064
+ return s.replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&apos;/g, "'").replace(/&#39;/g, "'").replace(/&amp;/g, "&");
71065
+ }
70557
71066
  function readDeclaredMentalModelNames(configText, agentName3) {
70558
71067
  if (!configText || !agentName3)
70559
71068
  return [];
@@ -70600,17 +71109,20 @@ function buildMentalModelAppendDiff(args) {
70600
71109
  detail: `agents.${agentName3} not present in config`
70601
71110
  };
70602
71111
  }
71112
+ const decodedName = decodeCanonicalEntities(spec.name);
70603
71113
  const existing = readDeclaredMentalModelNames(configText, agentName3);
70604
- if (existing.includes(spec.name)) {
71114
+ if (existing.includes(decodedName)) {
70605
71115
  return {
70606
71116
  ok: false,
70607
71117
  error: "duplicate",
70608
- detail: `mental model "${spec.name}" is already declared for ${agentName3}`
71118
+ detail: `mental model "${decodedName}" is already declared for ${agentName3}`
70609
71119
  };
70610
71120
  }
71121
+ const name = decodedName;
71122
+ const source_query = decodeCanonicalEntities(spec.source_query);
70611
71123
  const item = {
70612
- name: spec.name,
70613
- source_query: spec.source_query
71124
+ name,
71125
+ source_query
70614
71126
  };
70615
71127
  if (spec.refresh_after_consolidation !== undefined) {
70616
71128
  item.refresh_after_consolidation = spec.refresh_after_consolidation;
@@ -70897,27 +71409,27 @@ function buildSkillProposalApplyInbound(opts) {
70897
71409
  // ../src/self-improve/skill-proposals.ts
70898
71410
  import {
70899
71411
  closeSync as closeSync3,
70900
- existsSync as existsSync29,
70901
- mkdirSync as mkdirSync22,
71412
+ existsSync as existsSync31,
71413
+ mkdirSync as mkdirSync24,
70902
71414
  openSync as openSync3,
70903
- readFileSync as readFileSync26,
71415
+ readFileSync as readFileSync29,
70904
71416
  writeSync as writeSync3
70905
71417
  } from "node:fs";
70906
- import { join as join32 } from "node:path";
71418
+ import { join as join34 } from "node:path";
70907
71419
  import { randomUUID as randomUUID5 } from "node:crypto";
70908
71420
  var PROPOSALS_FILE = "skill-proposals.jsonl";
70909
71421
  var REJECTED_FILE = "skill-proposals-rejected.jsonl";
70910
71422
  var REJECTION_TTL_MS = 90 * 24 * 60 * 60 * 1000;
70911
71423
  var PROPOSAL_SIM_THRESHOLD = 0.5;
70912
71424
  function proposalsPath(stateDir) {
70913
- return join32(stateDir, PROPOSALS_FILE);
71425
+ return join34(stateDir, PROPOSALS_FILE);
70914
71426
  }
70915
71427
  function rejectedPath(stateDir) {
70916
- return join32(stateDir, REJECTED_FILE);
71428
+ return join34(stateDir, REJECTED_FILE);
70917
71429
  }
70918
71430
  function ensureDir2(stateDir) {
70919
- if (!existsSync29(stateDir)) {
70920
- mkdirSync22(stateDir, { recursive: true, mode: 493 });
71431
+ if (!existsSync31(stateDir)) {
71432
+ mkdirSync24(stateDir, { recursive: true, mode: 493 });
70921
71433
  }
70922
71434
  }
70923
71435
  function appendLine(path2, obj) {
@@ -70930,11 +71442,11 @@ function appendLine(path2, obj) {
70930
71442
  }
70931
71443
  }
70932
71444
  function readLines(path2, isValid2) {
70933
- if (!existsSync29(path2))
71445
+ if (!existsSync31(path2))
70934
71446
  return [];
70935
71447
  let raw;
70936
71448
  try {
70937
- raw = readFileSync26(path2, "utf-8");
71449
+ raw = readFileSync29(path2, "utf-8");
70938
71450
  } catch {
70939
71451
  return [];
70940
71452
  }
@@ -71563,11 +72075,11 @@ function escapeBody2(s) {
71563
72075
  }
71564
72076
 
71565
72077
  // gateway/pid-file.ts
71566
- import { writeFileSync as writeFileSync21, readFileSync as readFileSync27, unlinkSync as unlinkSync12, renameSync as renameSync8 } from "node:fs";
72078
+ import { writeFileSync as writeFileSync24, readFileSync as readFileSync30, unlinkSync as unlinkSync12, renameSync as renameSync9 } from "node:fs";
71567
72079
  function writePidFile(path2, record) {
71568
72080
  const tmp = `${path2}.tmp-${process.pid}-${Date.now()}`;
71569
- writeFileSync21(tmp, JSON.stringify(record), "utf-8");
71570
- renameSync8(tmp, path2);
72081
+ writeFileSync24(tmp, JSON.stringify(record), "utf-8");
72082
+ renameSync9(tmp, path2);
71571
72083
  }
71572
72084
  function clearPidFile(path2) {
71573
72085
  try {
@@ -71582,10 +72094,10 @@ import {
71582
72094
  writeFile as writeFileAsync,
71583
72095
  readFile as readFileAsync
71584
72096
  } from "node:fs/promises";
71585
- import { readFileSync as readFileSync28 } from "node:fs";
72097
+ import { readFileSync as readFileSync31 } from "node:fs";
71586
72098
  function readCurrentBootId() {
71587
72099
  try {
71588
- const stat = readFileSync28("/proc/1/stat", "utf-8");
72100
+ const stat = readFileSync31("/proc/1/stat", "utf-8");
71589
72101
  const lastParen = stat.lastIndexOf(")");
71590
72102
  if (lastParen < 0)
71591
72103
  return null;
@@ -71788,15 +72300,15 @@ function safeCount(fn) {
71788
72300
  }
71789
72301
 
71790
72302
  // gateway/session-marker.ts
71791
- import { writeFileSync as writeFileSync22, readFileSync as readFileSync29, renameSync as renameSync9, unlinkSync as unlinkSync13 } from "node:fs";
72303
+ import { writeFileSync as writeFileSync25, readFileSync as readFileSync32, renameSync as renameSync10, unlinkSync as unlinkSync13 } from "node:fs";
71792
72304
  function writeSessionMarker(path2, marker) {
71793
72305
  const tmp = `${path2}.tmp-${process.pid}-${Date.now()}`;
71794
- writeFileSync22(tmp, JSON.stringify(marker), "utf-8");
71795
- renameSync9(tmp, path2);
72306
+ writeFileSync25(tmp, JSON.stringify(marker), "utf-8");
72307
+ renameSync10(tmp, path2);
71796
72308
  }
71797
72309
  function readSessionMarker(path2) {
71798
72310
  try {
71799
- const raw = readFileSync29(path2, "utf-8");
72311
+ const raw = readFileSync32(path2, "utf-8");
71800
72312
  const parsed = JSON.parse(raw);
71801
72313
  if (typeof parsed.pid === "number" && typeof parsed.startedAtMs === "number" && Number.isFinite(parsed.pid) && Number.isFinite(parsed.startedAtMs)) {
71802
72314
  return { pid: parsed.pid, startedAtMs: parsed.startedAtMs };
@@ -71818,16 +72330,16 @@ function shouldFireRestartBanner(input) {
71818
72330
  }
71819
72331
 
71820
72332
  // gateway/clean-shutdown-marker.ts
71821
- import { writeFileSync as writeFileSync23, readFileSync as readFileSync30, renameSync as renameSync10, unlinkSync as unlinkSync14 } from "node:fs";
72333
+ import { writeFileSync as writeFileSync26, readFileSync as readFileSync33, renameSync as renameSync11, unlinkSync as unlinkSync14 } from "node:fs";
71822
72334
  var DEFAULT_MAX_AGE_MS = 60000;
71823
72335
  function writeCleanShutdownMarker(path2, marker) {
71824
72336
  const tmp = `${path2}.tmp-${process.pid}-${Date.now()}`;
71825
- writeFileSync23(tmp, JSON.stringify(marker), "utf-8");
71826
- renameSync10(tmp, path2);
72337
+ writeFileSync26(tmp, JSON.stringify(marker), "utf-8");
72338
+ renameSync11(tmp, path2);
71827
72339
  }
71828
72340
  function readCleanShutdownMarker(path2) {
71829
72341
  try {
71830
- const raw = readFileSync30(path2, "utf-8");
72342
+ const raw = readFileSync33(path2, "utf-8");
71831
72343
  const parsed = JSON.parse(raw);
71832
72344
  if (typeof parsed.ts === "number" && Number.isFinite(parsed.ts) && typeof parsed.signal === "string" && parsed.signal.length > 0) {
71833
72345
  const out = { ts: parsed.ts, signal: parsed.signal };
@@ -72433,16 +72945,16 @@ function classifyAdminGate(text4, myAgentName) {
72433
72945
 
72434
72946
  // subagent-watcher.ts
72435
72947
  import {
72436
- existsSync as existsSync31,
72948
+ existsSync as existsSync33,
72437
72949
  openSync as openSync5,
72438
72950
  readSync,
72439
72951
  statSync as statSync10,
72440
72952
  closeSync as closeSync5,
72441
72953
  watch,
72442
72954
  readdirSync as readdirSync6,
72443
- readFileSync as readFileSync32
72955
+ readFileSync as readFileSync35
72444
72956
  } from "fs";
72445
- import { join as join34 } from "path";
72957
+ import { join as join36 } from "path";
72446
72958
 
72447
72959
  // operator-events.ts
72448
72960
  function classifyClaudeError(raw) {
@@ -72922,20 +73434,20 @@ function recordNestedSubagentDispatch(db2, args) {
72922
73434
  // gateway/turn-active-marker.ts
72923
73435
  import {
72924
73436
  closeSync as closeSync4,
72925
- existsSync as existsSync30,
72926
- mkdirSync as mkdirSync23,
73437
+ existsSync as existsSync32,
73438
+ mkdirSync as mkdirSync25,
72927
73439
  openSync as openSync4,
72928
- readFileSync as readFileSync31,
73440
+ readFileSync as readFileSync34,
72929
73441
  statSync as statSync9,
72930
73442
  unlinkSync as unlinkSync15,
72931
73443
  utimesSync,
72932
- writeFileSync as writeFileSync24
73444
+ writeFileSync as writeFileSync27
72933
73445
  } from "node:fs";
72934
- import { join as join33 } from "node:path";
73446
+ import { join as join35 } from "node:path";
72935
73447
  var TURN_ACTIVE_MARKER_FILE = "turn-active.json";
72936
73448
  function touchTurnActiveMarker(stateDir) {
72937
- const path2 = join33(stateDir, TURN_ACTIVE_MARKER_FILE);
72938
- if (!existsSync30(path2))
73449
+ const path2 = join35(stateDir, TURN_ACTIVE_MARKER_FILE);
73450
+ if (!existsSync32(path2))
72939
73451
  return;
72940
73452
  const now = new Date;
72941
73453
  try {
@@ -72977,7 +73489,7 @@ function backfillJsonlAgentId(db2, jsonlPath, agentId, log) {
72977
73489
  const metaPath = jsonlPath.replace(/\.jsonl$/, ".meta.json");
72978
73490
  let meta;
72979
73491
  try {
72980
- const raw = readFileSync32(metaPath, "utf8");
73492
+ const raw = readFileSync35(metaPath, "utf8");
72981
73493
  meta = JSON.parse(raw);
72982
73494
  } catch {
72983
73495
  log?.(`subagent-watcher: backfill skip ${agentId} \u2014 meta.json not readable at ${metaPath}`);
@@ -73325,7 +73837,7 @@ function startSubagentWatcher(config) {
73325
73837
  clearTimeout(ref.ref);
73326
73838
  });
73327
73839
  const fs2 = config.fs ?? {
73328
- existsSync: existsSync31,
73840
+ existsSync: existsSync33,
73329
73841
  readdirSync: readdirSync6,
73330
73842
  statSync: statSync10,
73331
73843
  openSync: openSync5,
@@ -73623,8 +74135,8 @@ function startSubagentWatcher(config) {
73623
74135
  function rescanSubagentDirs() {
73624
74136
  if (stopped)
73625
74137
  return;
73626
- const claudeHome = join34(agentDir, ".claude");
73627
- const projectsRoot = join34(claudeHome, "projects");
74138
+ const claudeHome = join36(agentDir, ".claude");
74139
+ const projectsRoot = join36(claudeHome, "projects");
73628
74140
  if (!fs2.existsSync(projectsRoot))
73629
74141
  return;
73630
74142
  let projectDirs;
@@ -73658,7 +74170,7 @@ function startSubagentWatcher(config) {
73658
74170
  continue;
73659
74171
  }
73660
74172
  warnedForeignSlugs.delete(pDir);
73661
- const projectPath = join34(projectsRoot, pDir);
74173
+ const projectPath = join36(projectsRoot, pDir);
73662
74174
  let sessionDirs;
73663
74175
  try {
73664
74176
  sessionDirs = fs2.readdirSync(projectPath);
@@ -73668,7 +74180,7 @@ function startSubagentWatcher(config) {
73668
74180
  for (const sDir of sessionDirs) {
73669
74181
  if (sDir.endsWith(".jsonl"))
73670
74182
  continue;
73671
- const subagentsPath = join34(projectPath, sDir, "subagents");
74183
+ const subagentsPath = join36(projectPath, sDir, "subagents");
73672
74184
  if (!fs2.existsSync(subagentsPath))
73673
74185
  continue;
73674
74186
  const watchAndScan = (dirPath) => {
@@ -73677,7 +74189,7 @@ function startSubagentWatcher(config) {
73677
74189
  const w = fs2.watch(dirPath, (_event, filename) => {
73678
74190
  if (!filename || !filename.toString().startsWith("agent-") || !filename.toString().endsWith(".jsonl"))
73679
74191
  return;
73680
- const filePath = join34(dirPath, filename.toString());
74192
+ const filePath = join36(dirPath, filename.toString());
73681
74193
  if (!knownFiles.has(filePath)) {
73682
74194
  scanSubagentsDir(dirPath);
73683
74195
  }
@@ -73691,7 +74203,7 @@ function startSubagentWatcher(config) {
73691
74203
  scanSubagentsDir(dirPath);
73692
74204
  };
73693
74205
  watchAndScan(subagentsPath);
73694
- const workflowsPath = join34(subagentsPath, "workflows");
74206
+ const workflowsPath = join36(subagentsPath, "workflows");
73695
74207
  if (fs2.existsSync(workflowsPath)) {
73696
74208
  let wfDirs;
73697
74209
  try {
@@ -73701,7 +74213,7 @@ function startSubagentWatcher(config) {
73701
74213
  }
73702
74214
  for (const wfDir of wfDirs) {
73703
74215
  try {
73704
- const wfPath = join34(workflowsPath, wfDir);
74216
+ const wfPath = join36(workflowsPath, wfDir);
73705
74217
  if (!fs2.statSync(wfPath).isDirectory())
73706
74218
  continue;
73707
74219
  watchAndScan(wfPath);
@@ -73721,7 +74233,7 @@ function startSubagentWatcher(config) {
73721
74233
  for (const e of entries) {
73722
74234
  if (!e.startsWith("agent-") || !e.endsWith(".jsonl"))
73723
74235
  continue;
73724
- const filePath = join34(subagentsPath, e);
74236
+ const filePath = join36(subagentsPath, e);
73725
74237
  if (knownFiles.has(filePath))
73726
74238
  continue;
73727
74239
  const agentId = e.slice("agent-".length, -".jsonl".length);
@@ -73815,29 +74327,29 @@ function startSubagentWatcher(config) {
73815
74327
 
73816
74328
  // ../src/worktree/registry.ts
73817
74329
  import {
73818
- mkdirSync as mkdirSync24,
73819
- writeFileSync as writeFileSync25,
73820
- readFileSync as readFileSync33,
74330
+ mkdirSync as mkdirSync26,
74331
+ writeFileSync as writeFileSync28,
74332
+ readFileSync as readFileSync36,
73821
74333
  readdirSync as readdirSync7,
73822
74334
  unlinkSync as unlinkSync16,
73823
- existsSync as existsSync32,
73824
- renameSync as renameSync11
74335
+ existsSync as existsSync34,
74336
+ renameSync as renameSync12
73825
74337
  } from "node:fs";
73826
- import { join as join35, resolve as resolve8 } from "node:path";
74338
+ import { join as join37, resolve as resolve8 } from "node:path";
73827
74339
  import { homedir as homedir12 } from "node:os";
73828
74340
  function registryDir() {
73829
- return resolve8(process.env.SWITCHROOM_WORKTREE_DIR ?? join35(homedir12(), ".switchroom", "worktrees"));
74341
+ return resolve8(process.env.SWITCHROOM_WORKTREE_DIR ?? join37(homedir12(), ".switchroom", "worktrees"));
73830
74342
  }
73831
74343
  function recordPath(id) {
73832
- return join35(registryDir(), `${id}.json`);
74344
+ return join37(registryDir(), `${id}.json`);
73833
74345
  }
73834
74346
  function ensureDir3() {
73835
- mkdirSync24(registryDir(), { recursive: true });
74347
+ mkdirSync26(registryDir(), { recursive: true });
73836
74348
  }
73837
74349
  function readRecord(id) {
73838
74350
  const path2 = recordPath(id);
73839
74351
  try {
73840
- const raw = readFileSync33(path2, "utf8");
74352
+ const raw = readFileSync36(path2, "utf8");
73841
74353
  return JSON.parse(raw);
73842
74354
  } catch {
73843
74355
  return null;
@@ -73930,15 +74442,15 @@ function determineRestartReason(opts) {
73930
74442
  init_boot_card();
73931
74443
 
73932
74444
  // gateway/update-announce.ts
73933
- import { existsSync as existsSync37, mkdirSync as mkdirSync28, openSync as openSync6, closeSync as closeSync6, readFileSync as readFileSync39 } from "node:fs";
73934
- import { join as join40 } from "node:path";
74445
+ import { existsSync as existsSync39, mkdirSync as mkdirSync30, openSync as openSync6, closeSync as closeSync6, readFileSync as readFileSync42 } from "node:fs";
74446
+ import { join as join42 } from "node:path";
73935
74447
  import { homedir as homedir14 } from "node:os";
73936
74448
 
73937
74449
  // ../src/host-control/audit-reader.ts
73938
74450
  import { homedir as homedir13 } from "node:os";
73939
- import { join as join39 } from "node:path";
74451
+ import { join as join41 } from "node:path";
73940
74452
  function defaultAuditLogPath(home2 = homedir13()) {
73941
- return join39(home2, ".switchroom", "host-control-audit.log");
74453
+ return join41(home2, ".switchroom", "host-control-audit.log");
73942
74454
  }
73943
74455
  function parseAuditLine(line) {
73944
74456
  const trimmed = line.trim();
@@ -74059,8 +74571,8 @@ function readAndFilter(raw, filters, limit) {
74059
74571
  var DEFAULT_LOOKBACK_MS = 10 * 60 * 1000;
74060
74572
  function readLastTerminalUpdateAudit(opts = {}) {
74061
74573
  const path2 = opts.auditLogPath ?? defaultAuditLogPath();
74062
- const exists = opts.exists ?? existsSync37;
74063
- const readFile = opts.readFile ?? ((p) => readFileSync39(p, "utf-8"));
74574
+ const exists = opts.exists ?? existsSync39;
74575
+ const readFile = opts.readFile ?? ((p) => readFileSync42(p, "utf-8"));
74064
74576
  if (!exists(path2))
74065
74577
  return null;
74066
74578
  let raw;
@@ -74121,15 +74633,15 @@ function renderUpdateOutcomeLine(entry) {
74121
74633
  `);
74122
74634
  }
74123
74635
  function claimUpdateAnnouncement(requestId, opts = {}) {
74124
- const stateDir = opts.stateDir ?? process.env.TELEGRAM_STATE_DIR ?? join40(homedir14(), ".switchroom");
74125
- const dir = join40(stateDir, "update-announced");
74636
+ const stateDir = opts.stateDir ?? process.env.TELEGRAM_STATE_DIR ?? join42(homedir14(), ".switchroom");
74637
+ const dir = join42(stateDir, "update-announced");
74126
74638
  try {
74127
- mkdirSync28(dir, { recursive: true });
74639
+ mkdirSync30(dir, { recursive: true });
74128
74640
  } catch {
74129
74641
  return false;
74130
74642
  }
74131
74643
  const safeId = requestId.replace(/[^A-Za-z0-9_.-]/g, "_").slice(0, 200);
74132
- const path2 = join40(dir, safeId);
74644
+ const path2 = join42(dir, safeId);
74133
74645
  try {
74134
74646
  const fd = openSync6(path2, "wx");
74135
74647
  closeSync6(fd);
@@ -74149,7 +74661,7 @@ function maybeRenderUpdateAnnouncement(opts = {}) {
74149
74661
 
74150
74662
  // issues-card.ts
74151
74663
  init_card_format();
74152
- import { readFileSync as readFileSync40, writeFileSync as writeFileSync30 } from "node:fs";
74664
+ import { readFileSync as readFileSync43, writeFileSync as writeFileSync33 } from "node:fs";
74153
74665
  var SEVERITY_EMOJI = {
74154
74666
  info: "\u2139\ufe0f",
74155
74667
  warn: "\u26a0\ufe0f",
@@ -74241,7 +74753,7 @@ function extractRetryAfterSecs2(err) {
74241
74753
  var COOLDOWN_JITTER_MS2 = 500;
74242
74754
  function readPersistedMessageId(path2, log) {
74243
74755
  try {
74244
- const raw = readFileSync40(path2, "utf8");
74756
+ const raw = readFileSync43(path2, "utf8");
74245
74757
  const parsed = JSON.parse(raw);
74246
74758
  const v = parsed.messageId;
74247
74759
  if (typeof v === "number" && Number.isInteger(v) && v > 0)
@@ -74257,7 +74769,7 @@ function readPersistedMessageId(path2, log) {
74257
74769
  }
74258
74770
  function writePersistedMessageId(path2, messageId, log) {
74259
74771
  try {
74260
- writeFileSync30(path2, JSON.stringify({ messageId }) + `
74772
+ writeFileSync33(path2, JSON.stringify({ messageId }) + `
74261
74773
  `, { mode: 384 });
74262
74774
  } catch (err) {
74263
74775
  log(`issues-card: persist write failed (${err.message})`);
@@ -74350,24 +74862,24 @@ function createIssuesCardHandle(opts) {
74350
74862
  }
74351
74863
 
74352
74864
  // issues-watcher.ts
74353
- import { existsSync as existsSync39, statSync as statSync12 } from "node:fs";
74354
- import { join as join42 } from "node:path";
74865
+ import { existsSync as existsSync41, statSync as statSync12 } from "node:fs";
74866
+ import { join as join44 } from "node:path";
74355
74867
 
74356
74868
  // ../src/issues/store.ts
74357
74869
  import {
74358
74870
  closeSync as closeSync7,
74359
- existsSync as existsSync38,
74360
- mkdirSync as mkdirSync29,
74871
+ existsSync as existsSync40,
74872
+ mkdirSync as mkdirSync31,
74361
74873
  openSync as openSync7,
74362
74874
  readdirSync as readdirSync9,
74363
- readFileSync as readFileSync41,
74364
- renameSync as renameSync14,
74875
+ readFileSync as readFileSync44,
74876
+ renameSync as renameSync15,
74365
74877
  statSync as statSync11,
74366
74878
  unlinkSync as unlinkSync17,
74367
- writeFileSync as writeFileSync31,
74879
+ writeFileSync as writeFileSync34,
74368
74880
  writeSync as writeSync4
74369
74881
  } from "node:fs";
74370
- import { join as join41 } from "node:path";
74882
+ import { join as join43 } from "node:path";
74371
74883
  import { randomBytes as randomBytes6 } from "node:crypto";
74372
74884
  import { execSync } from "node:child_process";
74373
74885
 
@@ -74386,12 +74898,12 @@ init_redact();
74386
74898
  var ISSUES_FILE = "issues.jsonl";
74387
74899
  var ISSUES_LOCK = "issues.lock";
74388
74900
  function readAll(stateDir) {
74389
- const path2 = join41(stateDir, ISSUES_FILE);
74390
- if (!existsSync38(path2))
74901
+ const path2 = join43(stateDir, ISSUES_FILE);
74902
+ if (!existsSync40(path2))
74391
74903
  return [];
74392
74904
  let raw;
74393
74905
  try {
74394
- raw = readFileSync41(path2, "utf-8");
74906
+ raw = readFileSync44(path2, "utf-8");
74395
74907
  } catch {
74396
74908
  return [];
74397
74909
  }
@@ -74423,7 +74935,7 @@ function list2(stateDir, opts = {}) {
74423
74935
  });
74424
74936
  }
74425
74937
  function resolve9(stateDir, fingerprint, nowFn = Date.now) {
74426
- if (!existsSync38(join41(stateDir, ISSUES_FILE)))
74938
+ if (!existsSync40(join43(stateDir, ISSUES_FILE)))
74427
74939
  return 0;
74428
74940
  return withLock(stateDir, () => {
74429
74941
  const all2 = readAll(stateDir);
@@ -74441,14 +74953,14 @@ function resolve9(stateDir, fingerprint, nowFn = Date.now) {
74441
74953
  });
74442
74954
  }
74443
74955
  function writeAll(stateDir, events) {
74444
- const path2 = join41(stateDir, ISSUES_FILE);
74956
+ const path2 = join43(stateDir, ISSUES_FILE);
74445
74957
  sweepOrphanTmpFiles(stateDir);
74446
74958
  const tmp = `${path2}.tmp-${process.pid}-${randomBytes6(4).toString("hex")}`;
74447
74959
  const body = events.length === 0 ? "" : events.map((e) => JSON.stringify(e)).join(`
74448
74960
  `) + `
74449
74961
  `;
74450
- writeFileSync31(tmp, body, "utf-8");
74451
- renameSync14(tmp, path2);
74962
+ writeFileSync34(tmp, body, "utf-8");
74963
+ renameSync15(tmp, path2);
74452
74964
  }
74453
74965
  var ORPHAN_TMP_TTL_MS = 60000;
74454
74966
  var TMP_PREFIX = `${ISSUES_FILE}.tmp-`;
@@ -74463,7 +74975,7 @@ function sweepOrphanTmpFiles(stateDir) {
74463
74975
  for (const entry of entries) {
74464
74976
  if (!entry.startsWith(TMP_PREFIX))
74465
74977
  continue;
74466
- const tmpPath2 = join41(stateDir, entry);
74978
+ const tmpPath2 = join43(stateDir, entry);
74467
74979
  try {
74468
74980
  const stat = statSync11(tmpPath2);
74469
74981
  if (stat.mtimeMs < cutoff) {
@@ -74475,7 +74987,7 @@ function sweepOrphanTmpFiles(stateDir) {
74475
74987
  var LOCK_RETRY_MS = 25;
74476
74988
  var LOCK_TIMEOUT_MS = 1e4;
74477
74989
  function withLock(stateDir, fn) {
74478
- const lockPath = join41(stateDir, ISSUES_LOCK);
74990
+ const lockPath = join43(stateDir, ISSUES_LOCK);
74479
74991
  const startedAt = Date.now();
74480
74992
  let fd = null;
74481
74993
  while (fd === null) {
@@ -74510,7 +75022,7 @@ function withLock(stateDir, fn) {
74510
75022
  function tryStealStaleLock(lockPath) {
74511
75023
  let pidStr;
74512
75024
  try {
74513
- pidStr = readFileSync41(lockPath, "utf-8").trim();
75025
+ pidStr = readFileSync44(lockPath, "utf-8").trim();
74514
75026
  } catch {
74515
75027
  return true;
74516
75028
  }
@@ -74560,7 +75072,7 @@ function isIssueEvent(v) {
74560
75072
  // issues-watcher.ts
74561
75073
  var DEFAULT_POLL_INTERVAL_MS2 = 2000;
74562
75074
  function startIssuesWatcher(opts) {
74563
- const path2 = join42(opts.stateDir, ISSUES_FILE);
75075
+ const path2 = join44(opts.stateDir, ISSUES_FILE);
74564
75076
  const log = opts.log ?? (() => {});
74565
75077
  const intervalMs = opts.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS2;
74566
75078
  const setIntervalFn = opts.setInterval ?? setInterval;
@@ -74608,7 +75120,7 @@ function startIssuesWatcher(opts) {
74608
75120
  };
74609
75121
  }
74610
75122
  function defaultSignatureProvider(path2) {
74611
- if (!existsSync39(path2))
75123
+ if (!existsSync41(path2))
74612
75124
  return null;
74613
75125
  try {
74614
75126
  const stat = statSync12(path2);
@@ -75364,8 +75876,8 @@ function readBashCommand(inputPreview) {
75364
75876
  }
75365
75877
 
75366
75878
  // gateway/scoped-grant-store.ts
75367
- import { readFileSync as readFileSync42, writeFileSync as writeFileSync32 } from "node:fs";
75368
- import { join as join43 } from "node:path";
75879
+ import { readFileSync as readFileSync45, writeFileSync as writeFileSync35 } from "node:fs";
75880
+ import { join as join45 } from "node:path";
75369
75881
 
75370
75882
  // scoped-approval.ts
75371
75883
  var SCOPED_APPROVAL_DEFAULT_TTL_MS2 = 30 * 60 * 1000;
@@ -75407,11 +75919,11 @@ function scopedGrantPersistEnabled(env = process.env) {
75407
75919
  return env.SWITCHROOM_SCOPED_GRANT_PERSIST !== "0";
75408
75920
  }
75409
75921
  function createScopedGrantStore(stateDir, env = process.env) {
75410
- const filePath = join43(stateDir, "scoped-grants.json");
75922
+ const filePath = join45(stateDir, "scoped-grants.json");
75411
75923
  const enabled8 = scopedGrantPersistEnabled(env);
75412
75924
  function read() {
75413
75925
  try {
75414
- const raw = readFileSync42(filePath, "utf-8");
75926
+ const raw = readFileSync45(filePath, "utf-8");
75415
75927
  const parsed = JSON.parse(raw);
75416
75928
  return Array.isArray(parsed) ? parsed : [];
75417
75929
  } catch {
@@ -75429,7 +75941,7 @@ function createScopedGrantStore(stateDir, env = process.env) {
75429
75941
  if (!enabled8)
75430
75942
  return;
75431
75943
  try {
75432
- writeFileSync32(filePath, JSON.stringify(serializeScopedGrants(store2)), {
75944
+ writeFileSync35(filePath, JSON.stringify(serializeScopedGrants(store2)), {
75433
75945
  encoding: "utf-8",
75434
75946
  mode: 384
75435
75947
  });
@@ -75763,8 +76275,8 @@ function extractFlowItems(line) {
75763
76275
 
75764
76276
  // credits-watch.ts
75765
76277
  init_card_format();
75766
- import { readFileSync as readFileSync43, writeFileSync as writeFileSync33, existsSync as existsSync40, mkdirSync as mkdirSync30 } from "fs";
75767
- import { join as join44 } from "path";
76278
+ import { readFileSync as readFileSync46, writeFileSync as writeFileSync36, existsSync as existsSync42, mkdirSync as mkdirSync32 } from "fs";
76279
+ import { join as join46 } from "path";
75768
76280
  var STATE_FILE = "credits-watch.json";
75769
76281
  var DEFAULT_CREDIT_FATAL_REASONS = new Set;
75770
76282
  var KNOWN_CREDIT_REASONS = [
@@ -75786,12 +76298,12 @@ function emptyCreditState() {
75786
76298
  return { lastNotifiedReason: null, lastNotifiedAt: 0 };
75787
76299
  }
75788
76300
  function readClaudeJsonOverage(claudeConfigDir) {
75789
- const path2 = join44(claudeConfigDir, ".claude.json");
75790
- if (!existsSync40(path2))
76301
+ const path2 = join46(claudeConfigDir, ".claude.json");
76302
+ if (!existsSync42(path2))
75791
76303
  return null;
75792
76304
  let raw;
75793
76305
  try {
75794
- raw = readFileSync43(path2, "utf-8");
76306
+ raw = readFileSync46(path2, "utf-8");
75795
76307
  } catch {
75796
76308
  return null;
75797
76309
  }
@@ -75869,11 +76381,11 @@ function humanizeReason(reason) {
75869
76381
  }
75870
76382
  }
75871
76383
  function loadCreditState(stateDir) {
75872
- const path2 = join44(stateDir, STATE_FILE);
75873
- if (!existsSync40(path2))
76384
+ const path2 = join46(stateDir, STATE_FILE);
76385
+ if (!existsSync42(path2))
75874
76386
  return emptyCreditState();
75875
76387
  try {
75876
- const raw = readFileSync43(path2, "utf-8");
76388
+ const raw = readFileSync46(path2, "utf-8");
75877
76389
  const parsed = JSON.parse(raw);
75878
76390
  if (parsed && typeof parsed === "object" && (parsed.lastNotifiedReason === null || typeof parsed.lastNotifiedReason === "string") && typeof parsed.lastNotifiedAt === "number" && Number.isFinite(parsed.lastNotifiedAt)) {
75879
76391
  return {
@@ -75885,17 +76397,17 @@ function loadCreditState(stateDir) {
75885
76397
  return emptyCreditState();
75886
76398
  }
75887
76399
  function saveCreditState(stateDir, state4) {
75888
- mkdirSync30(stateDir, { recursive: true });
75889
- const path2 = join44(stateDir, STATE_FILE);
75890
- writeFileSync33(path2, JSON.stringify(state4, null, 2) + `
76400
+ mkdirSync32(stateDir, { recursive: true });
76401
+ const path2 = join46(stateDir, STATE_FILE);
76402
+ writeFileSync36(path2, JSON.stringify(state4, null, 2) + `
75891
76403
  `, { mode: 384 });
75892
76404
  }
75893
76405
 
75894
76406
  // quota-watch.ts
75895
76407
  init_auth_snapshot_format();
75896
76408
  init_card_format();
75897
- import { readFileSync as readFileSync44, writeFileSync as writeFileSync34, existsSync as existsSync41, mkdirSync as mkdirSync31 } from "fs";
75898
- import { join as join45 } from "path";
76409
+ import { readFileSync as readFileSync47, writeFileSync as writeFileSync37, existsSync as existsSync43, mkdirSync as mkdirSync33 } from "fs";
76410
+ import { join as join47 } from "path";
75899
76411
  var STATE_FILE2 = "quota-watch.json";
75900
76412
  function emptyQuotaWatchState() {
75901
76413
  return {};
@@ -76088,11 +76600,11 @@ function buildRecoveryMessage(agentName3, snap) {
76088
76600
  `);
76089
76601
  }
76090
76602
  function loadQuotaWatchState(stateDir) {
76091
- const path2 = join45(stateDir, STATE_FILE2);
76092
- if (!existsSync41(path2))
76603
+ const path2 = join47(stateDir, STATE_FILE2);
76604
+ if (!existsSync43(path2))
76093
76605
  return emptyQuotaWatchState();
76094
76606
  try {
76095
- const raw = readFileSync44(path2, "utf-8");
76607
+ const raw = readFileSync47(path2, "utf-8");
76096
76608
  const parsed = JSON.parse(raw);
76097
76609
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
76098
76610
  return emptyQuotaWatchState();
@@ -76109,9 +76621,9 @@ function loadQuotaWatchState(stateDir) {
76109
76621
  }
76110
76622
  }
76111
76623
  function saveQuotaWatchState(stateDir, state4) {
76112
- mkdirSync31(stateDir, { recursive: true });
76113
- const path2 = join45(stateDir, STATE_FILE2);
76114
- writeFileSync34(path2, JSON.stringify(state4, null, 2) + `
76624
+ mkdirSync33(stateDir, { recursive: true });
76625
+ const path2 = join47(stateDir, STATE_FILE2);
76626
+ writeFileSync37(path2, JSON.stringify(state4, null, 2) + `
76115
76627
  `, { mode: 384 });
76116
76628
  }
76117
76629
  function patchQuotaWatchState(current, accountLabel, accountState) {
@@ -76141,27 +76653,27 @@ function maskVaultKey(name) {
76141
76653
  // gateway/turn-active-marker.ts
76142
76654
  import {
76143
76655
  closeSync as closeSync8,
76144
- existsSync as existsSync42,
76145
- mkdirSync as mkdirSync32,
76656
+ existsSync as existsSync44,
76657
+ mkdirSync as mkdirSync34,
76146
76658
  openSync as openSync8,
76147
- readFileSync as readFileSync45,
76659
+ readFileSync as readFileSync48,
76148
76660
  statSync as statSync13,
76149
76661
  unlinkSync as unlinkSync18,
76150
76662
  utimesSync as utimesSync2,
76151
- writeFileSync as writeFileSync35
76663
+ writeFileSync as writeFileSync38
76152
76664
  } from "node:fs";
76153
- import { join as join46 } from "node:path";
76665
+ import { join as join48 } from "node:path";
76154
76666
  var TURN_ACTIVE_MARKER_FILE2 = "turn-active.json";
76155
76667
  function writeTurnActiveMarker(stateDir, marker) {
76156
76668
  try {
76157
- mkdirSync32(stateDir, { recursive: true });
76158
- writeFileSync35(join46(stateDir, TURN_ACTIVE_MARKER_FILE2), JSON.stringify(marker, null, 2) + `
76669
+ mkdirSync34(stateDir, { recursive: true });
76670
+ writeFileSync38(join48(stateDir, TURN_ACTIVE_MARKER_FILE2), JSON.stringify(marker, null, 2) + `
76159
76671
  `, { mode: 384 });
76160
76672
  } catch {}
76161
76673
  }
76162
76674
  function touchTurnActiveMarker2(stateDir) {
76163
- const path2 = join46(stateDir, TURN_ACTIVE_MARKER_FILE2);
76164
- if (!existsSync42(path2))
76675
+ const path2 = join48(stateDir, TURN_ACTIVE_MARKER_FILE2);
76676
+ if (!existsSync44(path2))
76165
76677
  return;
76166
76678
  const now = new Date;
76167
76679
  try {
@@ -76175,12 +76687,12 @@ function touchTurnActiveMarker2(stateDir) {
76175
76687
  }
76176
76688
  function removeTurnActiveMarker(stateDir) {
76177
76689
  try {
76178
- unlinkSync18(join46(stateDir, TURN_ACTIVE_MARKER_FILE2));
76690
+ unlinkSync18(join48(stateDir, TURN_ACTIVE_MARKER_FILE2));
76179
76691
  } catch {}
76180
76692
  }
76181
76693
  function sweepStaleTurnActiveMarker(stateDir, opts) {
76182
- const path2 = join46(stateDir, TURN_ACTIVE_MARKER_FILE2);
76183
- if (!existsSync42(path2))
76694
+ const path2 = join48(stateDir, TURN_ACTIVE_MARKER_FILE2);
76695
+ if (!existsSync44(path2))
76184
76696
  return false;
76185
76697
  const now = opts.now ?? Date.now();
76186
76698
  try {
@@ -76192,7 +76704,7 @@ function sweepStaleTurnActiveMarker(stateDir, opts) {
76192
76704
  return false;
76193
76705
  let payload = null;
76194
76706
  try {
76195
- payload = readFileSync45(path2, "utf8");
76707
+ payload = readFileSync48(path2, "utf8");
76196
76708
  } catch {}
76197
76709
  unlinkSync18(path2);
76198
76710
  if (opts.onRemove) {
@@ -76210,7 +76722,7 @@ function sweepStaleTurnActiveMarker(stateDir, opts) {
76210
76722
  }
76211
76723
  }
76212
76724
  function readTurnActiveMarkerAgeMs(stateDir, now) {
76213
- const path2 = join46(stateDir, TURN_ACTIVE_MARKER_FILE2);
76725
+ const path2 = join48(stateDir, TURN_ACTIVE_MARKER_FILE2);
76214
76726
  try {
76215
76727
  const st = statSync13(path2);
76216
76728
  return (now ?? Date.now()) - st.mtimeMs;
@@ -76220,10 +76732,10 @@ function readTurnActiveMarkerAgeMs(stateDir, now) {
76220
76732
  }
76221
76733
 
76222
76734
  // ../src/build-info.ts
76223
- var VERSION = "0.18.7";
76224
- var COMMIT_SHA = "21478348";
76225
- var COMMIT_DATE = "2026-07-10T08:22:23Z";
76226
- var LATEST_PR = 2990;
76735
+ var VERSION = "0.18.8";
76736
+ var COMMIT_SHA = "9255fe22";
76737
+ var COMMIT_DATE = "2026-07-10T12:33:35Z";
76738
+ var LATEST_PR = 3004;
76227
76739
  var COMMITS_AHEAD_OF_TAG = 0;
76228
76740
 
76229
76741
  // gateway/boot-version.ts
@@ -76297,11 +76809,11 @@ init_peercred();
76297
76809
  import * as net5 from "node:net";
76298
76810
  import * as fs2 from "node:fs";
76299
76811
  import { homedir as homedir15 } from "node:os";
76300
- import { join as join47 } from "node:path";
76812
+ import { join as join49 } from "node:path";
76301
76813
  var DEFAULT_TIMEOUT_MS4 = 2000;
76302
76814
  var UNLOCK_TIMEOUT_MS = 30000;
76303
- var LEGACY_SOCKET_PATH2 = join47(homedir15(), ".switchroom", "vault-broker.sock");
76304
- var OPERATOR_SOCKET_PATH2 = join47(homedir15(), ".switchroom", "broker-operator", "sock");
76815
+ var LEGACY_SOCKET_PATH2 = join49(homedir15(), ".switchroom", "vault-broker.sock");
76816
+ var OPERATOR_SOCKET_PATH2 = join49(homedir15(), ".switchroom", "broker-operator", "sock");
76305
76817
  function defaultBrokerSocketPath2() {
76306
76818
  if (fs2.existsSync(OPERATOR_SOCKET_PATH2))
76307
76819
  return OPERATOR_SOCKET_PATH2;
@@ -77231,8 +77743,8 @@ function matchesAdminOnlyKey(key, patterns) {
77231
77743
  }
77232
77744
 
77233
77745
  // registry/turns-schema.ts
77234
- import { chmodSync as chmodSync7, mkdirSync as mkdirSync33 } from "fs";
77235
- import { join as join48 } from "path";
77746
+ import { chmodSync as chmodSync7, mkdirSync as mkdirSync35 } from "fs";
77747
+ import { join as join50 } from "path";
77236
77748
  var DatabaseClass2 = null;
77237
77749
  function loadDatabaseClass2() {
77238
77750
  if (DatabaseClass2 != null)
@@ -77300,9 +77812,9 @@ function applySchema(db2) {
77300
77812
  }
77301
77813
  function openTurnsDb(agentDir) {
77302
77814
  const Database = loadDatabaseClass2();
77303
- const dir = join48(agentDir, "telegram");
77304
- mkdirSync33(dir, { recursive: true, mode: 448 });
77305
- const path2 = join48(dir, "registry.db");
77815
+ const dir = join50(agentDir, "telegram");
77816
+ mkdirSync35(dir, { recursive: true, mode: 448 });
77817
+ const path2 = join50(dir, "registry.db");
77306
77818
  const db2 = new Database(path2, { create: true });
77307
77819
  applySchema(db2);
77308
77820
  try {
@@ -77757,7 +78269,7 @@ installGlobalErrorHandlers();
77757
78269
  process.on("beforeExit", () => {
77758
78270
  shutdownAnalytics();
77759
78271
  });
77760
- var STATE_DIR = process.env.TELEGRAM_STATE_DIR ?? join49(homedir16(), ".claude", "channels", "telegram");
78272
+ var STATE_DIR = process.env.TELEGRAM_STATE_DIR ?? join51(homedir16(), ".claude", "channels", "telegram");
77761
78273
  var permCardStore = createPermissionCardStore(STATE_DIR);
77762
78274
  var pendingCardStore = createPendingCardStore(STATE_DIR);
77763
78275
  var missedApprovalsStore = createMissedApprovalsStore(STATE_DIR);
@@ -77776,7 +78288,7 @@ function alwaysAllowDrainDeps() {
77776
78288
  return {
77777
78289
  readConfigText: () => {
77778
78290
  const cfgPath = process.env.SWITCHROOM_CONFIG ?? SWITCHROOM_CONFIG ?? findConfigFile2();
77779
- return readFileSync47(cfgPath, "utf8");
78291
+ return readFileSync50(cfgPath, "utf8");
77780
78292
  },
77781
78293
  resolveAllowList: (_configText, agentName3) => {
77782
78294
  const cfg = loadConfig2();
@@ -77837,11 +78349,11 @@ function scheduleAlwaysAllowPersistDrain() {
77837
78349
  }, ALWAYS_ALLOW_DRAIN_INTERVAL_MS);
77838
78350
  timer3.unref?.();
77839
78351
  }
77840
- var ACCESS_FILE = join49(STATE_DIR, "access.json");
77841
- var APPROVED_DIR = join49(STATE_DIR, "approved");
77842
- var ENV_FILE = join49(STATE_DIR, ".env");
77843
- var INBOX_DIR = join49(STATE_DIR, "inbox");
77844
- var PEOPLE_FILE = join49(STATE_DIR, "people.json");
78352
+ var ACCESS_FILE = join51(STATE_DIR, "access.json");
78353
+ var APPROVED_DIR = join51(STATE_DIR, "approved");
78354
+ var ENV_FILE = join51(STATE_DIR, ".env");
78355
+ var INBOX_DIR = join51(STATE_DIR, "inbox");
78356
+ var PEOPLE_FILE = join51(STATE_DIR, "people.json");
77845
78357
  function triggerSelfRestart(targetAgent, reason, delayMs = 300) {
77846
78358
  const isDocker = process.env.SWITCHROOM_RUNTIME === "docker";
77847
78359
  const selfAgent = process.env.SWITCHROOM_AGENT_NAME;
@@ -77851,6 +78363,11 @@ function triggerSelfRestart(targetAgent, reason, delayMs = 300) {
77851
78363
  `);
77852
78364
  return false;
77853
78365
  }
78366
+ {
78367
+ const smDir = resolveAgentDirFromEnv();
78368
+ if (smDir)
78369
+ writeRelaunchModelIntent(smDir, intentForRestartReason(reason), reason);
78370
+ }
77854
78371
  process.stderr.write(`telegram gateway: restart-via-SIGTERM-PID1 agent=${targetAgent} reason=${reason} (docker)
77855
78372
  `);
77856
78373
  setTimeout(() => {
@@ -77863,6 +78380,11 @@ function triggerSelfRestart(targetAgent, reason, delayMs = 300) {
77863
78380
  }, delayMs).unref();
77864
78381
  return true;
77865
78382
  }
78383
+ if (targetAgent === selfAgent) {
78384
+ const smDir = resolveAgentDirFromEnv();
78385
+ if (smDir)
78386
+ writeRelaunchModelIntent(smDir, intentForRestartReason(reason), reason);
78387
+ }
77866
78388
  process.stderr.write(`telegram gateway: restart-via-systemctl agent=${targetAgent} reason=${reason}
77867
78389
  `);
77868
78390
  try {
@@ -77906,7 +78428,7 @@ function formatBootVersion() {
77906
78428
  }
77907
78429
  try {
77908
78430
  chmodSync8(ENV_FILE, 384);
77909
- for (const line of readFileSync47(ENV_FILE, "utf8").split(`
78431
+ for (const line of readFileSync50(ENV_FILE, "utf8").split(`
77910
78432
  `)) {
77911
78433
  const m = line.match(/^(\w+)=(.*)$/);
77912
78434
  if (m && process.env[m[1]] === undefined)
@@ -77971,7 +78493,7 @@ bot.api.config.use(async (prev, method, payload, signal) => {
77971
78493
  });
77972
78494
  var GRAMMY_VERSION = (() => {
77973
78495
  try {
77974
- const raw = readFileSync47(new URL("../../node_modules/grammy/package.json", import.meta.url), "utf8");
78496
+ const raw = readFileSync50(new URL("../../node_modules/grammy/package.json", import.meta.url), "utf8");
77975
78497
  return JSON.parse(raw).version ?? "unknown";
77976
78498
  } catch {
77977
78499
  return "unknown";
@@ -78038,7 +78560,7 @@ function assertSendable(f) {
78038
78560
  } catch {
78039
78561
  throw new Error(`refusing to send file \u2014 cannot resolve real path: ${f}`);
78040
78562
  }
78041
- const inbox = join49(stateReal, "inbox");
78563
+ const inbox = join51(stateReal, "inbox");
78042
78564
  if (real.startsWith(stateReal + sep3) && !real.startsWith(inbox + sep3)) {
78043
78565
  throw new Error(`refusing to send channel state: ${f}`);
78044
78566
  }
@@ -78057,7 +78579,7 @@ function assertSendable(f) {
78057
78579
  }
78058
78580
  function readAccessFile() {
78059
78581
  try {
78060
- const raw = readFileSync47(ACCESS_FILE, "utf8");
78582
+ const raw = readFileSync50(ACCESS_FILE, "utf8");
78061
78583
  const parsed = JSON.parse(raw);
78062
78584
  const allowFrom = validateStringArray("allowFrom", parsed.allowFrom ?? []);
78063
78585
  const groups = {};
@@ -78095,7 +78617,7 @@ function readAccessFile() {
78095
78617
  if (err.code === "ENOENT")
78096
78618
  return defaultAccess();
78097
78619
  try {
78098
- renameSync15(ACCESS_FILE, `${ACCESS_FILE}.corrupt-${Date.now()}`);
78620
+ renameSync16(ACCESS_FILE, `${ACCESS_FILE}.corrupt-${Date.now()}`);
78099
78621
  } catch {}
78100
78622
  process.stderr.write(`telegram gateway: access.json is corrupt, moved aside. Starting fresh.
78101
78623
  `);
@@ -78117,7 +78639,7 @@ function loadAccess() {
78117
78639
  }
78118
78640
  function readPeopleFile() {
78119
78641
  try {
78120
- const raw = readFileSync47(PEOPLE_FILE, "utf8");
78642
+ const raw = readFileSync50(PEOPLE_FILE, "utf8");
78121
78643
  const parsed = JSON.parse(raw);
78122
78644
  if (!Array.isArray(parsed.entries))
78123
78645
  return [];
@@ -78139,11 +78661,11 @@ function assertAllowedChat(chat_id) {
78139
78661
  function saveAccess(a) {
78140
78662
  if (STATIC)
78141
78663
  return;
78142
- mkdirSync35(STATE_DIR, { recursive: true, mode: 448 });
78664
+ mkdirSync37(STATE_DIR, { recursive: true, mode: 448 });
78143
78665
  const tmp = ACCESS_FILE + ".tmp";
78144
- writeFileSync37(tmp, JSON.stringify(a, null, 2) + `
78666
+ writeFileSync40(tmp, JSON.stringify(a, null, 2) + `
78145
78667
  `, { mode: 384 });
78146
- renameSync15(tmp, ACCESS_FILE);
78668
+ renameSync16(tmp, ACCESS_FILE);
78147
78669
  }
78148
78670
  function pruneExpired(a) {
78149
78671
  const now = Date.now();
@@ -78161,7 +78683,7 @@ var HISTORY_ENABLED = HISTORY_ACCESS.historyEnabled !== false;
78161
78683
  if (HISTORY_ENABLED) {
78162
78684
  try {
78163
78685
  initHistory(STATE_DIR, HISTORY_ACCESS.historyRetentionDays ?? 30);
78164
- process.stderr.write(`telegram gateway: history capture enabled at ${join49(STATE_DIR, "history.db")}
78686
+ process.stderr.write(`telegram gateway: history capture enabled at ${join51(STATE_DIR, "history.db")}
78165
78687
  `);
78166
78688
  } catch (err) {
78167
78689
  process.stderr.write(`telegram gateway: history init failed (${err.message}) \u2014 capture disabled
@@ -78177,12 +78699,12 @@ try {
78177
78699
  let markerTurnKey = null;
78178
78700
  let markerAgeMs = null;
78179
78701
  try {
78180
- const markerPath = join49(STATE_DIR, TURN_ACTIVE_MARKER_FILE2);
78181
- if (existsSync45(markerPath)) {
78702
+ const markerPath = join51(STATE_DIR, TURN_ACTIVE_MARKER_FILE2);
78703
+ if (existsSync47(markerPath)) {
78182
78704
  const st = statSync16(markerPath);
78183
78705
  markerAgeMs = Date.now() - st.mtimeMs;
78184
78706
  try {
78185
- const payload = JSON.parse(readFileSync47(markerPath, "utf8"));
78707
+ const payload = JSON.parse(readFileSync50(markerPath, "utf8"));
78186
78708
  if (typeof payload.turnKey === "string" && payload.turnKey.length > 0) {
78187
78709
  markerTurnKey = payload.turnKey;
78188
78710
  }
@@ -78202,13 +78724,13 @@ try {
78202
78724
  process.stderr.write(`telegram gateway: turn-registry boot-reaper stamped ${reaped} orphaned turn(s)${timeoutTurnKey ? ` (turnKey=${timeoutTurnKey} as 'timeout', markerAgeMs=${markerAgeMs})` : " as 'restart'"}
78203
78725
  `);
78204
78726
  } else {
78205
- process.stderr.write(`telegram gateway: turn-registry initialized at ${join49(agentDir, "telegram", "registry.db")}
78727
+ process.stderr.write(`telegram gateway: turn-registry initialized at ${join51(agentDir, "telegram", "registry.db")}
78206
78728
  `);
78207
78729
  }
78208
78730
  const pending2 = findLatestTurnIfInterrupted(turnsDb);
78209
78731
  const selfAgent = process.env.SWITCHROOM_AGENT_NAME ?? "";
78210
78732
  if (pending2 != null && selfAgent) {
78211
- const bootResumeMarkerPath = process.env.SWITCHROOM_GATEWAY_CLEAN_SHUTDOWN_MARKER ?? join49(STATE_DIR, "clean-shutdown.json");
78733
+ const bootResumeMarkerPath = process.env.SWITCHROOM_GATEWAY_CLEAN_SHUTDOWN_MARKER ?? join51(STATE_DIR, "clean-shutdown.json");
78212
78734
  const bootResumeCleanMarker = readCleanShutdownMarker(bootResumeMarkerPath);
78213
78735
  const bootResumeForceAlways = process.env.SWITCHROOM_BOOT_RESUME_ALWAYS === "1";
78214
78736
  const bootResumeMode = parseBootResumeMode(process.env.SWITCHROOM_BOOT_RESUME);
@@ -78275,7 +78797,7 @@ try {
78275
78797
  `);
78276
78798
  }
78277
78799
  }
78278
- const pendingEnvPath = join49(agentDir, ".pending-turn.env");
78800
+ const pendingEnvPath = join51(agentDir, ".pending-turn.env");
78279
78801
  try {
78280
78802
  if (pending2 != null) {
78281
78803
  const lines = [
@@ -78289,14 +78811,14 @@ try {
78289
78811
  pending2.interrupt_reason != null ? `SWITCHROOM_PENDING_INTERRUPT_REASON=${pending2.interrupt_reason}` : `SWITCHROOM_PENDING_INTERRUPT_REASON=`
78290
78812
  ];
78291
78813
  const pendingEnvTmp = `${pendingEnvPath}.tmp-${process.pid}`;
78292
- writeFileSync37(pendingEnvTmp, lines.join(`
78814
+ writeFileSync40(pendingEnvTmp, lines.join(`
78293
78815
  `) + `
78294
78816
  `, { mode: 384 });
78295
- renameSync15(pendingEnvTmp, pendingEnvPath);
78817
+ renameSync16(pendingEnvTmp, pendingEnvPath);
78296
78818
  process.stderr.write(`telegram gateway: pending-turn env written to ${pendingEnvPath} turnKey=${pending2.turn_key} endedVia=${pending2.ended_via ?? "open"}
78297
78819
  `);
78298
- } else if (existsSync45(pendingEnvPath)) {
78299
- rmSync4(pendingEnvPath, { force: true });
78820
+ } else if (existsSync47(pendingEnvPath)) {
78821
+ rmSync5(pendingEnvPath, { force: true });
78300
78822
  process.stderr.write(`telegram gateway: pending-turn env cleared (clean previous shutdown)
78301
78823
  `);
78302
78824
  }
@@ -78391,11 +78913,11 @@ function checkApprovals() {
78391
78913
  return;
78392
78914
  }
78393
78915
  for (const senderId of files) {
78394
- const file = join49(APPROVED_DIR, senderId);
78395
- bot.api.sendMessage(senderId, "Paired! Say hi to Claude.").then(() => rmSync4(file, { force: true }), (err) => {
78916
+ const file = join51(APPROVED_DIR, senderId);
78917
+ bot.api.sendMessage(senderId, "Paired! Say hi to Claude.").then(() => rmSync5(file, { force: true }), (err) => {
78396
78918
  process.stderr.write(`telegram gateway: failed to send approval confirm: ${err}
78397
78919
  `);
78398
- rmSync4(file, { force: true });
78920
+ rmSync5(file, { force: true });
78399
78921
  });
78400
78922
  }
78401
78923
  }
@@ -78408,6 +78930,8 @@ var chatThreadMap = new Map;
78408
78930
  var activeStatusReactions = new Map;
78409
78931
  var activeReactionMsgIds = new Map;
78410
78932
  var queuedStatusMsgIds = new Map;
78933
+ var busyAckPostedKeys = new Set;
78934
+ var busyAckRecheckTimers = new Map;
78411
78935
  var deferredDoneReactions = new DeferredDoneReactions({
78412
78936
  countRunningWorkers: () => countRunningWorkers(),
78413
78937
  getActive: (key) => activeStatusReactions.get(key),
@@ -78493,12 +79017,12 @@ function noteAgentOutputAt(key, ts) {
78493
79017
  lastAgentOutputAt.delete(oldest);
78494
79018
  }
78495
79019
  }
78496
- var OBLIGATION_STORE_PATH = join49(STATE_DIR, "obligations.json");
79020
+ var OBLIGATION_STORE_PATH = join51(STATE_DIR, "obligations.json");
78497
79021
  var obligationStoreFs = {
78498
- readFileSync: (p) => readFileSync47(p, "utf8"),
78499
- writeFileSync: (p, d) => writeFileSync37(p, d),
78500
- renameSync: (a, b) => renameSync15(a, b),
78501
- existsSync: (p) => existsSync45(p)
79022
+ readFileSync: (p) => readFileSync50(p, "utf8"),
79023
+ writeFileSync: (p, d) => writeFileSync40(p, d),
79024
+ renameSync: (a, b) => renameSync16(a, b),
79025
+ existsSync: (p) => existsSync47(p)
78502
79026
  };
78503
79027
  var obligationLedger = new ObligationLedger(OBLIGATION_REPRESENT_MAX, {
78504
79028
  onChange: STATIC || !OBLIGATION_LEDGER_ENABLED ? undefined : (snapshot) => persistObligations(OBLIGATION_STORE_PATH, obligationStoreFs, snapshot)
@@ -78522,6 +79046,7 @@ var FRAMEWORK_ORIGIN_ROUTING_ENABLED = process.env.SWITCHROOM_FRAMEWORK_ORIGIN_R
78522
79046
  var REPLY_TOPIC_AUTHORITY_ENABLED = process.env.SWITCHROOM_REPLY_TOPIC_AUTHORITY !== "0";
78523
79047
  var TOPIC_FRAMING_ENABLED = process.env.SWITCHROOM_TOPIC_FRAMING !== "0";
78524
79048
  var QUEUED_STATUS_UX_ENABLED = process.env.SWITCHROOM_QUEUED_STATUS_UX !== "0";
79049
+ var MIDFLIGHT_BUSY_ACK_ENABLED = process.env.SWITCHROOM_MIDFLIGHT_BUSY_ACK !== "0";
78525
79050
  var FEED_REOPEN_AFTER_ACK_ENABLED = process.env.SWITCHROOM_FEED_REOPEN_AFTER_ACK !== "0";
78526
79051
  var FEED_HEARTBEAT_ENABLED = process.env.SWITCHROOM_FEED_HEARTBEAT !== "0";
78527
79052
  var FEED_HEARTBEAT_TICK_MS = 6000;
@@ -78816,13 +79341,11 @@ function postQueuedStatus(chatId, bufferedThread, inFlightThread) {
78816
79341
  function promoteQueuedStatus(chatId, thread) {
78817
79342
  if (!QUEUED_STATUS_UX_ENABLED)
78818
79343
  return;
78819
- if (thread == null)
78820
- return;
78821
79344
  const key = statusKey(chatId, thread);
78822
79345
  const entry = queuedStatusMsgIds.get(key);
78823
79346
  if (entry == null)
78824
79347
  return;
78825
- swallowingApiCall(() => bot.api.editMessageText(chatId, entry.messageId, "\u270D\uFE0F On it \u2014 replying now.", {}), { chat_id: chatId, verb: "queued-status.promote", threadId: thread });
79348
+ swallowingApiCall(() => bot.api.editMessageText(chatId, entry.messageId, "\u270D\uFE0F On it \u2014 replying now.", {}), { chat_id: chatId, verb: "queued-status.promote", ...thread != null ? { threadId: thread } : {} });
78826
79349
  }
78827
79350
  function reapQueuedStatus(chatId, thread) {
78828
79351
  const key = statusKey(chatId, thread ?? null);
@@ -78832,6 +79355,65 @@ function reapQueuedStatus(chatId, thread) {
78832
79355
  queuedStatusMsgIds.delete(key);
78833
79356
  swallowingApiCall(() => bot.api.deleteMessage(chatId, entry.messageId), { chat_id: chatId, verb: "queued-status.reap", ...entry.threadId != null ? { threadId: entry.threadId } : {} });
78834
79357
  }
79358
+ function maybePostBusyAck(gateDecision, chatId, threadId) {
79359
+ if (!MIDFLIGHT_BUSY_ACK_ENABLED)
79360
+ return;
79361
+ const key = statusKey(chatId, threadId);
79362
+ const inFlight = currentTurn;
79363
+ const inFlightKey = inFlight != null ? statusKey(inFlight.sessionChatId, inFlight.sessionThreadId) : key;
79364
+ const now = Date.now();
79365
+ const step = longestInFlightTool(inFlightKey, now);
79366
+ const midToolCall = toolFlightTracker.isMidToolCall();
79367
+ const stepAgeMs = step?.durationMs ?? null;
79368
+ const alreadyAcked = queuedStatusMsgIds.has(key) || busyAckPostedKeys.has(key);
79369
+ const fire = shouldPostBusyAck({ gateDecision, midToolCall, stepAgeMs, alreadyAcked });
79370
+ if (!fire) {
79371
+ if (midToolCall && !alreadyAcked && stepAgeMs != null && stepAgeMs < BUSY_ACK_STEP_AGE_THRESHOLD_MS && !busyAckRecheckTimers.has(key)) {
79372
+ const turnIdAtSchedule = inFlight?.turnId ?? null;
79373
+ const delayMs = BUSY_ACK_STEP_AGE_THRESHOLD_MS - stepAgeMs + 250;
79374
+ busyAckRecheckTimers.set(key, setTimeout(() => {
79375
+ busyAckRecheckTimers.delete(key);
79376
+ if (turnIdAtSchedule == null || currentTurn?.turnId !== turnIdAtSchedule)
79377
+ return;
79378
+ maybePostBusyAck(gateDecision, chatId, threadId);
79379
+ }, delayMs));
79380
+ }
79381
+ return;
79382
+ }
79383
+ const pendingRecheck = busyAckRecheckTimers.get(key);
79384
+ if (pendingRecheck != null) {
79385
+ clearTimeout(pendingRecheck);
79386
+ busyAckRecheckTimers.delete(key);
79387
+ }
79388
+ busyAckPostedKeys.add(key);
79389
+ const text5 = formatBusyAckText({
79390
+ gateDecision,
79391
+ toolName: step?.name ?? null,
79392
+ toolLabel: step?.label ?? null
79393
+ });
79394
+ process.stderr.write(`telegram gateway: mid-flight busy ack chat=${chatId} thread=${threadId ?? "-"} decision=${gateDecision} step=${step?.name ?? "-"} step_age_ms=${step?.durationMs ?? "-"}
79395
+ `);
79396
+ postBusyAck(chatId, threadId, text5);
79397
+ }
79398
+ function postBusyAck(chatId, threadId, text5) {
79399
+ const key = statusKey(chatId, threadId);
79400
+ if (queuedStatusMsgIds.has(key))
79401
+ return;
79402
+ (async () => {
79403
+ const sent = await swallowingApiCall(() => bot.api.sendMessage(chatId, text5, {
79404
+ ...threadId != null ? { message_thread_id: threadId } : {},
79405
+ disable_notification: true
79406
+ }), { chat_id: chatId, verb: "busy-ack.post", ...threadId != null ? { threadId } : {} });
79407
+ const messageId = sent?.message_id;
79408
+ if (typeof messageId !== "number")
79409
+ return;
79410
+ if (queuedStatusMsgIds.has(key)) {
79411
+ swallowingApiCall(() => bot.api.deleteMessage(chatId, messageId), { chat_id: chatId, verb: "busy-ack.post-race-cleanup", ...threadId != null ? { threadId } : {} });
79412
+ return;
79413
+ }
79414
+ queuedStatusMsgIds.set(key, { chatId, threadId: threadId ?? null, messageId });
79415
+ })();
79416
+ }
78835
79417
  var toolFlightTracker = new ToolFlightTracker;
78836
79418
  var pendingDeferredInterrupt = null;
78837
79419
  function cancelInterruptedObligation() {
@@ -78970,6 +79552,12 @@ function purgeReactionTracking(key, endingTurn) {
78970
79552
  const pqThread = pqThreadPart === "_" || pqThreadPart === "" ? null : Number(pqThreadPart);
78971
79553
  reapQueuedStatus(pqChatId, Number.isFinite(pqThread) ? pqThread : undefined);
78972
79554
  }
79555
+ busyAckPostedKeys.delete(key);
79556
+ const busyAckRecheck = busyAckRecheckTimers.get(key);
79557
+ if (busyAckRecheck != null) {
79558
+ clearTimeout(busyAckRecheck);
79559
+ busyAckRecheckTimers.delete(key);
79560
+ }
78973
79561
  claudeBusyKeys.delete(key);
78974
79562
  claudeBusyKeySince.delete(key);
78975
79563
  reactionTransitionCounts.delete(key);
@@ -79058,7 +79646,7 @@ function emitTurnRecord(turn, endedAt) {
79058
79646
  return;
79059
79647
  }
79060
79648
  },
79061
- rename: (from, to) => renameSync15(from, to)
79649
+ rename: (from, to) => renameSync16(from, to)
79062
79650
  });
79063
79651
  appendFileSync6(turnsPath, rec);
79064
79652
  } catch {}
@@ -79480,8 +80068,10 @@ var typingWrapper = createTypingWrapper({
79480
80068
  stopTypingLoop,
79481
80069
  isSurfaceTool: isTelegramSurfaceTool2
79482
80070
  });
80071
+ var FLOOD_STATE_PATH = floodStatePath(STATE_DIR);
79483
80072
  var robustApiCall = createRetryApiCall2({
79484
- log: (line) => process.stderr.write(line)
80073
+ log: (line) => process.stderr.write(line),
80074
+ onFloodWait: makeFloodWaitRecorder2(FLOOD_STATE_PATH)
79485
80075
  });
79486
80076
  var swallowingApiCall = createSwallowingRetryApiCall(robustApiCall, (line) => process.stderr.write(line));
79487
80077
  function wrapBootCardApi(threadId) {
@@ -80348,23 +80938,29 @@ var PIN_STATUS_WHILE_WORKING = (() => {
80348
80938
  })();
80349
80939
  var statusPinState = new Map;
80350
80940
  var statusPinChatIds = new Map;
80351
- var STATUS_PIN_STORE_PATH = join49(STATE_DIR, "status-pins.json");
80941
+ var statusPinPinnedAt = new Map;
80942
+ var STATUS_PIN_STORE_PATH = join51(STATE_DIR, "status-pins.json");
80352
80943
  var statusPinStoreFs = {
80353
- readFileSync: (p) => readFileSync47(p, "utf8"),
80354
- writeFileSync: (p, d) => writeFileSync37(p, d),
80355
- renameSync: (a, b) => renameSync15(a, b),
80356
- existsSync: (p) => existsSync45(p)
80944
+ readFileSync: (p) => readFileSync50(p, "utf8"),
80945
+ writeFileSync: (p, d) => writeFileSync40(p, d),
80946
+ renameSync: (a, b) => renameSync16(a, b),
80947
+ existsSync: (p) => existsSync47(p)
80357
80948
  };
80358
80949
  var statusPinPersistEnabled = !STATIC && PIN_STATUS_WHILE_WORKING;
80359
- var ACTIVITY_CARD_STORE_PATH = join49(STATE_DIR, "activity-cards-pending.json");
80950
+ var ACTIVITY_CARD_STORE_PATH = join51(STATE_DIR, "activity-cards-pending.json");
80360
80951
  var activityCardStoreFs = {
80361
- readFileSync: (p) => readFileSync47(p, "utf8"),
80362
- writeFileSync: (p, d) => writeFileSync37(p, d),
80363
- renameSync: (a, b) => renameSync15(a, b),
80364
- existsSync: (p) => existsSync45(p)
80952
+ readFileSync: (p) => readFileSync50(p, "utf8"),
80953
+ writeFileSync: (p, d) => writeFileSync40(p, d),
80954
+ renameSync: (a, b) => renameSync16(a, b),
80955
+ existsSync: (p) => existsSync47(p)
80365
80956
  };
80366
80957
  var activityCardPersistEnabled = !STATIC;
80367
80958
  var bannerPinPersistEnabled = !STATIC;
80959
+ var toolPinPersistEnabled = !STATIC;
80960
+ var TOOL_PIN_TTL_MS = (() => {
80961
+ const v = Number(process.env.SWITCHROOM_TOOL_PIN_TTL_MS);
80962
+ return Number.isFinite(v) && v > 0 ? v : 604800000;
80963
+ })();
80368
80964
  function statusPinApi() {
80369
80965
  return {
80370
80966
  pinChatMessage: (chat_id, message_id, opts) => robustApiCall(() => lockedBot.api.pinChatMessage(chat_id, message_id, opts), { chat_id: String(chat_id), verb: "status-pin.pin" }),
@@ -80372,16 +80968,16 @@ function statusPinApi() {
80372
80968
  };
80373
80969
  }
80374
80970
  async function statusPinBootCleanup() {
80375
- if (!statusPinPersistEnabled && !bannerPinPersistEnabled)
80971
+ if (!statusPinPersistEnabled && !bannerPinPersistEnabled && !toolPinPersistEnabled)
80376
80972
  return;
80377
80973
  const api = statusPinApi();
80378
- const { cleared, total } = await runStatusPinBootCleanup({
80974
+ const { cleared, retained, kept, total } = await runStatusPinBootCleanup({
80379
80975
  path: STATUS_PIN_STORE_PATH,
80380
80976
  fs: statusPinStoreFs,
80381
80977
  unpin: (chatId, messageId) => api.unpinChatMessage(chatId, messageId)
80382
80978
  });
80383
80979
  if (total > 0) {
80384
- process.stderr.write(`telegram gateway: status-pin: cleared ${cleared}/${total} orphaned pin(s) from a prior session
80980
+ process.stderr.write(`telegram gateway: status-pin: cleared ${cleared}/${total} orphaned pin(s) from a prior session (retained ${retained} for retry, kept ${kept} unexpired tool pin(s))
80385
80981
  `);
80386
80982
  }
80387
80983
  }
@@ -80416,6 +81012,11 @@ var MID_SESSION_CARD_REAPER_INTERVAL_MS = (() => {
80416
81012
  const v = Number(process.env.SWITCHROOM_MID_SESSION_CARD_REAPER_INTERVAL_MS);
80417
81013
  return Number.isFinite(v) && v > 0 ? v : 300000;
80418
81014
  })();
81015
+ var WORKER_PIN_REAPER_ENABLED = process.env.SWITCHROOM_WORKER_PIN_REAPER !== "0";
81016
+ var WORKER_PIN_REAPER_TTL_MS = (() => {
81017
+ const v = Number(process.env.SWITCHROOM_WORKER_PIN_REAPER_TTL_MS);
81018
+ return Number.isFinite(v) && v > 0 ? v : WORKER_PIN_TTL_MS_DEFAULT;
81019
+ })();
80419
81020
  function liveTurnKeySets() {
80420
81021
  const registryKeys = new Set;
80421
81022
  const topicKeys = new Set;
@@ -80465,11 +81066,18 @@ async function runMidSessionCardReaper() {
80465
81066
  ...record2.threadId != null ? { threadId: record2.threadId } : {},
80466
81067
  verb: "activity-card.mid-session-reap-finalize"
80467
81068
  }),
80468
- unpinCard: (record2) => robustApiCall(() => lockedBot.api.unpinChatMessage(record2.chatId, record2.activityMessageId), {
80469
- chat_id: record2.chatId,
80470
- ...record2.threadId != null ? { threadId: record2.threadId } : {},
80471
- verb: "activity-card.mid-session-reap-unpin"
80472
- })
81069
+ unpinCard: async (record2) => {
81070
+ const pinKey = `fg:${record2.turnKey}`;
81071
+ if (statusPinState.has(pinKey)) {
81072
+ await reconcileStatusPin(pinKey, record2.chatId, { pinned: false });
81073
+ return true;
81074
+ }
81075
+ return robustApiCall(() => lockedBot.api.unpinChatMessage(record2.chatId, record2.activityMessageId), {
81076
+ chat_id: record2.chatId,
81077
+ ...record2.threadId != null ? { threadId: record2.threadId } : {},
81078
+ verb: "activity-card.mid-session-reap-unpin"
81079
+ });
81080
+ }
80473
81081
  });
80474
81082
  if (total > 0) {
80475
81083
  process.stderr.write(`telegram gateway: activity-card: mid-session finalized ${finalized}/${total} (vanished ${vanished}/${total}) orphaned card(s) (at-most-once)
@@ -80477,6 +81085,44 @@ async function runMidSessionCardReaper() {
80477
81085
  }
80478
81086
  } catch (err) {
80479
81087
  process.stderr.write(`telegram gateway: mid-session card reaper error: ${err.message}
81088
+ `);
81089
+ }
81090
+ }
81091
+ if (WORKER_PIN_REAPER_ENABLED && PIN_STATUS_WHILE_WORKING) {
81092
+ try {
81093
+ const candidates = [...statusPinState.keys()].filter((k) => k.startsWith("wk:")).map((k) => ({
81094
+ pinKey: k,
81095
+ chatId: statusPinChatIds.get(k) ?? "",
81096
+ pinnedAt: statusPinPinnedAt.get(k) ?? now
81097
+ }));
81098
+ const reaps = decideWorkerPinReaps({
81099
+ pins: candidates,
81100
+ statusOf: (agentId) => {
81101
+ if (turnsDb == null)
81102
+ return "unknown";
81103
+ try {
81104
+ const row = getSubagentByJsonlId(turnsDb, agentId);
81105
+ if (row == null)
81106
+ return "unknown";
81107
+ if (row.status === "completed" || row.status === "failed")
81108
+ return "terminal";
81109
+ if (row.status === "running")
81110
+ return "running";
81111
+ return "unknown";
81112
+ } catch {
81113
+ return "unknown";
81114
+ }
81115
+ },
81116
+ ttlMs: WORKER_PIN_REAPER_TTL_MS,
81117
+ now
81118
+ });
81119
+ for (const reap of reaps) {
81120
+ process.stderr.write(`telegram gateway: worker-pin reaper unpinning ${reap.pinKey} (chat=${reap.chatId} reason=${reap.reason})
81121
+ `);
81122
+ await reconcileStatusPin(reap.pinKey, reap.chatId, { pinned: false });
81123
+ }
81124
+ } catch (err) {
81125
+ process.stderr.write(`telegram gateway: worker-pin reaper error: ${err.message}
80480
81126
  `);
80481
81127
  }
80482
81128
  }
@@ -80518,9 +81164,12 @@ async function reconcileStatusPinInner(pinKey, chatId, desired) {
80518
81164
  if (next2 == null) {
80519
81165
  statusPinState.delete(pinKey);
80520
81166
  statusPinChatIds.delete(pinKey);
81167
+ statusPinPinnedAt.delete(pinKey);
80521
81168
  } else {
80522
81169
  statusPinState.set(pinKey, next2);
80523
81170
  statusPinChatIds.set(pinKey, chatId);
81171
+ if (!statusPinPinnedAt.has(pinKey))
81172
+ statusPinPinnedAt.set(pinKey, Date.now());
80524
81173
  }
80525
81174
  return;
80526
81175
  }
@@ -80535,9 +81184,12 @@ async function reconcileStatusPinInner(pinKey, chatId, desired) {
80535
81184
  if (next == null) {
80536
81185
  statusPinState.delete(pinKey);
80537
81186
  statusPinChatIds.delete(pinKey);
81187
+ statusPinPinnedAt.delete(pinKey);
80538
81188
  } else {
80539
81189
  statusPinState.set(pinKey, next);
80540
81190
  statusPinChatIds.set(pinKey, chatId);
81191
+ if (!statusPinPinnedAt.has(pinKey))
81192
+ statusPinPinnedAt.set(pinKey, Date.now());
80541
81193
  }
80542
81194
  }
80543
81195
  function reconcileWorkerPin(agentId, chatId, running) {
@@ -80567,6 +81219,7 @@ async function unpinAllStatusPins() {
80567
81219
  const chatId = statusPinChatIds.get(key);
80568
81220
  if (chatId == null) {
80569
81221
  statusPinState.delete(key);
81222
+ statusPinPinnedAt.delete(key);
80570
81223
  continue;
80571
81224
  }
80572
81225
  await reconcileStatusPin(key, chatId, { pinned: false });
@@ -80578,11 +81231,11 @@ var getPinnedProgressCardMessageId = null;
80578
81231
  var completeProgressCardTurn = null;
80579
81232
  var subagentWatcher = null;
80580
81233
  var workerActivityFeed = null;
80581
- var SOCKET_PATH = process.env.SWITCHROOM_GATEWAY_SOCKET ?? join49(STATE_DIR, "gateway.sock");
80582
- mkdirSync35(STATE_DIR, { recursive: true, mode: 448 });
80583
- var GATEWAY_PID_PATH = process.env.SWITCHROOM_GATEWAY_PID_FILE ?? join49(STATE_DIR, "gateway.pid.json");
80584
- var GATEWAY_SESSION_MARKER_PATH = process.env.SWITCHROOM_GATEWAY_SESSION_MARKER ?? join49(STATE_DIR, "gateway-session.json");
80585
- var GATEWAY_CLEAN_SHUTDOWN_MARKER_PATH = process.env.SWITCHROOM_GATEWAY_CLEAN_SHUTDOWN_MARKER ?? join49(STATE_DIR, "clean-shutdown.json");
81234
+ var SOCKET_PATH = process.env.SWITCHROOM_GATEWAY_SOCKET ?? join51(STATE_DIR, "gateway.sock");
81235
+ mkdirSync37(STATE_DIR, { recursive: true, mode: 448 });
81236
+ var GATEWAY_PID_PATH = process.env.SWITCHROOM_GATEWAY_PID_FILE ?? join51(STATE_DIR, "gateway.pid.json");
81237
+ var GATEWAY_SESSION_MARKER_PATH = process.env.SWITCHROOM_GATEWAY_SESSION_MARKER ?? join51(STATE_DIR, "gateway-session.json");
81238
+ var GATEWAY_CLEAN_SHUTDOWN_MARKER_PATH = process.env.SWITCHROOM_GATEWAY_CLEAN_SHUTDOWN_MARKER ?? join51(STATE_DIR, "clean-shutdown.json");
80586
81239
  var GATEWAY_STARTED_AT_MS = Date.now();
80587
81240
  var BOOT_CARD_ENABLED = process.env.SWITCHROOM_BOOT_CARD !== "false";
80588
81241
  var activeBootCard = null;
@@ -80611,7 +81264,7 @@ function ensureIssuesCard(chatId, threadId) {
80611
81264
  bot: botApi,
80612
81265
  log: (msg) => process.stderr.write(`telegram gateway: ${msg}
80613
81266
  `),
80614
- persistPath: join49(stateDir, "issues-card.json")
81267
+ persistPath: join51(stateDir, "issues-card.json")
80615
81268
  });
80616
81269
  activeIssuesWatcher = startIssuesWatcher({
80617
81270
  stateDir,
@@ -80940,13 +81593,13 @@ startTimer2({
80940
81593
  }
80941
81594
  });
80942
81595
  var inboundSpool = STATIC ? undefined : createInboundSpool({
80943
- path: join49(STATE_DIR, "inbound-spool.jsonl"),
81596
+ path: join51(STATE_DIR, "inbound-spool.jsonl"),
80944
81597
  fs: {
80945
81598
  appendFileSync: (p, d) => appendFileSync6(p, d),
80946
- readFileSync: (p) => readFileSync47(p, "utf8"),
80947
- writeFileSync: (p, d) => writeFileSync37(p, d),
80948
- renameSync: (a, b) => renameSync15(a, b),
80949
- existsSync: (p) => existsSync45(p),
81599
+ readFileSync: (p) => readFileSync50(p, "utf8"),
81600
+ writeFileSync: (p, d) => writeFileSync40(p, d),
81601
+ renameSync: (a, b) => renameSync16(a, b),
81602
+ existsSync: (p) => existsSync47(p),
80950
81603
  statSizeSync: (p) => statSync16(p).size
80951
81604
  },
80952
81605
  onDegraded: (info) => {
@@ -81242,8 +81895,9 @@ var ipcServer = createIpcServer({
81242
81895
  probeQuotaViaBroker: (t) => probeQuotaForBootCard(agentSlug, t),
81243
81896
  tmuxSupervisor: process.env.SWITCHROOM_TMUX_SUPERVISOR === "1",
81244
81897
  dockerMode: process.env.SWITCHROOM_RUNTIME === "docker",
81245
- configSnapshotPath: join49(resolvedAgentDirForCard, ".config-snapshot.json"),
81246
- bootCardStatePath: join49(resolvedAgentDirForCard, ".boot-card-msgid.json"),
81898
+ configSnapshotPath: join51(resolvedAgentDirForCard, ".config-snapshot.json"),
81899
+ bootCardStatePath: join51(resolvedAgentDirForCard, ".boot-card-msgid.json"),
81900
+ floodStatePath: FLOOD_STATE_PATH,
81247
81901
  ...updateOutcomeLine ? { updateOutcomeLine } : {}
81248
81902
  }, ackMsgId).then((handle) => {
81249
81903
  activeBootCard = handle;
@@ -81906,7 +82560,7 @@ var ipcServer = createIpcServer({
81906
82560
  const receiverUid = receiverUidRaw ? Number(receiverUidRaw) : NaN;
81907
82561
  if (Number.isInteger(receiverUid))
81908
82562
  allowedUids.push(receiverUid);
81909
- const socketPath = join49(STATE_DIR, "webhook.sock");
82563
+ const socketPath = join51(STATE_DIR, "webhook.sock");
81910
82564
  const webhookInject = (agentName3, inbound) => {
81911
82565
  const msg = inbound;
81912
82566
  const delivered = ipcServer.sendToAgent(agentName3, msg);
@@ -82139,9 +82793,9 @@ function redactOutboundText(text5, site) {
82139
82793
  var VOICE_OUT_DEFAULT_CHUNK_CHARS = 600;
82140
82794
  var VOICE_OUT_HARD_CHUNK_CAP = 4096;
82141
82795
  var voiceOnDemandCache = new VoiceOnDemandCache({
82142
- persistPath: join49(STATE_DIR, "voice-ondemand.json")
82796
+ persistPath: join51(STATE_DIR, "voice-ondemand.json")
82143
82797
  });
82144
- var VOICE_CACHE_DIR = join49(STATE_DIR, "voice-cache");
82798
+ var VOICE_CACHE_DIR = join51(STATE_DIR, "voice-cache");
82145
82799
  var voicePreSynthQueue = new PreSynthQueue({
82146
82800
  runJob: async (job) => {
82147
82801
  const sidecarToken = await materializeSidecarToken();
@@ -82416,7 +83070,7 @@ ${url}`;
82416
83070
  }
82417
83071
  const limit = Math.max(1, Math.min(access.textChunkLimit ?? RICH_MESSAGE_MAX_CHARS2, MAX_CHUNK_LIMIT));
82418
83072
  const replyMode = access.replyToMode ?? "first";
82419
- const chunks = literalText ? chunk2(effectiveText, limit, access.chunkMode ?? "length") : splitMarkdownChunks(effectiveText, limit);
83073
+ const chunks = literalText ? chunk2(effectiveText, limit, access.chunkMode ?? "length") : splitMarkdownChunks2(effectiveText, limit);
82420
83074
  const sentIds = [];
82421
83075
  const useOnDemandButton = voiceOutPlan != null && voiceOutPlan.replyMode === "on-demand" && voiceOutPlan.engine === "kokoro";
82422
83076
  const voiceOggs = [];
@@ -82665,8 +83319,8 @@ ${url}`;
82665
83319
  return lockedBot.api.sendRichMessage(chat_id, richMessage2(chunks[i]), richOpts);
82666
83320
  };
82667
83321
  const sendChunkResplit = async (opts) => {
82668
- const subPieces = splitMarkdownChunks(chunks[i], RICH_MESSAGE_MAX_CHARS2);
82669
- const pieces = subPieces.length > 1 ? subPieces : hardSliceToCap(chunks[i], RICH_MESSAGE_MAX_CHARS2);
83322
+ const subPieces = splitMarkdownChunks2(chunks[i], RICH_MESSAGE_MAX_CHARS2);
83323
+ const pieces = subPieces.length > 1 ? subPieces : hardSliceToCap2(chunks[i], RICH_MESSAGE_MAX_CHARS2);
82670
83324
  for (let p = 0;p < pieces.length; p++) {
82671
83325
  let sent;
82672
83326
  if (literalText) {
@@ -83092,11 +83746,11 @@ async function executeSendGif(rawArgs) {
83092
83746
  };
83093
83747
  }
83094
83748
  async function publishToTelegraph(text5, shortName, authorName) {
83095
- const accountPath = join49(STATE_DIR, "telegraph-account.json");
83749
+ const accountPath = join51(STATE_DIR, "telegraph-account.json");
83096
83750
  let account = null;
83097
83751
  try {
83098
- if (existsSync45(accountPath)) {
83099
- const raw = readFileSync47(accountPath, "utf-8");
83752
+ if (existsSync47(accountPath)) {
83753
+ const raw = readFileSync50(accountPath, "utf-8");
83100
83754
  const parsed = JSON.parse(raw);
83101
83755
  if (parsed.shortName && parsed.accessToken) {
83102
83756
  account = parsed;
@@ -83115,8 +83769,8 @@ async function publishToTelegraph(text5, shortName, authorName) {
83115
83769
  }
83116
83770
  account = created.value;
83117
83771
  try {
83118
- mkdirSync35(STATE_DIR, { recursive: true, mode: 448 });
83119
- writeFileSync37(accountPath, JSON.stringify(account, null, 2), { mode: 384 });
83772
+ mkdirSync37(STATE_DIR, { recursive: true, mode: 448 });
83773
+ writeFileSync40(accountPath, JSON.stringify(account, null, 2), { mode: 384 });
83120
83774
  } catch (err) {
83121
83775
  process.stderr.write(`telegram gateway: telegraph cache write failed: ${err.message}
83122
83776
  `);
@@ -83555,7 +84209,7 @@ async function executeVaultRequestAccess(args) {
83555
84209
  }
83556
84210
  function readLiveSwitchroomConfigText() {
83557
84211
  const cfgPath = process.env.SWITCHROOM_CONFIG ?? findConfigFile2();
83558
- return readFileSync47(cfgPath, "utf8");
84212
+ return readFileSync50(cfgPath, "utf8");
83559
84213
  }
83560
84214
  var MENTAL_MODEL_NAME_REGEX = /^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$/;
83561
84215
  function buildMentalModelProposeKeyboard(stageId) {
@@ -83724,9 +84378,9 @@ async function executeDownloadAttachment(args) {
83724
84378
  fileUniqueId: file.file_unique_id,
83725
84379
  now: Date.now()
83726
84380
  });
83727
- mkdirSync35(INBOX_DIR, { recursive: true, mode: 448 });
84381
+ mkdirSync37(INBOX_DIR, { recursive: true, mode: 448 });
83728
84382
  assertInsideInbox(INBOX_DIR, dlPath);
83729
- writeFileSync37(dlPath, buf, { mode: 384 });
84383
+ writeFileSync40(dlPath, buf, { mode: 384 });
83730
84384
  return { content: [{ type: "text", text: dlPath }] };
83731
84385
  }
83732
84386
  async function executeEditMessage(args) {
@@ -83799,7 +84453,17 @@ async function executePinMessage(args) {
83799
84453
  throw new Error("pin_message: message_id is required");
83800
84454
  const pinChatId = String(args.chat_id ?? "");
83801
84455
  assertAllowedChat(pinChatId);
83802
- await robustApiCall(() => lockedBot.api.pinChatMessage(pinChatId, Number(args.message_id)), { chat_id: pinChatId, verb: "pin_message" });
84456
+ const pinMsgId = Number(args.message_id);
84457
+ await robustApiCall(() => lockedBot.api.pinChatMessage(pinChatId, pinMsgId), { chat_id: pinChatId, verb: "pin_message" });
84458
+ if (toolPinPersistEnabled) {
84459
+ const toolPinKey = `tool:${pinChatId}:${pinMsgId}`;
84460
+ mutateStatusPinRow(STATUS_PIN_STORE_PATH, statusPinStoreFs, toolPinKey, {
84461
+ pinKey: toolPinKey,
84462
+ chatId: pinChatId,
84463
+ messageId: pinMsgId,
84464
+ expiresAt: Date.now() + TOOL_PIN_TTL_MS
84465
+ });
84466
+ }
83803
84467
  return { content: [{ type: "text", text: `pinned message ${args.message_id}` }] };
83804
84468
  }
83805
84469
  async function executeDeleteMessage(args) {
@@ -84894,7 +85558,7 @@ function handleSessionEvent(ev) {
84894
85558
  };
84895
85559
  const limit = RICH_MESSAGE_MAX_CHARS2;
84896
85560
  const renderedText = addParagraphSpacers(capturedText);
84897
- const htmlChunks = splitMarkdownChunks(renderedText, limit);
85561
+ const htmlChunks = splitMarkdownChunks2(renderedText, limit);
84898
85562
  const sentIds = [];
84899
85563
  try {
84900
85564
  let firstSendUsedEdit = false;
@@ -85999,8 +86663,11 @@ ${preBlock(write.output)}`;
85999
86663
  process.stderr.write(`telegram gateway: inbound held mid-turn agent=${selfAgent} chat=${chat_id} msg=${msgId ?? "-"} \u2014 will flush on turn-complete
86000
86664
  `);
86001
86665
  const inFlightThread = currentTurn?.sessionThreadId;
86002
- if (QUEUED_STATUS_UX_ENABLED && !isDmChatId(chat_id) && messageThreadId != null && messageThreadId !== inFlightThread) {
86666
+ const crossTopicQueuedCard = QUEUED_STATUS_UX_ENABLED && !isDmChatId(chat_id) && messageThreadId != null && messageThreadId !== inFlightThread;
86667
+ if (crossTopicQueuedCard) {
86003
86668
  postQueuedStatus(chat_id, messageThreadId, inFlightThread);
86669
+ } else {
86670
+ maybePostBusyAck("buffer-until-idle", chat_id, messageThreadId ?? undefined);
86004
86671
  }
86005
86672
  return;
86006
86673
  }
@@ -86023,6 +86690,9 @@ ${preBlock(write.output)}`;
86023
86690
  }
86024
86691
  const delivered = ipcServer.sendToAgent(selfAgent, inboundMsg);
86025
86692
  if (delivered) {
86693
+ if (isSteering) {
86694
+ maybePostBusyAck("steer", chat_id, messageThreadId ?? undefined);
86695
+ }
86026
86696
  const busyKey = reservedBusyKey ?? markClaudeBusyForInbound(inboundMsg);
86027
86697
  if (DELIVERY_CONFIRM_ENABLED && shouldTrackDelivery({
86028
86698
  isSteering,
@@ -86154,14 +86824,14 @@ function restartMarkerPath() {
86154
86824
  const agentDir = resolveAgentDirFromEnv();
86155
86825
  if (!agentDir)
86156
86826
  return null;
86157
- return join49(agentDir, "restart-pending.json");
86827
+ return join51(agentDir, "restart-pending.json");
86158
86828
  }
86159
86829
  function writeRestartMarker(marker) {
86160
86830
  const p = restartMarkerPath();
86161
86831
  if (!p)
86162
86832
  return;
86163
86833
  try {
86164
- writeFileSync37(p, JSON.stringify(marker));
86834
+ writeFileSync40(p, JSON.stringify(marker));
86165
86835
  lastPlannedRestartAt = Date.now();
86166
86836
  process.stderr.write(`telegram gateway: restart-marker: write chat_id=${marker.chat_id} thread_id=${marker.thread_id ?? "-"} ack=${marker.ack_message_id ?? "-"} path=${p}
86167
86837
  `);
@@ -86180,7 +86850,7 @@ function readRestartMarker() {
86180
86850
  if (!p)
86181
86851
  return null;
86182
86852
  try {
86183
- return JSON.parse(readFileSync47(p, "utf8"));
86853
+ return JSON.parse(readFileSync50(p, "utf8"));
86184
86854
  } catch {
86185
86855
  return null;
86186
86856
  }
@@ -86190,7 +86860,7 @@ function clearRestartMarker() {
86190
86860
  if (!p)
86191
86861
  return;
86192
86862
  try {
86193
- rmSync4(p, { force: true });
86863
+ rmSync5(p, { force: true });
86194
86864
  process.stderr.write(`telegram gateway: restart-marker: cleared path=${p}
86195
86865
  `);
86196
86866
  } catch {}
@@ -86329,7 +86999,7 @@ var _dockerReachable;
86329
86999
  function isDockerReachable() {
86330
87000
  if (_dockerReachable !== undefined)
86331
87001
  return _dockerReachable;
86332
- if (!existsSync45("/var/run/docker.sock")) {
87002
+ if (!existsSync47("/var/run/docker.sock")) {
86333
87003
  _dockerReachable = false;
86334
87004
  return _dockerReachable;
86335
87005
  }
@@ -86346,12 +87016,12 @@ function _resetDockerReachableCache() {
86346
87016
  }
86347
87017
  function spawnSwitchroomDetached(args, onFailure) {
86348
87018
  const fullArgs = SWITCHROOM_CONFIG ? ["--config", SWITCHROOM_CONFIG, ...args] : args;
86349
- const logPath = join49(STATE_DIR, "detached-spawn.log");
87019
+ const logPath = join51(STATE_DIR, "detached-spawn.log");
86350
87020
  let outFd = null;
86351
87021
  try {
86352
- mkdirSync35(STATE_DIR, { recursive: true });
87022
+ mkdirSync37(STATE_DIR, { recursive: true });
86353
87023
  outFd = openSync9(logPath, "a");
86354
- writeFileSync37(logPath, `
87024
+ writeFileSync40(logPath, `
86355
87025
  [${new Date().toISOString()}] spawn ${SWITCHROOM_CLI} ${fullArgs.join(" ")}
86356
87026
  `, { flag: "a" });
86357
87027
  } catch {}
@@ -86377,7 +87047,7 @@ function spawnSwitchroomDetached(args, onFailure) {
86377
87047
  return;
86378
87048
  let tail = "";
86379
87049
  try {
86380
- const full = readFileSync47(logPath, "utf8");
87050
+ const full = readFileSync50(logPath, "utf8");
86381
87051
  tail = full.split(`
86382
87052
  `).slice(-30).join(`
86383
87053
  `).trim();
@@ -86744,10 +87414,10 @@ bot.use(async (ctx, next) => {
86744
87414
  });
86745
87415
  function readRecentDenialsForAgent(agentName3, windowMs, limit) {
86746
87416
  try {
86747
- const auditPath = join49(homedir16(), ".switchroom", "vault-audit.log");
86748
- if (!existsSync45(auditPath))
87417
+ const auditPath = join51(homedir16(), ".switchroom", "vault-audit.log");
87418
+ if (!existsSync47(auditPath))
86749
87419
  return [];
86750
- const raw = readFileSync47(auditPath, "utf8");
87420
+ const raw = readFileSync50(auditPath, "utf8");
86751
87421
  return recentDenialsFromAuditLog(raw, { agentName: agentName3, windowMs, limit });
86752
87422
  } catch {
86753
87423
  return [];
@@ -86798,7 +87468,7 @@ async function buildAgentMetadata(agentName3) {
86798
87468
  try {
86799
87469
  const agentDir = resolveAgentDirFromEnv();
86800
87470
  if (agentDir) {
86801
- const raw = readFileSync47(join49(agentDir, ".claude", ".claude.json"), "utf8");
87471
+ const raw = readFileSync50(join51(agentDir, ".claude", ".claude.json"), "utf8");
86802
87472
  claudeJson = JSON.parse(raw);
86803
87473
  }
86804
87474
  } catch {}
@@ -86976,7 +87646,7 @@ function buildModelDeps(restartCtx) {
86976
87646
  try {
86977
87647
  const agentDir = resolveAgentDirFromEnv();
86978
87648
  if (agentDir) {
86979
- const local = await fetchQuota2({ claudeConfigDir: join49(agentDir, ".claude") });
87649
+ const local = await fetchQuota2({ claudeConfigDir: join51(agentDir, ".claude") });
86980
87650
  if (local.ok)
86981
87651
  return formatQuotaLine2(local.data);
86982
87652
  }
@@ -87008,6 +87678,11 @@ function buildModelDeps(restartCtx) {
87008
87678
  });
87009
87679
  }
87010
87680
  stampUserRestartReason(reason);
87681
+ {
87682
+ const smDir = resolveAgentDirFromEnv();
87683
+ if (smDir)
87684
+ writeRelaunchModelIntent(smDir, "keep", reason);
87685
+ }
87011
87686
  await sweepBeforeSelfRestart();
87012
87687
  const hostdResp = await tryHostdDispatch(name, {
87013
87688
  v: 1,
@@ -87022,26 +87697,29 @@ function buildModelDeps(restartCtx) {
87022
87697
  }
87023
87698
  if (hostdResp.result !== "started" && hostdResp.result !== "completed") {
87024
87699
  clearRestartMarker();
87700
+ {
87701
+ const smDir = resolveAgentDirFromEnv();
87702
+ if (smDir)
87703
+ clearRelaunchModelIntent(smDir);
87704
+ }
87025
87705
  throw new Error(`hostd restart failed (result=${hostdResp.result}): ${hostdResp.error ?? "(no details)"}`);
87026
87706
  }
87027
87707
  },
87028
87708
  scheduleModelRelaunch: async (model, reason) => {
87029
87709
  const agentDir = resolveAgentDirFromEnv();
87030
87710
  if (!agentDir)
87031
- throw new Error("agent dir unresolvable \u2014 cannot write session-model carrier");
87711
+ throw new Error("agent dir unresolvable \u2014 cannot write session-model file");
87032
87712
  const prevOverride = sessionModelSource.getOverride();
87033
- writeFileSync37(join49(agentDir, ".session-model-override"), `${model}
87034
- `, "utf8");
87713
+ const prevFileRaw = readSessionModelFileRaw(agentDir);
87714
+ writeSessionModelFile(agentDir, model, readConfiguredDefaultModel(agentDir) ?? resolveMainModel(deps.getConfiguredModel() ?? undefined));
87035
87715
  sessionModelSource.setOverride(model);
87036
87716
  try {
87037
87717
  await deps.scheduleRestart(reason);
87038
87718
  } catch (err) {
87039
- const carrierPath = join49(agentDir, ".session-model-override");
87040
87719
  if (err?.code !== "restart_in_flight") {
87041
- try {
87042
- rmSync4(carrierPath, { force: true });
87043
- } catch {}
87720
+ restoreSessionModelFileRaw(agentDir, prevFileRaw);
87044
87721
  sessionModelSource.setOverride(prevOverride);
87722
+ clearRelaunchModelIntent(agentDir);
87045
87723
  }
87046
87724
  throw err;
87047
87725
  }
@@ -87074,10 +87752,29 @@ bot.command("model", async (ctx) => {
87074
87752
  return;
87075
87753
  }
87076
87754
  const reply = await handleModelCommand(parsed, deps);
87077
- if (reply.selectedModel) {
87755
+ const requested = parsed.kind === "set" ? expandSrAlias(parsed.model) : null;
87756
+ let persistWarning = "";
87757
+ if (requested?.toLowerCase() === "default") {
87758
+ const smDir = resolveAgentDirFromEnv();
87759
+ if (smDir)
87760
+ clearSessionModelFile(smDir);
87761
+ if (reply.selectedModel)
87762
+ sessionModelSource.setOverride(null);
87763
+ } else if (reply.selectedModel) {
87078
87764
  sessionModelSource.setOverride(reply.selectedModel);
87765
+ const smDir = resolveAgentDirFromEnv();
87766
+ if (smDir && requested && isValidModelArg(requested) && !isSrModel(requested)) {
87767
+ try {
87768
+ writeSessionModelFile(smDir, requested, readConfiguredDefaultModel(smDir) ?? resolveMainModel(deps.getConfiguredModel() ?? undefined));
87769
+ } catch (err) {
87770
+ persistWarning = `
87771
+ \u26A0\uFE0F Couldn\u2019t persist the sticky override \u2014 the switch is live now but won\u2019t survive a relaunch.`;
87772
+ process.stderr.write(`telegram gateway: session-model persist failed (typed /model): ${err?.message ?? String(err)}
87773
+ `);
87774
+ }
87775
+ }
87079
87776
  }
87080
- await switchroomReply(ctx, reply.text, { html: reply.html });
87777
+ await switchroomReply(ctx, reply.text + persistWarning, { html: reply.html });
87081
87778
  });
87082
87779
  function buildEffortDeps() {
87083
87780
  return {
@@ -87173,6 +87870,11 @@ bot.command("restart", async (ctx) => {
87173
87870
  } catch {}
87174
87871
  writeRestartMarker({ chat_id: chatId, thread_id: threadId ?? null, ack_message_id: ackId, ts: Date.now() });
87175
87872
  stampUserRestartReason("user: /restart from chat");
87873
+ {
87874
+ const smDir = resolveAgentDirFromEnv();
87875
+ if (smDir)
87876
+ writeRelaunchModelIntent(smDir, "revert", "user: /restart from chat");
87877
+ }
87176
87878
  await sweepBeforeSelfRestart();
87177
87879
  const hostdResp = await tryHostdDispatch(getMyAgentName(), {
87178
87880
  v: 1,
@@ -87203,9 +87905,9 @@ bot.command("restart", async (ctx) => {
87203
87905
  function flushAgentHandoff(agentDir) {
87204
87906
  let removed = 0;
87205
87907
  for (const fname of [".handoff.md", ".handoff-topic"]) {
87206
- const p = join49(agentDir, fname);
87908
+ const p = join51(agentDir, fname);
87207
87909
  try {
87208
- if (existsSync45(p)) {
87910
+ if (existsSync47(p)) {
87209
87911
  unlinkSync20(p);
87210
87912
  removed++;
87211
87913
  }
@@ -87261,7 +87963,7 @@ async function handleNewCommand(ctx) {
87261
87963
  writeRestartMarker({ chat_id: chatId, thread_id: threadId ?? null, ack_message_id: ackId, ts: Date.now() });
87262
87964
  if (agentDir != null) {
87263
87965
  try {
87264
- writeFileSync37(join49(agentDir, ".force-fresh-session"), `${kind} at ${new Date().toISOString()}
87966
+ writeFileSync40(join51(agentDir, ".force-fresh-session"), `${kind} at ${new Date().toISOString()}
87265
87967
  `, "utf8");
87266
87968
  } catch (err) {
87267
87969
  process.stderr.write(`telegram gateway: failed to write force-fresh marker: ${err}
@@ -87269,6 +87971,9 @@ async function handleNewCommand(ctx) {
87269
87971
  }
87270
87972
  }
87271
87973
  stampUserRestartReason(`user: /${kind} from chat`);
87974
+ if (agentDir != null) {
87975
+ writeRelaunchModelIntent(agentDir, "keep", `user: /${kind} from chat`);
87976
+ }
87272
87977
  await sweepBeforeSelfRestart();
87273
87978
  const hostdResp = await tryHostdDispatch(getMyAgentName(), {
87274
87979
  v: 1,
@@ -87625,16 +88330,16 @@ bot.command("interrupt", async (ctx) => {
87625
88330
  await runSwitchroomCommand(ctx, ["agent", "interrupt", name], `interrupt ${name}`);
87626
88331
  });
87627
88332
  var lockoutOps = {
87628
- readFileSync: (p, enc) => readFileSync47(p, enc),
87629
- writeFileSync: (p, data, opts) => writeFileSync37(p, data, opts),
87630
- existsSync: (p) => existsSync45(p),
87631
- mkdirSync: (p, opts) => mkdirSync35(p, opts),
87632
- joinPath: (...parts) => join49(...parts)
88333
+ readFileSync: (p, enc) => readFileSync50(p, enc),
88334
+ writeFileSync: (p, data, opts) => writeFileSync40(p, data, opts),
88335
+ existsSync: (p) => existsSync47(p),
88336
+ mkdirSync: (p, opts) => mkdirSync37(p, opts),
88337
+ joinPath: (...parts) => join51(...parts)
87633
88338
  };
87634
88339
  var FLEET_FALLBACK_DEDUP_MS = 30000;
87635
88340
  function isAuthBrokerSocketReachable() {
87636
88341
  try {
87637
- return existsSync45(resolveAuthBrokerSocketPath2());
88342
+ return existsSync47(resolveAuthBrokerSocketPath2());
87638
88343
  } catch {
87639
88344
  return false;
87640
88345
  }
@@ -87757,7 +88462,7 @@ async function runCreditWatch() {
87757
88462
  if (!agentDir)
87758
88463
  return;
87759
88464
  const agentName3 = getMyAgentName();
87760
- const claudeConfigDir = join49(agentDir, ".claude");
88465
+ const claudeConfigDir = join51(agentDir, ".claude");
87761
88466
  const stateDir = STATE_DIR;
87762
88467
  const reason = readClaudeJsonOverage(claudeConfigDir);
87763
88468
  const prev = loadCreditState(stateDir);
@@ -88275,10 +88980,10 @@ async function handleVaultRecentDenialCallback(ctx, data) {
88275
88980
  return;
88276
88981
  }
88277
88982
  const { token, id } = result;
88278
- const tokenPath = join49(homedir16(), ".switchroom", "agents", agentName3, ".vault-token");
88983
+ const tokenPath = join51(homedir16(), ".switchroom", "agents", agentName3, ".vault-token");
88279
88984
  try {
88280
- mkdirSync35(join49(homedir16(), ".switchroom", "agents", agentName3), { recursive: true });
88281
- writeFileSync37(tokenPath, token, { mode: 384 });
88985
+ mkdirSync37(join51(homedir16(), ".switchroom", "agents", agentName3), { recursive: true });
88986
+ writeFileSync40(tokenPath, token, { mode: 384 });
88282
88987
  } catch (err) {
88283
88988
  await switchroomReply(ctx, `**Grant created (${escapeHtmlForTg2(id)}) but token write failed:** ${escapeHtmlForTg2(String(err))}
88284
88989
  _Recover with: \`switchroom vault grant ${escapeHtmlForTg2(agentName3)} --keys ${escapeHtmlForTg2(keyName)} --duration 30d\` on the host._`, { html: true });
@@ -88355,10 +89060,10 @@ async function performVaultAccessApproval(ctx, pending2, stageId, senderId, atte
88355
89060
  return;
88356
89061
  }
88357
89062
  const { token, id } = result;
88358
- const tokenPath = join49(homedir16(), ".switchroom", "agents", pending2.agent, ".vault-token");
89063
+ const tokenPath = join51(homedir16(), ".switchroom", "agents", pending2.agent, ".vault-token");
88359
89064
  try {
88360
- mkdirSync35(join49(homedir16(), ".switchroom", "agents", pending2.agent), { recursive: true });
88361
- writeFileSync37(tokenPath, token, { mode: 384 });
89065
+ mkdirSync37(join51(homedir16(), ".switchroom", "agents", pending2.agent), { recursive: true });
89066
+ writeFileSync40(tokenPath, token, { mode: 384 });
88362
89067
  } catch (err) {
88363
89068
  await switchroomReply(ctx, `**Grant created (${escapeHtmlForTg2(id)}) but token write failed:** ${escapeHtmlForTg2(String(err))}
88364
89069
  _Recover with: \`switchroom vault grant ${escapeHtmlForTg2(pending2.agent)} --keys ${escapeHtmlForTg2(pending2.key)} --duration ${Math.round(pending2.ttl_seconds / 86400)}d\` on the host._`, { html: true });
@@ -89064,10 +89769,10 @@ async function executeGrantWizard(ctx, chatId, state4) {
89064
89769
  return;
89065
89770
  }
89066
89771
  const { token, id } = result;
89067
- const tokenPath = join49(homedir16(), ".switchroom", "agents", state4.agent, ".vault-token");
89772
+ const tokenPath = join51(homedir16(), ".switchroom", "agents", state4.agent, ".vault-token");
89068
89773
  try {
89069
- mkdirSync35(join49(homedir16(), ".switchroom", "agents", state4.agent), { recursive: true });
89070
- writeFileSync37(tokenPath, token, { mode: 384 });
89774
+ mkdirSync37(join51(homedir16(), ".switchroom", "agents", state4.agent), { recursive: true });
89775
+ writeFileSync40(tokenPath, token, { mode: 384 });
89071
89776
  } catch (err) {
89072
89777
  await switchroomReply(ctx, `**Grant created but token write failed:** ${escapeHtmlForTg2(String(err))}`, { html: true });
89073
89778
  return;
@@ -89917,7 +90622,7 @@ bot.command("usage", async (ctx) => {
89917
90622
  await switchroomReply(ctx, "**/usage:** cannot resolve agent dir.", { html: true });
89918
90623
  return;
89919
90624
  }
89920
- const result = await fetchQuota2({ claudeConfigDir: join49(agentDir, ".claude") });
90625
+ const result = await fetchQuota2({ claudeConfigDir: join51(agentDir, ".claude") });
89921
90626
  if (!result.ok) {
89922
90627
  await switchroomReply(ctx, `**/usage:** ${escapeHtmlForTg2(result.reason)}`, { html: true });
89923
90628
  return;
@@ -90197,6 +90902,22 @@ bot.on("callback_query:data", async (ctx) => {
90197
90902
  const outcome = await handleModelMenuCallback(data, modelDeps);
90198
90903
  if (outcome.selectedModel) {
90199
90904
  sessionModelSource.setOverride(outcome.selectedModel);
90905
+ const smDir = resolveAgentDirFromEnv();
90906
+ if (smDir && outcome.selectedModelToken) {
90907
+ try {
90908
+ writeSessionModelFile(smDir, outcome.selectedModelToken, readConfiguredDefaultModel(smDir) ?? resolveMainModel(modelDeps.getConfiguredModel() ?? undefined));
90909
+ } catch (err) {
90910
+ outcome.reply.text += `
90911
+ \u26A0\uFE0F Couldn\u2019t persist the sticky override \u2014 the switch is live now but won\u2019t survive a relaunch.`;
90912
+ process.stderr.write(`telegram gateway: session-model persist failed (menu): ${err?.message ?? String(err)}
90913
+ `);
90914
+ }
90915
+ }
90916
+ }
90917
+ if (outcome.clearedDefault) {
90918
+ const smDir = resolveAgentDirFromEnv();
90919
+ if (smDir)
90920
+ clearSessionModelFile(smDir);
90200
90921
  }
90201
90922
  if (outcome.toastOnly && !didInterimSrEdit)
90202
90923
  return;
@@ -90208,13 +90929,14 @@ bot.on("callback_query:data", async (ctx) => {
90208
90929
  const token = outcome.selectedModelToken;
90209
90930
  if (agentDir && token) {
90210
90931
  try {
90211
- writeFileSync37(join49(agentDir, ".session-model-override"), `${token}
90212
- `, "utf8");
90932
+ writeSessionModelFile(agentDir, token, readConfiguredDefaultModel(agentDir) ?? resolveMainModel(modelDeps.getConfiguredModel() ?? undefined));
90213
90933
  sessionModelSource.setOverride(token);
90214
90934
  } catch (e) {
90215
- process.stderr.write(`telegram gateway: sr-to-claude carrier write failed: ${e?.message ?? String(e)}
90935
+ process.stderr.write(`telegram gateway: sr-to-claude session-model write failed: ${e?.message ?? String(e)}
90216
90936
  `);
90217
90937
  }
90938
+ } else if (agentDir) {
90939
+ clearSessionModelFile(agentDir);
90218
90940
  }
90219
90941
  }
90220
90942
  writeRestartMarker({ chat_id: cbChatId, thread_id: cbThreadId ?? null, ack_message_id: null, ts: Date.now() });
@@ -90426,22 +91148,34 @@ ${preBlock(formatSwitchroomOutput(err.message ?? "unknown error"))}`, { html: tr
90426
91148
  }
90427
91149
  return;
90428
91150
  })();
90429
- let audio = null;
90430
- if (entry.filePath != null) {
90431
- try {
90432
- audio = readFileSync47(entry.filePath);
90433
- } catch {
90434
- audio = null;
91151
+ const tok = token;
91152
+ const sendOpts = {
91153
+ ...cbMessageId != null ? { reply_parameters: { message_id: cbMessageId } } : {},
91154
+ ...cbThreadId != null ? { message_thread_id: cbThreadId } : {}
91155
+ };
91156
+ const sendVerbOpts = {
91157
+ chat_id: cbChatId,
91158
+ verb: "voice-ondemand.sendVoice",
91159
+ ...cbThreadId != null ? { threadId: cbThreadId } : {}
91160
+ };
91161
+ const loadAudio = async () => {
91162
+ let audio = null;
91163
+ if (entry.filePath != null) {
91164
+ try {
91165
+ audio = readFileSync50(entry.filePath);
91166
+ } catch {
91167
+ audio = null;
91168
+ }
91169
+ }
91170
+ if (audio != null) {
91171
+ await ctx.answerCallbackQuery({ text: "\uD83D\uDD0A" }).catch(() => {});
91172
+ return audio;
90435
91173
  }
90436
- }
90437
- if (audio != null) {
90438
- await ctx.answerCallbackQuery({ text: "\uD83D\uDD0A" }).catch(() => {});
90439
- } else {
90440
91174
  await ctx.answerCallbackQuery({ text: "\uD83D\uDD0A Synthesizing\u2026" }).catch(() => {});
90441
91175
  const sidecarToken = await materializeSidecarToken();
90442
91176
  if (!sidecarToken) {
90443
91177
  await ctx.answerCallbackQuery({ text: "Voice sidecar unavailable \u2014 try again later." }).catch(() => {});
90444
- return;
91178
+ return null;
90445
91179
  }
90446
91180
  const result = await synthesizeViaSidecar({
90447
91181
  token: sidecarToken,
@@ -90453,20 +91187,31 @@ ${preBlock(formatSwitchroomOutput(err.message ?? "unknown error"))}`, { html: tr
90453
91187
  process.stderr.write(`telegram gateway: voice-out on-demand: synthesis failed reason=${result.reason}
90454
91188
  `);
90455
91189
  await ctx.answerCallbackQuery({ text: `Voice failed: ${result.reason}` }).catch(() => {});
90456
- return;
91190
+ return null;
90457
91191
  }
90458
- audio = result.audio;
91192
+ return result.audio;
91193
+ };
91194
+ if (entry.telegramFileId != null) {
91195
+ await ctx.answerCallbackQuery({ text: "\uD83D\uDD0A" }).catch(() => {});
91196
+ }
91197
+ const sendResult = await sendVoiceReusingFileId({
91198
+ fileId: entry.telegramFileId ?? null,
91199
+ sendByFileId: (fid) => robustApiCall(() => bot.api.sendVoice(cbChatId, fid, sendOpts), sendVerbOpts),
91200
+ loadAudio,
91201
+ sendByUpload: (audioOut) => robustApiCall(() => bot.api.sendVoice(cbChatId, new import_grammy12.InputFile(Buffer.from(audioOut)), sendOpts), sendVerbOpts),
91202
+ onFileId: (fid) => voiceOnDemandCache.setTelegramFileId(tok, fid),
91203
+ log: (line) => process.stderr.write(line)
91204
+ });
91205
+ if (!sendResult.ok) {
91206
+ if (sendResult.reason === "send-failed") {
91207
+ const err = sendResult.error;
91208
+ const msg2 = err instanceof Error ? err.message : String(err);
91209
+ process.stderr.write(`telegram gateway: voice-out on-demand: sendVoice failed (non-fatal): ${msg2}
91210
+ `);
91211
+ }
91212
+ return;
90459
91213
  }
90460
- const audioOut = audio;
90461
91214
  try {
90462
- await robustApiCall(() => bot.api.sendVoice(cbChatId, new import_grammy12.InputFile(Buffer.from(audioOut)), {
90463
- ...cbMessageId != null ? { reply_parameters: { message_id: cbMessageId } } : {},
90464
- ...cbThreadId != null ? { message_thread_id: cbThreadId } : {}
90465
- }), {
90466
- chat_id: cbChatId,
90467
- verb: "voice-ondemand.sendVoice",
90468
- ...cbThreadId != null ? { threadId: cbThreadId } : {}
90469
- });
90470
91215
  if (cbMessageId != null) {
90471
91216
  await robustApiCall(() => bot.api.editMessageReplyMarkup(cbChatId, cbMessageId, {
90472
91217
  reply_markup: { inline_keyboard: [] }
@@ -90478,7 +91223,7 @@ ${preBlock(formatSwitchroomOutput(err.message ?? "unknown error"))}`, { html: tr
90478
91223
  }
90479
91224
  } catch (err) {
90480
91225
  const msg2 = err instanceof Error ? err.message : String(err);
90481
- process.stderr.write(`telegram gateway: voice-out on-demand: sendVoice failed (non-fatal): ${msg2}
91226
+ process.stderr.write(`telegram gateway: voice-out on-demand: strip-listen-keyboard failed (non-fatal): ${msg2}
90482
91227
  `);
90483
91228
  }
90484
91229
  return;
@@ -90648,7 +91393,7 @@ ${interimLabel}` : interimLabel
90648
91393
  const unifiedDiff = (() => {
90649
91394
  try {
90650
91395
  const cfgPath = process.env.SWITCHROOM_CONFIG ?? SWITCHROOM_CONFIG ?? findConfigFile2();
90651
- const raw = readFileSync47(cfgPath, "utf8");
91396
+ const raw = readFileSync50(cfgPath, "utf8");
90652
91397
  return synthesizeAllowRuleDiff({ agentName: agentName3, rule: chosen.rule, configText: raw });
90653
91398
  } catch (err) {
90654
91399
  process.stderr.write(`telegram gateway: always-allow diff synth failed: ${err.message}
@@ -90837,9 +91582,9 @@ bot.on("message:photo", async (ctx) => {
90837
91582
  fileUniqueId: best.file_unique_id,
90838
91583
  now: Date.now()
90839
91584
  });
90840
- mkdirSync35(INBOX_DIR, { recursive: true, mode: 448 });
91585
+ mkdirSync37(INBOX_DIR, { recursive: true, mode: 448 });
90841
91586
  assertInsideInbox(INBOX_DIR, dlPath);
90842
- writeFileSync37(dlPath, buf, { mode: 384 });
91587
+ writeFileSync40(dlPath, buf, { mode: 384 });
90843
91588
  return dlPath;
90844
91589
  } catch (err) {
90845
91590
  const msg = err instanceof Error ? err.message : "unknown error";
@@ -91932,7 +92677,7 @@ var didOneTimeSetup = false;
91932
92677
  return;
91933
92678
  }
91934
92679
  })();
91935
- const resolvedAgentDirForBootCard = agentDir ?? join49(homedir16(), ".switchroom", "agents", agentSlug);
92680
+ const resolvedAgentDirForBootCard = agentDir ?? join51(homedir16(), ".switchroom", "agents", agentSlug);
91936
92681
  const handle = await startBootCard(chatId, threadId, botApiForCard, {
91937
92682
  agentName: agentDisplayName,
91938
92683
  agentSlug,
@@ -91946,8 +92691,9 @@ var didOneTimeSetup = false;
91946
92691
  probeQuotaViaBroker: (t) => probeQuotaForBootCard(agentSlug, t),
91947
92692
  tmuxSupervisor: process.env.SWITCHROOM_TMUX_SUPERVISOR === "1",
91948
92693
  dockerMode: process.env.SWITCHROOM_RUNTIME === "docker",
91949
- configSnapshotPath: join49(resolvedAgentDirForBootCard, ".config-snapshot.json"),
91950
- bootCardStatePath: join49(resolvedAgentDirForBootCard, ".boot-card-msgid.json"),
92694
+ configSnapshotPath: join51(resolvedAgentDirForBootCard, ".config-snapshot.json"),
92695
+ bootCardStatePath: join51(resolvedAgentDirForBootCard, ".boot-card-msgid.json"),
92696
+ floodStatePath: FLOOD_STATE_PATH,
91951
92697
  ...updateOutcomeLine ? { updateOutcomeLine } : {}
91952
92698
  }, ackMsgId);
91953
92699
  activeBootCard = handle;
@@ -91977,10 +92723,10 @@ var didOneTimeSetup = false;
91977
92723
  try {
91978
92724
  const smAgentDir = resolveAgentDirFromEnv();
91979
92725
  if (smAgentDir) {
91980
- const activePath = join49(smAgentDir, ".active-session-model");
91981
- if (existsSync45(activePath)) {
92726
+ const activePath = join51(smAgentDir, ".active-session-model");
92727
+ if (existsSync47(activePath)) {
91982
92728
  try {
91983
- const launched = readFileSync47(activePath, "utf8").trim();
92729
+ const launched = readFileSync50(activePath, "utf8").trim();
91984
92730
  const configured = (() => {
91985
92731
  const d = switchroomExecJson(["agent", "list"]);
91986
92732
  const raw = d?.agents?.find((a) => a.name === getMyAgentName())?.model ?? null;
@@ -91989,11 +92735,11 @@ var didOneTimeSetup = false;
91989
92735
  sessionModelSource.setOverride(launched.length > 0 && launched !== configured ? launched : null);
91990
92736
  } catch {}
91991
92737
  }
91992
- const alertPath = join49(smAgentDir, ".session-model-alert");
91993
- if (existsSync45(alertPath)) {
92738
+ const alertPath = join51(smAgentDir, ".session-model-alert");
92739
+ if (existsSync47(alertPath)) {
91994
92740
  let alertText = null;
91995
92741
  try {
91996
- alertText = readFileSync47(alertPath, "utf8").trim();
92742
+ alertText = readFileSync50(alertPath, "utf8").trim();
91997
92743
  } catch {
91998
92744
  alertText = null;
91999
92745
  }