switchroom 0.15.40 → 0.15.42

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.
@@ -23922,6 +23922,8 @@ var init_schema = __esm(() => {
23922
23922
  max_memories: exports_external.number().int().min(0).optional().describe("Cap on the number of memories injected into the prompt by " + "auto-recall, regardless of token budget. Plugin default is 12. " + "0 disables the cap (all memories Hindsight returns are injected)."),
23923
23923
  cache_ttl_secs: exports_external.number().int().min(0).optional().describe("Per-session recall cache TTL in seconds. When > 0, identical " + "(prompt, bank) within the same session reuse the cached recall " + "result instead of round-tripping to Hindsight. 0 disables. " + "Default is 600 (10 min) for switchroom-managed agents."),
23924
23924
  min_overlap: exports_external.number().min(0).max(1).optional().describe("Minimum Jaccard token overlap [0.0\u20131.0] between the user " + "prompt and a memory's text for the memory to be injected. " + "Drops low-relevance matches before the count cap so weak hits " + "don't fill the slot on real queries. 0.0 disables (default \u2014 " + "current behaviour). Try 0.10\u20130.20 to start; observe the " + "`overlap_dropped` field via `switchroom memory recall-log`."),
23925
+ types: exports_external.array(exports_external.string()).optional().describe("Hindsight fact types to recall. Switchroom default is " + '["world", "experience", "observation"] \u2014 the synthesized ' + "`observation` tier is on by default. Set to " + '["world", "experience"] to opt out of observation-backed ' + "recall for this agent (or fleet-wide under defaults)."),
23926
+ skip_trivial: exports_external.boolean().optional().describe("Skip recall on plausibly-stateless trivial turns (time/date/" + "greeting). Switchroom default true \u2014 saves the recall arm + " + "injected tokens on turns that never need memory, guarded so it " + "never skips a turn that references user/project/session state. " + "Set false to always run recall."),
23925
23927
  topic_filter_mode: exports_external.enum(["soft-preamble", "hard-filter"]).optional().describe("Supergroup-mode cross-topic memory behaviour. Default " + "(unset) \u2192 soft-preamble: recall returns memories from all " + "topics, and a 'Current topic: \u2026' preamble tells the model " + "to self-scope. hard-filter: drop any recalled memory whose " + "metadata.thread_id differs from the active inbound's topic. " + "Flip to hard-filter when the recall_log shows binding " + "failures (model surfacing the right memory but applying " + "it to the wrong topic).")
23926
23928
  }).optional().describe("Auto-recall tuning knobs")
23927
23929
  }).optional();
@@ -23958,7 +23960,8 @@ var init_schema = __esm(() => {
23958
23960
  SessionSchema = exports_external.object({
23959
23961
  max_idle: exports_external.string().regex(/^\d+[smh]$/, "Duration must be a number followed by s, m, or h (e.g. '2h', '30m')").optional().describe("Start a fresh session if the previous one has been idle " + "longer than this duration. Examples: '2h', '30m', '7200s'."),
23960
23962
  max_turns: exports_external.number().int().positive().optional().describe("Start a fresh session if the previous one has more user " + "turns than this. Useful for preventing context bloat on " + "long-running agents."),
23961
- max_context_tokens: exports_external.number().int().positive().optional().describe("Proactively run /compact when the live context window " + "occupancy (latest assistant turn input + cache-read + " + "cache-creation tokens) reaches this many tokens. Opt-in: " + "unset means rely on Claude Code's native auto-compaction. " + "Useful on large-window models (e.g. 1M Opus) to hold a " + "deliberately lean working context.")
23963
+ max_context_tokens: exports_external.number().int().positive().optional().describe("Proactively run /compact when the live context window " + "occupancy (latest assistant turn input + cache-read + " + "cache-creation tokens) reaches this many tokens. Opt-in: " + "unset means rely on Claude Code's native auto-compaction. " + "Useful on large-window models (e.g. 1M Opus) to hold a " + "deliberately lean working context."),
23964
+ idle_clear_after: exports_external.string().regex(/^\d+[smh]$/, "Duration must be a number followed by s, m, or h (e.g. '3h', '90m')").optional().describe("Auto-run /clear (wipe the working context) after the live " + "session has been idle this long. Defaults to '3h' when unset " + "(on by default); set '0s' to disable. Long-term memory lives " + "in Hindsight, so a clear loses only the in-session thread.")
23962
23965
  }).optional();
23963
23966
  SessionContinuitySchema = exports_external.object({
23964
23967
  enabled: exports_external.boolean().optional().describe("Master switch for the session-handoff briefing (default true)."),
@@ -28926,10 +28929,10 @@ function renderAuthLine(state4, agentName3, now = Date.now()) {
28926
28929
  }
28927
28930
 
28928
28931
  // gateway/quota-cache.ts
28929
- import { existsSync as existsSync26, readFileSync as readFileSync24, writeFileSync as writeFileSync15, mkdirSync as mkdirSync16 } from "fs";
28930
- import { join as join23, dirname as dirname8 } from "path";
28932
+ import { existsSync as existsSync26, readFileSync as readFileSync24, writeFileSync as writeFileSync16, mkdirSync as mkdirSync17 } from "fs";
28933
+ import { join as join24, dirname as dirname8 } from "path";
28931
28934
  function defaultCachePath() {
28932
- return process.env.SWITCHROOM_QUOTA_CACHE_PATH ?? join23(process.env.HOME ?? "/tmp", ".switchroom", "quota-cache.json");
28935
+ return process.env.SWITCHROOM_QUOTA_CACHE_PATH ?? join24(process.env.HOME ?? "/tmp", ".switchroom", "quota-cache.json");
28933
28936
  }
28934
28937
  function readQuotaCache(opts = {}) {
28935
28938
  const path = opts.path ?? defaultCachePath();
@@ -28964,8 +28967,8 @@ function writeQuotaCache(result, opts = {}) {
28964
28967
  result
28965
28968
  };
28966
28969
  try {
28967
- mkdirSync16(dirname8(path), { recursive: true });
28968
- writeFileSync15(path, JSON.stringify(entry, null, 2), { mode: 384 });
28970
+ mkdirSync17(dirname8(path), { recursive: true });
28971
+ writeFileSync16(path, JSON.stringify(entry, null, 2), { mode: 384 });
28969
28972
  } catch {}
28970
28973
  }
28971
28974
  var DEFAULT_TTL_MS4, RATE_LIMIT_TTL_MS;
@@ -28976,7 +28979,7 @@ var init_quota_cache = __esm(() => {
28976
28979
 
28977
28980
  // gateway/boot-probes.ts
28978
28981
  import { readFileSync as readFileSync25, readdirSync as readdirSync4, existsSync as existsSync27 } from "fs";
28979
- import { join as join24 } from "path";
28982
+ import { join as join25 } from "path";
28980
28983
  import { execFile as execFileCb } from "child_process";
28981
28984
  import { promisify as promisify3 } from "util";
28982
28985
  async function withTimeout(label, p, timeoutMs = PROBE_TIMEOUT_MS) {
@@ -29018,8 +29021,8 @@ function mapPlan(billingType, hasExtra) {
29018
29021
  }
29019
29022
  async function probeAccount(agentDir) {
29020
29023
  return withTimeout("Account", (async () => {
29021
- const claudeDir = join24(agentDir, ".claude");
29022
- const claudeJsonPath = join24(claudeDir, ".claude.json");
29024
+ const claudeDir = join25(agentDir, ".claude");
29025
+ const claudeJsonPath = join25(claudeDir, ".claude.json");
29023
29026
  let cfg = {};
29024
29027
  try {
29025
29028
  const raw = readFileSync25(claudeJsonPath, "utf8");
@@ -29040,8 +29043,8 @@ async function probeAccount(agentDir) {
29040
29043
  let tokenStr = "";
29041
29044
  let status = "ok";
29042
29045
  for (const candidate of [
29043
- join24(claudeDir, ".oauth-token.meta.json"),
29044
- join24(claudeDir, "accounts", "default", ".oauth-token.meta.json")
29046
+ join25(claudeDir, ".oauth-token.meta.json"),
29047
+ join25(claudeDir, "accounts", "default", ".oauth-token.meta.json")
29045
29048
  ]) {
29046
29049
  if (existsSync27(candidate)) {
29047
29050
  try {
@@ -29427,9 +29430,9 @@ async function probeQuota(claudeConfigDir, _agentDir, fetchImpl = fetch, opts =
29427
29430
  let claudeDirForProbe = null;
29428
29431
  for (const candidate of [
29429
29432
  claudeConfigDir,
29430
- join24(claudeConfigDir, "accounts", "default")
29433
+ join25(claudeConfigDir, "accounts", "default")
29431
29434
  ]) {
29432
- if (existsSync27(join24(candidate, ".oauth-token"))) {
29435
+ if (existsSync27(join25(candidate, ".oauth-token"))) {
29433
29436
  claudeDirForProbe = candidate;
29434
29437
  break;
29435
29438
  }
@@ -29664,7 +29667,7 @@ async function probeSkills(agentDir, opts = {}) {
29664
29667
  return withTimeout("Skills", (async () => {
29665
29668
  const fs2 = opts.fs ?? realSkillsFs;
29666
29669
  const max = opts.maxNamesShown ?? 3;
29667
- const skillsDir = join24(agentDir, ".claude", "skills");
29670
+ const skillsDir = join25(agentDir, ".claude", "skills");
29668
29671
  if (!fs2.exists(skillsDir)) {
29669
29672
  return { status: "ok", label: "Skills", detail: "no skills dir" };
29670
29673
  }
@@ -29679,17 +29682,17 @@ async function probeSkills(agentDir, opts = {}) {
29679
29682
  }
29680
29683
  const dangling = [];
29681
29684
  for (const name of entries) {
29682
- const skillPath = join24(skillsDir, name);
29685
+ const skillPath = join25(skillsDir, name);
29683
29686
  if (!fs2.exists(skillPath)) {
29684
29687
  dangling.push(name);
29685
29688
  continue;
29686
29689
  }
29687
- const skillMd = join24(skillPath, "SKILL.md");
29690
+ const skillMd = join25(skillPath, "SKILL.md");
29688
29691
  if (!fs2.exists(skillMd) && !fs2.exists(skillPath + ".md")) {
29689
29692
  continue;
29690
29693
  }
29691
29694
  }
29692
- const overlayDir = opts.overlaySkillsDir ?? join24(agentDir, "skills.d");
29695
+ const overlayDir = opts.overlaySkillsDir ?? join25(agentDir, "skills.d");
29693
29696
  const overlaySlugs = new Set;
29694
29697
  if (fs2.exists(overlayDir)) {
29695
29698
  let overlayEntries = [];
@@ -29731,7 +29734,7 @@ function renderBucketedSkills(switchroom, agent) {
29731
29734
  }
29732
29735
  async function probeConnections(agentDir, opts = {}) {
29733
29736
  return withTimeout("Connections", (async () => {
29734
- const path = join24(agentDir, ".claude", "connection-health.json");
29737
+ const path = join25(agentDir, ".claude", "connection-health.json");
29735
29738
  const read = opts.readFileImpl ?? ((p) => readFileSync25(p, "utf8"));
29736
29739
  let issues = [];
29737
29740
  try {
@@ -29780,7 +29783,7 @@ var init_boot_probes = __esm(() => {
29780
29783
  });
29781
29784
 
29782
29785
  // gateway/boot-issue-cache.ts
29783
- import { existsSync as existsSync28, readFileSync as readFileSync26, writeFileSync as writeFileSync16, mkdirSync as mkdirSync17, renameSync as renameSync9 } from "fs";
29786
+ import { existsSync as existsSync28, readFileSync as readFileSync26, writeFileSync as writeFileSync17, mkdirSync as mkdirSync18, renameSync as renameSync9 } from "fs";
29784
29787
  import { dirname as dirname9 } from "path";
29785
29788
  function fingerprintProbe(key, r) {
29786
29789
  if (r.status === "ok")
@@ -29907,9 +29910,9 @@ function applyAndSave(path, cache, diff) {
29907
29910
  }
29908
29911
  }
29909
29912
  try {
29910
- mkdirSync17(dirname9(path), { recursive: true });
29913
+ mkdirSync18(dirname9(path), { recursive: true });
29911
29914
  const tmp = `${path}.tmp`;
29912
- writeFileSync16(tmp, JSON.stringify(next), { mode: 384 });
29915
+ writeFileSync17(tmp, JSON.stringify(next), { mode: 384 });
29913
29916
  renameSync9(tmp, path);
29914
29917
  } catch {}
29915
29918
  return next;
@@ -29923,7 +29926,7 @@ var init_boot_issue_cache = __esm(() => {
29923
29926
 
29924
29927
  // gateway/config-snapshot.ts
29925
29928
  import { createHash as createHash2 } from "crypto";
29926
- import { existsSync as existsSync29, readFileSync as readFileSync27, writeFileSync as writeFileSync17, mkdirSync as mkdirSync18, renameSync as renameSync10 } from "fs";
29929
+ import { existsSync as existsSync29, readFileSync as readFileSync27, writeFileSync as writeFileSync18, mkdirSync as mkdirSync19, renameSync as renameSync10 } from "fs";
29927
29930
  import { dirname as dirname10 } from "path";
29928
29931
  function hashStringArray(items) {
29929
29932
  if (!items || items.length === 0)
@@ -30022,16 +30025,16 @@ function loadSnapshot(path, now = Date.now) {
30022
30025
  }
30023
30026
  function persistSnapshot(path, snapshot) {
30024
30027
  try {
30025
- mkdirSync18(dirname10(path), { recursive: true });
30028
+ mkdirSync19(dirname10(path), { recursive: true });
30026
30029
  const tmp = `${path}.tmp`;
30027
- writeFileSync17(tmp, JSON.stringify(snapshot), { mode: 384 });
30030
+ writeFileSync18(tmp, JSON.stringify(snapshot), { mode: 384 });
30028
30031
  renameSync10(tmp, path);
30029
30032
  } catch {}
30030
30033
  }
30031
30034
  var init_config_snapshot = () => {};
30032
30035
 
30033
30036
  // gateway/boot-card-msgid.ts
30034
- import { readFileSync as readFileSync28, writeFileSync as writeFileSync18 } from "node:fs";
30037
+ import { readFileSync as readFileSync28, writeFileSync as writeFileSync19 } from "node:fs";
30035
30038
  function bootCardChatKey(chatId, threadId) {
30036
30039
  return `${chatId}:${threadId ?? ""}`;
30037
30040
  }
@@ -30056,7 +30059,7 @@ function saveBootCardMsgId(path, chatKey3, messageId) {
30056
30059
  if (store2[chatKey3] === messageId)
30057
30060
  return;
30058
30061
  store2[chatKey3] = messageId;
30059
- writeFileSync18(path, JSON.stringify(store2), "utf8");
30062
+ writeFileSync19(path, JSON.stringify(store2), "utf8");
30060
30063
  } catch {}
30061
30064
  }
30062
30065
  var init_boot_card_msgid = () => {};
@@ -30071,7 +30074,7 @@ __export(exports_boot_card, {
30071
30074
  renderBootCard: () => renderBootCard,
30072
30075
  renderAccountRows: () => renderAuthLine
30073
30076
  });
30074
- import { join as join25 } from "path";
30077
+ import { join as join26 } from "path";
30075
30078
  function resolvePersonaName(slug, loadConfig3) {
30076
30079
  try {
30077
30080
  const config = loadConfig3 ? loadConfig3() : loadConfig();
@@ -30161,7 +30164,7 @@ function renderBootCard(opts) {
30161
30164
  `);
30162
30165
  }
30163
30166
  async function runAllProbes(opts) {
30164
- const claudeDir = join25(opts.agentDir, ".claude");
30167
+ const claudeDir = join26(opts.agentDir, ".claude");
30165
30168
  const probes = {};
30166
30169
  const slug = opts.agentSlug ?? opts.agentName;
30167
30170
  await Promise.allSettled([
@@ -30917,7 +30920,7 @@ __export(exports_tmux, {
30917
30920
  captureAgentPane: () => captureAgentPane
30918
30921
  });
30919
30922
  import { execFileSync as execFileSync4 } from "node:child_process";
30920
- import { mkdirSync as mkdirSync25, readdirSync as readdirSync6, statSync as statSync12, unlinkSync as unlinkSync13, writeFileSync as writeFileSync24 } from "node:fs";
30923
+ import { mkdirSync as mkdirSync26, readdirSync as readdirSync6, statSync as statSync12, unlinkSync as unlinkSync13, writeFileSync as writeFileSync25 } from "node:fs";
30921
30924
  import { resolve as resolve7 } from "node:path";
30922
30925
  function captureAgentPane(opts) {
30923
30926
  const { agentName: agentName3, agentDir, reason } = opts;
@@ -30929,7 +30932,7 @@ function captureAgentPane(opts) {
30929
30932
  const reasonSlug = sanitizeReason(reason);
30930
30933
  const outPath = resolve7(outDir, `${ts}-${reasonSlug}.txt`);
30931
30934
  try {
30932
- mkdirSync25(outDir, { recursive: true, mode: 493 });
30935
+ mkdirSync26(outDir, { recursive: true, mode: 493 });
30933
30936
  } catch (err) {
30934
30937
  const msg = `mkdir crash-reports failed: ${err.message}`;
30935
30938
  console.error(`[tmux-capture] ${agentName3}: ${msg}`);
@@ -30963,7 +30966,7 @@ function captureAgentPane(opts) {
30963
30966
  ` + `
30964
30967
  `;
30965
30968
  try {
30966
- writeFileSync24(outPath, Buffer.concat([Buffer.from(header, "utf8"), body]), {
30969
+ writeFileSync25(outPath, Buffer.concat([Buffer.from(header, "utf8"), body]), {
30967
30970
  mode: 420
30968
30971
  });
30969
30972
  } catch (err) {
@@ -31558,8 +31561,8 @@ import { randomBytes as randomBytes6 } from "crypto";
31558
31561
  import { execFileSync as execFileSync5, execSync as execSync2, spawn as spawn2 } from "child_process";
31559
31562
  import {
31560
31563
  readFileSync as readFileSync36,
31561
- writeFileSync as writeFileSync25,
31562
- mkdirSync as mkdirSync26,
31564
+ writeFileSync as writeFileSync26,
31565
+ mkdirSync as mkdirSync27,
31563
31566
  readdirSync as readdirSync7,
31564
31567
  rmSync as rmSync4,
31565
31568
  statSync as statSync13,
@@ -31573,7 +31576,7 @@ import {
31573
31576
  appendFileSync as appendFileSync5
31574
31577
  } from "fs";
31575
31578
  import { homedir as homedir14 } from "os";
31576
- import { join as join35, extname, sep as sep3, basename as basename10 } from "path";
31579
+ import { join as join36, extname, sep as sep3, basename as basename10 } from "path";
31577
31580
 
31578
31581
  // plugin-logger.ts
31579
31582
  import { appendFileSync, mkdirSync, renameSync, statSync, existsSync } from "fs";
@@ -43161,7 +43164,7 @@ function helpText(agentName3) {
43161
43164
  ``,
43162
43165
  `This bot is the <b>${escapeHtml6(agentName3)}</b> agent. Text and photos route through to it; replies, reactions and progress cards come back.`,
43163
43166
  ``,
43164
- `Tool approvals surface as inline buttons (\u2705 / \u274c) or via <code>/approve</code>, <code>/deny</code>, <code>/pending</code>. Start a fresh session with <code>/new</code> or <code>/reset</code>.`,
43167
+ `Tool approvals surface as inline buttons (\u2705 / \u274c) or via <code>/approve</code>, <code>/deny</code>, <code>/pending</code>. Start a fresh session with <code>/new</code>, or trim/clear context with <code>/compact</code> / <code>/clear</code>.`,
43165
43168
  ``,
43166
43169
  `<code>/start</code> \u2014 pairing instructions`,
43167
43170
  `<code>/status</code> \u2014 agent, model, auth`,
@@ -43229,7 +43232,8 @@ var TELEGRAM_MENU_COMMANDS = [
43229
43232
  { command: "help", description: "What this bot can do" },
43230
43233
  { command: "status", description: "Agent, model, auth" },
43231
43234
  { command: "new", description: "Fresh session (flush handoff, restart)" },
43232
- { command: "reset", description: "Alias of /new" },
43235
+ { command: "compact", description: "Compact context (summarize, keep the thread)" },
43236
+ { command: "clear", description: "Clear context (fresh slate; memory in Hindsight)" },
43233
43237
  { command: "approve", description: "Approve pending tool permission" },
43234
43238
  { command: "deny", description: "Deny pending tool permission" },
43235
43239
  { command: "pending", description: "List pending permission prompts" },
@@ -43237,7 +43241,6 @@ var TELEGRAM_MENU_COMMANDS = [
43237
43241
  { command: "restart", description: "Restart the agent (drain by default)" },
43238
43242
  { command: "version", description: "Show versions + running agent health" },
43239
43243
  { command: "logs", description: "Show recent agent logs" },
43240
- { command: "inject", description: "Inject a Claude Code slash command (e.g. /cost)" },
43241
43244
  { command: "model", description: "Show or switch the Claude model" },
43242
43245
  { command: "effort", description: "Show or switch the reasoning effort" },
43243
43246
  { command: "doctor", description: "Health check (deps, services, MCP)" },
@@ -43254,7 +43257,8 @@ function switchroomHelpText(agentName3) {
43254
43257
  ``,
43255
43258
  `<b>Session &amp; approvals</b>`,
43256
43259
  `<code>/new</code> \u2014 fresh session (flush handoff, restart)`,
43257
- `<code>/reset</code> \u2014 alias of /new`,
43260
+ `<code>/compact</code> \u2014 compact context (summarize, keep the thread)`,
43261
+ `<code>/clear</code> \u2014 clear context (fresh slate; memory in Hindsight)`,
43258
43262
  `<code>/approve [id]</code> \u2014 approve pending tool permission`,
43259
43263
  `<code>/deny [id]</code> \u2014 deny pending tool permission`,
43260
43264
  `<code>/pending</code> \u2014 list pending permission prompts`,
@@ -43308,10 +43312,6 @@ function newSessionAckText(agentName3, flushedHandoff) {
43308
43312
  const tail = flushedHandoff ? " \u00b7 flushed handoff" : "";
43309
43313
  return `\uD83C\uDD95 Started fresh session for <b>${escapeHtml6(agentName3)}</b>${tail} \u00b7 restarting\u2026`;
43310
43314
  }
43311
- function resetSessionAckText(agentName3, flushedHandoff) {
43312
- const tail = flushedHandoff ? " \u00b7 flushed handoff" : "";
43313
- return `\uD83D\uDD04 Reset session for <b>${escapeHtml6(agentName3)}</b>${tail} \u00b7 restarting\u2026`;
43314
- }
43315
43315
 
43316
43316
  // gateway/auth-status-adapter.ts
43317
43317
  function formatExpiresInRelative(expiresAt, now = Date.now()) {
@@ -46381,6 +46381,38 @@ function numField(obj, key) {
46381
46381
  return 0;
46382
46382
  }
46383
46383
 
46384
+ // gateway/context-occupancy.ts
46385
+ import { mkdirSync as mkdirSync12, writeFileSync as writeFileSync9 } from "node:fs";
46386
+ import { join as join18 } from "node:path";
46387
+ var CONTEXT_OCCUPANCY_FILENAME = "context-occupancy.json";
46388
+ var TIGHT_FRACTION = 0.8;
46389
+ function buildContextOccupancy(occupancy, cap, now) {
46390
+ if (!Number.isFinite(occupancy) || occupancy < 0) {
46391
+ return { occupancy: 0, cap: cap ?? null, headroom: null, pct: null, state: "unknown", computedAt: now };
46392
+ }
46393
+ const c = cap != null && cap > 0 ? cap : null;
46394
+ if (c == null) {
46395
+ return { occupancy, cap: null, headroom: null, pct: null, state: "ok", computedAt: now };
46396
+ }
46397
+ const pct = occupancy / c;
46398
+ return {
46399
+ occupancy,
46400
+ cap: c,
46401
+ headroom: c - occupancy,
46402
+ pct,
46403
+ state: pct >= TIGHT_FRACTION ? "tight" : "ok",
46404
+ computedAt: now
46405
+ };
46406
+ }
46407
+ function writeContextOccupancySnapshot(stateDir, snapshot, deps) {
46408
+ try {
46409
+ const path = join18(stateDir, CONTEXT_OCCUPANCY_FILENAME);
46410
+ (deps?.mkdir ?? ((p, o) => mkdirSync12(p, o)))(stateDir, { recursive: true });
46411
+ (deps?.writeFile ?? ((p, d) => writeFileSync9(p, d)))(path, JSON.stringify(snapshot, null, 2) + `
46412
+ `);
46413
+ } catch {}
46414
+ }
46415
+
46384
46416
  // gateway/proactive-compact.ts
46385
46417
  var COMPACT_REARM_FRACTION = 0.6;
46386
46418
  var COMPACT_COOLDOWN_TURNS = 3;
@@ -46407,6 +46439,36 @@ function decideProactiveCompact(state3, occupancy, cap) {
46407
46439
  };
46408
46440
  }
46409
46441
 
46442
+ // gateway/idle-clear.ts
46443
+ var DEFAULT_IDLE_CLEAR_MS = 3 * 60 * 60 * 1000;
46444
+ function decideIdleClear(state3, now) {
46445
+ if (state3.idleClearMs <= 0)
46446
+ return { clear: false };
46447
+ if (state3.turnInFlight)
46448
+ return { clear: false };
46449
+ if (state3.alreadyCleared)
46450
+ return { clear: false };
46451
+ if (now - state3.lastActivityAt < state3.idleClearMs)
46452
+ return { clear: false };
46453
+ return { clear: true };
46454
+ }
46455
+ function idleDurationToMs(raw) {
46456
+ const m = /^(\d+)([smh])$/.exec(raw.trim());
46457
+ if (!m)
46458
+ return null;
46459
+ const n = Number(m[1]);
46460
+ switch (m[2]) {
46461
+ case "s":
46462
+ return n * 1000;
46463
+ case "m":
46464
+ return n * 60000;
46465
+ case "h":
46466
+ return n * 3600000;
46467
+ default:
46468
+ return null;
46469
+ }
46470
+ }
46471
+
46410
46472
  // gateway/compact-notify.ts
46411
46473
  function idleCompactNotifyState() {
46412
46474
  return { phase: "idle", fileAtStart: null };
@@ -46980,13 +47042,13 @@ function startWebhookIngestServer(opts) {
46980
47042
  }
46981
47043
 
46982
47044
  // ../src/web/webhook-gateway-record.ts
46983
- import { appendFileSync as appendFileSync4, mkdirSync as mkdirSync14 } from "fs";
46984
- import { join as join20 } from "path";
47045
+ import { appendFileSync as appendFileSync4, mkdirSync as mkdirSync15 } from "fs";
47046
+ import { join as join21 } from "path";
46985
47047
  import { homedir as homedir10 } from "os";
46986
47048
 
46987
47049
  // ../src/web/webhook-handler.ts
46988
- import { appendFileSync as appendFileSync3, existsSync as existsSync22, mkdirSync as mkdirSync12, readFileSync as readFileSync16, writeFileSync as writeFileSync9 } from "fs";
46989
- import { join as join18 } from "path";
47050
+ import { appendFileSync as appendFileSync3, existsSync as existsSync22, mkdirSync as mkdirSync13, readFileSync as readFileSync16, writeFileSync as writeFileSync10 } from "fs";
47051
+ import { join as join19 } from "path";
46990
47052
  var DEDUP_MAX = 1000;
46991
47053
  var DEDUP_TTL_MS = 24 * 60 * 60 * 1000;
46992
47054
  function loadDedupFile(path) {
@@ -47007,7 +47069,7 @@ function saveDedupFile(path, deliveries, now) {
47007
47069
  }
47008
47070
  const sorted = Object.entries(pruned).sort((a, b) => b[1] - a[1]).slice(0, DEDUP_MAX);
47009
47071
  const final = Object.fromEntries(sorted);
47010
- writeFileSync9(path, JSON.stringify({ deliveries: final }), {
47072
+ writeFileSync10(path, JSON.stringify({ deliveries: final }), {
47011
47073
  mode: 384
47012
47074
  });
47013
47075
  }
@@ -47015,8 +47077,8 @@ var agentDedupCache = new Map;
47015
47077
  function createFileDedupStore(resolveAgentDir) {
47016
47078
  return {
47017
47079
  check(agent, deliveryId, now) {
47018
- const telegramDir = join18(resolveAgentDir(agent), "telegram");
47019
- const filePath = join18(telegramDir, "webhook-dedup.json");
47080
+ const telegramDir = join19(resolveAgentDir(agent), "telegram");
47081
+ const filePath = join19(telegramDir, "webhook-dedup.json");
47020
47082
  if (!agentDedupCache.has(agent)) {
47021
47083
  agentDedupCache.set(agent, loadDedupFile(filePath));
47022
47084
  }
@@ -47026,7 +47088,7 @@ function createFileDedupStore(resolveAgentDir) {
47026
47088
  }
47027
47089
  deliveries[deliveryId] = now;
47028
47090
  try {
47029
- mkdirSync12(telegramDir, { recursive: true });
47091
+ mkdirSync13(telegramDir, { recursive: true });
47030
47092
  saveDedupFile(filePath, deliveries, now);
47031
47093
  } catch {}
47032
47094
  return;
@@ -47037,8 +47099,8 @@ var tokenBuckets = new Map;
47037
47099
  var throttleIssueWindow = new Map;
47038
47100
 
47039
47101
  // ../src/web/webhook-dispatch.ts
47040
- import { existsSync as existsSync23, mkdirSync as mkdirSync13, readFileSync as readFileSync17, writeFileSync as writeFileSync10 } from "fs";
47041
- import { join as join19 } from "path";
47102
+ import { existsSync as existsSync23, mkdirSync as mkdirSync14, readFileSync as readFileSync17, writeFileSync as writeFileSync11 } from "fs";
47103
+ import { join as join20 } from "path";
47042
47104
  import { homedir as homedir9 } from "os";
47043
47105
 
47044
47106
  // ../src/agent-scheduler/ipc-client.ts
@@ -47347,7 +47409,7 @@ function loadCooldownFile(path) {
47347
47409
  }
47348
47410
  function saveCooldownFile(path, dispatches) {
47349
47411
  try {
47350
- writeFileSync10(path, JSON.stringify({ dispatches }), {
47412
+ writeFileSync11(path, JSON.stringify({ dispatches }), {
47351
47413
  mode: 384
47352
47414
  });
47353
47415
  } catch {}
@@ -47358,8 +47420,8 @@ function createFileCooldownStore(resolveAgentDir) {
47358
47420
  isCoolingDown(agent, key, cooldownMs, now) {
47359
47421
  if (cooldownMs <= 0)
47360
47422
  return false;
47361
- const telegramDir = join19(resolveAgentDir(agent), "telegram");
47362
- const filePath = join19(telegramDir, "webhook-cooldown.json");
47423
+ const telegramDir = join20(resolveAgentDir(agent), "telegram");
47424
+ const filePath = join20(telegramDir, "webhook-cooldown.json");
47363
47425
  if (!cache.has(agent)) {
47364
47426
  cache.set(agent, loadCooldownFile(filePath));
47365
47427
  }
@@ -47370,7 +47432,7 @@ function createFileCooldownStore(resolveAgentDir) {
47370
47432
  }
47371
47433
  dispatches[key] = now;
47372
47434
  try {
47373
- mkdirSync13(telegramDir, { recursive: true });
47435
+ mkdirSync14(telegramDir, { recursive: true });
47374
47436
  saveCooldownFile(filePath, dispatches);
47375
47437
  } catch {}
47376
47438
  return false;
@@ -47416,9 +47478,9 @@ async function defaultInject(socketPath, agentName3, inbound) {
47416
47478
  }
47417
47479
  function injectWebhookInbound(agent, prompt, ctx, deps = {}) {
47418
47480
  const log = deps.log ?? ((s) => process.stderr.write(s));
47419
- const resolveAgentDir = deps.resolveAgentDir ?? ((a) => join19(homedir9(), ".switchroom", "agents", a));
47481
+ const resolveAgentDir = deps.resolveAgentDir ?? ((a) => join20(homedir9(), ".switchroom", "agents", a));
47420
47482
  const now = (deps.now ?? Date.now)();
47421
- const socketPath = join19(resolveAgentDir(agent), "telegram", "gateway.sock");
47483
+ const socketPath = join20(resolveAgentDir(agent), "telegram", "gateway.sock");
47422
47484
  const inbound = {
47423
47485
  type: "inbound",
47424
47486
  chatId: ctx.chatId,
@@ -47490,7 +47552,7 @@ function evaluateDispatch(args, deps = {}) {
47490
47552
  const log = deps.log ?? ((s) => process.stderr.write(s));
47491
47553
  const now = (deps.now ?? Date.now)();
47492
47554
  const nowDate = deps.nowDate ?? (() => new Date(now));
47493
- const resolveAgentDir = deps.resolveAgentDir ?? ((a) => join19(homedir9(), ".switchroom", "agents", a));
47555
+ const resolveAgentDir = deps.resolveAgentDir ?? ((a) => join20(homedir9(), ".switchroom", "agents", a));
47494
47556
  const cooldownStore = deps.cooldownStore ?? createFileCooldownStore(resolveAgentDir);
47495
47557
  if (!DISPATCH_SOURCES.includes(args.source))
47496
47558
  return 0;
@@ -47566,10 +47628,10 @@ function resolveChannelTarget(config, agentName3) {
47566
47628
  function recordWebhookEvent(rec, deps = {}) {
47567
47629
  const log = deps.log ?? ((s) => process.stderr.write(s));
47568
47630
  const now = rec.ts || (deps.now ?? Date.now)();
47569
- const resolveAgentDir = deps.resolveAgentDir ?? ((a) => join20(homedir10(), ".switchroom", "agents", a));
47631
+ const resolveAgentDir = deps.resolveAgentDir ?? ((a) => join21(homedir10(), ".switchroom", "agents", a));
47570
47632
  const dedupStore = deps.dedupStore ?? createFileDedupStore(resolveAgentDir);
47571
47633
  const agent = rec.agent;
47572
- const telegramDir = join20(resolveAgentDir(agent), "telegram");
47634
+ const telegramDir = join21(resolveAgentDir(agent), "telegram");
47573
47635
  if (rec.source === "github" && rec.delivery_id) {
47574
47636
  const originalTs = dedupStore.check(agent, rec.delivery_id, now);
47575
47637
  if (originalTs !== undefined) {
@@ -47578,9 +47640,9 @@ function recordWebhookEvent(rec, deps = {}) {
47578
47640
  return { status: "deduped", ts: originalTs };
47579
47641
  }
47580
47642
  }
47581
- const logPath = join20(telegramDir, "webhook-events.jsonl");
47643
+ const logPath = join21(telegramDir, "webhook-events.jsonl");
47582
47644
  try {
47583
- mkdirSync14(telegramDir, { recursive: true });
47645
+ mkdirSync15(telegramDir, { recursive: true });
47584
47646
  const record = {
47585
47647
  ts: now,
47586
47648
  source: rec.source,
@@ -50255,6 +50317,20 @@ function createPollHealthCheck(options) {
50255
50317
  };
50256
50318
  }
50257
50319
 
50320
+ // gateway/poll-stall-recovery.ts
50321
+ var POLL_STALL_EXIT_CODE = 1;
50322
+ function recoverFromPollStall(deps = {}) {
50323
+ const exit = deps.exit ?? ((code) => process.exit(code));
50324
+ const log = deps.log ?? ((msg) => {
50325
+ process.stderr.write(msg.endsWith(`
50326
+ `) ? msg : msg + `
50327
+ `);
50328
+ });
50329
+ const agentName3 = deps.agentName ?? "-";
50330
+ log(`telegram gateway: poll.health_check.stall_recovery exiting code=${POLL_STALL_EXIT_CODE} ` + `pid=${process.pid} agent=${agentName3} \u2014 supervisor will restart the gateway with a fresh runner ` + `(not awaiting runnerHandle.stop(): grammy stop() blocks on a non-abortable getUpdates retry backoff during an outage)`);
50331
+ exit(POLL_STALL_EXIT_CODE);
50332
+ }
50333
+
50258
50334
  // gateway/reaction-trigger.ts
50259
50335
  var REACTIONS_DEFAULTS = Object.freeze({
50260
50336
  enabled: true,
@@ -50479,10 +50555,10 @@ function escapeBody2(s) {
50479
50555
  }
50480
50556
 
50481
50557
  // gateway/pid-file.ts
50482
- import { writeFileSync as writeFileSync11, readFileSync as readFileSync18, unlinkSync as unlinkSync7, renameSync as renameSync6 } from "node:fs";
50558
+ import { writeFileSync as writeFileSync12, readFileSync as readFileSync18, unlinkSync as unlinkSync7, renameSync as renameSync6 } from "node:fs";
50483
50559
  function writePidFile(path, record) {
50484
50560
  const tmp = `${path}.tmp-${process.pid}-${Date.now()}`;
50485
- writeFileSync11(tmp, JSON.stringify(record), "utf-8");
50561
+ writeFileSync12(tmp, JSON.stringify(record), "utf-8");
50486
50562
  renameSync6(tmp, path);
50487
50563
  }
50488
50564
  function clearPidFile(path) {
@@ -50704,10 +50780,10 @@ function safeCount(fn) {
50704
50780
  }
50705
50781
 
50706
50782
  // gateway/session-marker.ts
50707
- import { writeFileSync as writeFileSync12, readFileSync as readFileSync20, renameSync as renameSync7, unlinkSync as unlinkSync8 } from "node:fs";
50783
+ import { writeFileSync as writeFileSync13, readFileSync as readFileSync20, renameSync as renameSync7, unlinkSync as unlinkSync8 } from "node:fs";
50708
50784
  function writeSessionMarker(path, marker) {
50709
50785
  const tmp = `${path}.tmp-${process.pid}-${Date.now()}`;
50710
- writeFileSync12(tmp, JSON.stringify(marker), "utf-8");
50786
+ writeFileSync13(tmp, JSON.stringify(marker), "utf-8");
50711
50787
  renameSync7(tmp, path);
50712
50788
  }
50713
50789
  function readSessionMarker(path) {
@@ -50734,11 +50810,11 @@ function shouldFireRestartBanner(input) {
50734
50810
  }
50735
50811
 
50736
50812
  // gateway/clean-shutdown-marker.ts
50737
- import { writeFileSync as writeFileSync13, readFileSync as readFileSync21, renameSync as renameSync8, unlinkSync as unlinkSync9 } from "node:fs";
50813
+ import { writeFileSync as writeFileSync14, readFileSync as readFileSync21, renameSync as renameSync8, unlinkSync as unlinkSync9 } from "node:fs";
50738
50814
  var DEFAULT_MAX_AGE_MS = 60000;
50739
50815
  function writeCleanShutdownMarker(path, marker) {
50740
50816
  const tmp = `${path}.tmp-${process.pid}-${Date.now()}`;
50741
- writeFileSync13(tmp, JSON.stringify(marker), "utf-8");
50817
+ writeFileSync14(tmp, JSON.stringify(marker), "utf-8");
50742
50818
  renameSync8(tmp, path);
50743
50819
  }
50744
50820
  function readCleanShutdownMarker(path) {
@@ -51331,7 +51407,7 @@ import {
51331
51407
  readdirSync as readdirSync3,
51332
51408
  readFileSync as readFileSync23
51333
51409
  } from "fs";
51334
- import { join as join22 } from "path";
51410
+ import { join as join23 } from "path";
51335
51411
 
51336
51412
  // operator-events.ts
51337
51413
  function classifyClaudeError(raw) {
@@ -51817,18 +51893,18 @@ function bumpSubagentActivity(db2, args) {
51817
51893
  import {
51818
51894
  closeSync as closeSync3,
51819
51895
  existsSync as existsSync24,
51820
- mkdirSync as mkdirSync15,
51896
+ mkdirSync as mkdirSync16,
51821
51897
  openSync as openSync3,
51822
51898
  readFileSync as readFileSync22,
51823
51899
  statSync as statSync6,
51824
51900
  unlinkSync as unlinkSync10,
51825
51901
  utimesSync,
51826
- writeFileSync as writeFileSync14
51902
+ writeFileSync as writeFileSync15
51827
51903
  } from "node:fs";
51828
- import { join as join21 } from "node:path";
51904
+ import { join as join22 } from "node:path";
51829
51905
  var TURN_ACTIVE_MARKER_FILE = "turn-active.json";
51830
51906
  function touchTurnActiveMarker(stateDir) {
51831
- const path = join21(stateDir, TURN_ACTIVE_MARKER_FILE);
51907
+ const path = join22(stateDir, TURN_ACTIVE_MARKER_FILE);
51832
51908
  if (!existsSync24(path))
51833
51909
  return;
51834
51910
  const now = new Date;
@@ -52355,8 +52431,8 @@ function startSubagentWatcher(config) {
52355
52431
  function rescanSubagentDirs() {
52356
52432
  if (stopped)
52357
52433
  return;
52358
- const claudeHome = join22(agentDir, ".claude");
52359
- const projectsRoot = join22(claudeHome, "projects");
52434
+ const claudeHome = join23(agentDir, ".claude");
52435
+ const projectsRoot = join23(claudeHome, "projects");
52360
52436
  if (!fs2.existsSync(projectsRoot))
52361
52437
  return;
52362
52438
  let projectDirs;
@@ -52373,7 +52449,7 @@ function startSubagentWatcher(config) {
52373
52449
  }
52374
52450
  continue;
52375
52451
  }
52376
- const projectPath = join22(projectsRoot, pDir);
52452
+ const projectPath = join23(projectsRoot, pDir);
52377
52453
  let sessionDirs;
52378
52454
  try {
52379
52455
  sessionDirs = fs2.readdirSync(projectPath);
@@ -52383,7 +52459,7 @@ function startSubagentWatcher(config) {
52383
52459
  for (const sDir of sessionDirs) {
52384
52460
  if (sDir.endsWith(".jsonl"))
52385
52461
  continue;
52386
- const subagentsPath = join22(projectPath, sDir, "subagents");
52462
+ const subagentsPath = join23(projectPath, sDir, "subagents");
52387
52463
  if (!fs2.existsSync(subagentsPath))
52388
52464
  continue;
52389
52465
  if (!dirWatchers.has(subagentsPath)) {
@@ -52391,7 +52467,7 @@ function startSubagentWatcher(config) {
52391
52467
  const w = fs2.watch(subagentsPath, (_event, filename) => {
52392
52468
  if (!filename || !filename.toString().startsWith("agent-") || !filename.toString().endsWith(".jsonl"))
52393
52469
  return;
52394
- const filePath = join22(subagentsPath, filename.toString());
52470
+ const filePath = join23(subagentsPath, filename.toString());
52395
52471
  if (!knownFiles.has(filePath)) {
52396
52472
  scanSubagentsDir(subagentsPath);
52397
52473
  }
@@ -52416,7 +52492,7 @@ function startSubagentWatcher(config) {
52416
52492
  for (const e of entries) {
52417
52493
  if (!e.startsWith("agent-") || !e.endsWith(".jsonl"))
52418
52494
  continue;
52419
- const filePath = join22(subagentsPath, e);
52495
+ const filePath = join23(subagentsPath, e);
52420
52496
  if (knownFiles.has(filePath))
52421
52497
  continue;
52422
52498
  const agentId = e.slice("agent-".length, -".jsonl".length);
@@ -52542,15 +52618,15 @@ function determineRestartReason(opts) {
52542
52618
  init_boot_card();
52543
52619
 
52544
52620
  // gateway/update-announce.ts
52545
- import { existsSync as existsSync30, mkdirSync as mkdirSync19, openSync as openSync5, closeSync as closeSync5, readFileSync as readFileSync29 } from "node:fs";
52546
- import { join as join27 } from "node:path";
52621
+ import { existsSync as existsSync30, mkdirSync as mkdirSync20, openSync as openSync5, closeSync as closeSync5, readFileSync as readFileSync29 } from "node:fs";
52622
+ import { join as join28 } from "node:path";
52547
52623
  import { homedir as homedir12 } from "node:os";
52548
52624
 
52549
52625
  // ../src/host-control/audit-reader.ts
52550
52626
  import { homedir as homedir11 } from "node:os";
52551
- import { join as join26 } from "node:path";
52627
+ import { join as join27 } from "node:path";
52552
52628
  function defaultAuditLogPath(home2 = homedir11()) {
52553
- return join26(home2, ".switchroom", "host-control-audit.log");
52629
+ return join27(home2, ".switchroom", "host-control-audit.log");
52554
52630
  }
52555
52631
  function parseAuditLine(line) {
52556
52632
  const trimmed = line.trim();
@@ -52718,15 +52794,15 @@ function renderUpdateOutcomeLine(entry) {
52718
52794
  `);
52719
52795
  }
52720
52796
  function claimUpdateAnnouncement(requestId, opts = {}) {
52721
- const stateDir = opts.stateDir ?? process.env.TELEGRAM_STATE_DIR ?? join27(homedir12(), ".switchroom");
52722
- const dir = join27(stateDir, "update-announced");
52797
+ const stateDir = opts.stateDir ?? process.env.TELEGRAM_STATE_DIR ?? join28(homedir12(), ".switchroom");
52798
+ const dir = join28(stateDir, "update-announced");
52723
52799
  try {
52724
- mkdirSync19(dir, { recursive: true });
52800
+ mkdirSync20(dir, { recursive: true });
52725
52801
  } catch {
52726
52802
  return false;
52727
52803
  }
52728
52804
  const safeId = requestId.replace(/[^A-Za-z0-9_.-]/g, "_").slice(0, 200);
52729
- const path = join27(dir, safeId);
52805
+ const path = join28(dir, safeId);
52730
52806
  try {
52731
52807
  const fd = openSync5(path, "wx");
52732
52808
  closeSync5(fd);
@@ -52745,7 +52821,7 @@ function maybeRenderUpdateAnnouncement(opts = {}) {
52745
52821
  }
52746
52822
 
52747
52823
  // issues-card.ts
52748
- import { readFileSync as readFileSync30, writeFileSync as writeFileSync19 } from "node:fs";
52824
+ import { readFileSync as readFileSync30, writeFileSync as writeFileSync20 } from "node:fs";
52749
52825
  var SEVERITY_EMOJI = {
52750
52826
  info: "\u2139\ufe0f",
52751
52827
  warn: "\u26a0\ufe0f",
@@ -52853,7 +52929,7 @@ function readPersistedMessageId(path, log) {
52853
52929
  }
52854
52930
  function writePersistedMessageId(path, messageId, log) {
52855
52931
  try {
52856
- writeFileSync19(path, JSON.stringify({ messageId }) + `
52932
+ writeFileSync20(path, JSON.stringify({ messageId }) + `
52857
52933
  `, { mode: 384 });
52858
52934
  } catch (err) {
52859
52935
  log(`issues-card: persist write failed (${err.message})`);
@@ -52947,23 +53023,23 @@ function createIssuesCardHandle(opts) {
52947
53023
 
52948
53024
  // issues-watcher.ts
52949
53025
  import { existsSync as existsSync32, statSync as statSync9 } from "node:fs";
52950
- import { join as join29 } from "node:path";
53026
+ import { join as join30 } from "node:path";
52951
53027
 
52952
53028
  // ../src/issues/store.ts
52953
53029
  import {
52954
53030
  closeSync as closeSync6,
52955
53031
  existsSync as existsSync31,
52956
- mkdirSync as mkdirSync20,
53032
+ mkdirSync as mkdirSync21,
52957
53033
  openSync as openSync6,
52958
53034
  readdirSync as readdirSync5,
52959
53035
  readFileSync as readFileSync31,
52960
53036
  renameSync as renameSync11,
52961
53037
  statSync as statSync8,
52962
53038
  unlinkSync as unlinkSync11,
52963
- writeFileSync as writeFileSync20,
53039
+ writeFileSync as writeFileSync21,
52964
53040
  writeSync as writeSync2
52965
53041
  } from "node:fs";
52966
- import { join as join28 } from "node:path";
53042
+ import { join as join29 } from "node:path";
52967
53043
  import { randomBytes as randomBytes5 } from "node:crypto";
52968
53044
  import { execSync } from "node:child_process";
52969
53045
 
@@ -52982,7 +53058,7 @@ init_redact();
52982
53058
  var ISSUES_FILE = "issues.jsonl";
52983
53059
  var ISSUES_LOCK = "issues.lock";
52984
53060
  function readAll(stateDir) {
52985
- const path = join28(stateDir, ISSUES_FILE);
53061
+ const path = join29(stateDir, ISSUES_FILE);
52986
53062
  if (!existsSync31(path))
52987
53063
  return [];
52988
53064
  let raw;
@@ -53019,7 +53095,7 @@ function list(stateDir, opts = {}) {
53019
53095
  });
53020
53096
  }
53021
53097
  function resolve6(stateDir, fingerprint, nowFn = Date.now) {
53022
- if (!existsSync31(join28(stateDir, ISSUES_FILE)))
53098
+ if (!existsSync31(join29(stateDir, ISSUES_FILE)))
53023
53099
  return 0;
53024
53100
  return withLock(stateDir, () => {
53025
53101
  const all = readAll(stateDir);
@@ -53037,13 +53113,13 @@ function resolve6(stateDir, fingerprint, nowFn = Date.now) {
53037
53113
  });
53038
53114
  }
53039
53115
  function writeAll(stateDir, events) {
53040
- const path = join28(stateDir, ISSUES_FILE);
53116
+ const path = join29(stateDir, ISSUES_FILE);
53041
53117
  sweepOrphanTmpFiles(stateDir);
53042
53118
  const tmp = `${path}.tmp-${process.pid}-${randomBytes5(4).toString("hex")}`;
53043
53119
  const body = events.length === 0 ? "" : events.map((e) => JSON.stringify(e)).join(`
53044
53120
  `) + `
53045
53121
  `;
53046
- writeFileSync20(tmp, body, "utf-8");
53122
+ writeFileSync21(tmp, body, "utf-8");
53047
53123
  renameSync11(tmp, path);
53048
53124
  }
53049
53125
  var ORPHAN_TMP_TTL_MS = 60000;
@@ -53059,7 +53135,7 @@ function sweepOrphanTmpFiles(stateDir) {
53059
53135
  for (const entry of entries) {
53060
53136
  if (!entry.startsWith(TMP_PREFIX))
53061
53137
  continue;
53062
- const tmpPath2 = join28(stateDir, entry);
53138
+ const tmpPath2 = join29(stateDir, entry);
53063
53139
  try {
53064
53140
  const stat = statSync8(tmpPath2);
53065
53141
  if (stat.mtimeMs < cutoff) {
@@ -53071,7 +53147,7 @@ function sweepOrphanTmpFiles(stateDir) {
53071
53147
  var LOCK_RETRY_MS = 25;
53072
53148
  var LOCK_TIMEOUT_MS = 1e4;
53073
53149
  function withLock(stateDir, fn) {
53074
- const lockPath = join28(stateDir, ISSUES_LOCK);
53150
+ const lockPath = join29(stateDir, ISSUES_LOCK);
53075
53151
  const startedAt = Date.now();
53076
53152
  let fd = null;
53077
53153
  while (fd === null) {
@@ -53156,7 +53232,7 @@ function isIssueEvent(v) {
53156
53232
  // issues-watcher.ts
53157
53233
  var DEFAULT_POLL_INTERVAL_MS2 = 2000;
53158
53234
  function startIssuesWatcher(opts) {
53159
- const path = join29(opts.stateDir, ISSUES_FILE);
53235
+ const path = join30(opts.stateDir, ISSUES_FILE);
53160
53236
  const log = opts.log ?? (() => {});
53161
53237
  const intervalMs = opts.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS2;
53162
53238
  const setIntervalFn = opts.setInterval ?? setInterval;
@@ -54090,8 +54166,8 @@ function extractFlowItems(line) {
54090
54166
  }
54091
54167
 
54092
54168
  // credits-watch.ts
54093
- import { readFileSync as readFileSync32, writeFileSync as writeFileSync21, existsSync as existsSync33, mkdirSync as mkdirSync21 } from "fs";
54094
- import { join as join30 } from "path";
54169
+ import { readFileSync as readFileSync32, writeFileSync as writeFileSync22, existsSync as existsSync33, mkdirSync as mkdirSync22 } from "fs";
54170
+ import { join as join31 } from "path";
54095
54171
  var STATE_FILE = "credits-watch.json";
54096
54172
  var DEFAULT_CREDIT_FATAL_REASONS = new Set;
54097
54173
  var KNOWN_CREDIT_REASONS = [
@@ -54113,7 +54189,7 @@ function emptyCreditState() {
54113
54189
  return { lastNotifiedReason: null, lastNotifiedAt: 0 };
54114
54190
  }
54115
54191
  function readClaudeJsonOverage(claudeConfigDir) {
54116
- const path = join30(claudeConfigDir, ".claude.json");
54192
+ const path = join31(claudeConfigDir, ".claude.json");
54117
54193
  if (!existsSync33(path))
54118
54194
  return null;
54119
54195
  let raw;
@@ -54199,7 +54275,7 @@ function escapeHtml9(s) {
54199
54275
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
54200
54276
  }
54201
54277
  function loadCreditState(stateDir) {
54202
- const path = join30(stateDir, STATE_FILE);
54278
+ const path = join31(stateDir, STATE_FILE);
54203
54279
  if (!existsSync33(path))
54204
54280
  return emptyCreditState();
54205
54281
  try {
@@ -54215,15 +54291,15 @@ function loadCreditState(stateDir) {
54215
54291
  return emptyCreditState();
54216
54292
  }
54217
54293
  function saveCreditState(stateDir, state4) {
54218
- mkdirSync21(stateDir, { recursive: true });
54219
- const path = join30(stateDir, STATE_FILE);
54220
- writeFileSync21(path, JSON.stringify(state4, null, 2) + `
54294
+ mkdirSync22(stateDir, { recursive: true });
54295
+ const path = join31(stateDir, STATE_FILE);
54296
+ writeFileSync22(path, JSON.stringify(state4, null, 2) + `
54221
54297
  `, { mode: 384 });
54222
54298
  }
54223
54299
 
54224
54300
  // quota-watch.ts
54225
- import { readFileSync as readFileSync33, writeFileSync as writeFileSync22, existsSync as existsSync34, mkdirSync as mkdirSync22 } from "fs";
54226
- import { join as join31 } from "path";
54301
+ import { readFileSync as readFileSync33, writeFileSync as writeFileSync23, existsSync as existsSync34, mkdirSync as mkdirSync23 } from "fs";
54302
+ import { join as join32 } from "path";
54227
54303
  var STATE_FILE2 = "quota-watch.json";
54228
54304
  function emptyQuotaWatchState() {
54229
54305
  return {};
@@ -54404,7 +54480,7 @@ function escapeHtml10(s) {
54404
54480
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
54405
54481
  }
54406
54482
  function loadQuotaWatchState(stateDir) {
54407
- const path = join31(stateDir, STATE_FILE2);
54483
+ const path = join32(stateDir, STATE_FILE2);
54408
54484
  if (!existsSync34(path))
54409
54485
  return emptyQuotaWatchState();
54410
54486
  try {
@@ -54425,9 +54501,9 @@ function loadQuotaWatchState(stateDir) {
54425
54501
  }
54426
54502
  }
54427
54503
  function saveQuotaWatchState(stateDir, state4) {
54428
- mkdirSync22(stateDir, { recursive: true });
54429
- const path = join31(stateDir, STATE_FILE2);
54430
- writeFileSync22(path, JSON.stringify(state4, null, 2) + `
54504
+ mkdirSync23(stateDir, { recursive: true });
54505
+ const path = join32(stateDir, STATE_FILE2);
54506
+ writeFileSync23(path, JSON.stringify(state4, null, 2) + `
54431
54507
  `, { mode: 384 });
54432
54508
  }
54433
54509
  function patchQuotaWatchState(current, accountLabel, accountState) {
@@ -54441,25 +54517,25 @@ init_auth_snapshot_format();
54441
54517
  import {
54442
54518
  closeSync as closeSync7,
54443
54519
  existsSync as existsSync35,
54444
- mkdirSync as mkdirSync23,
54520
+ mkdirSync as mkdirSync24,
54445
54521
  openSync as openSync7,
54446
54522
  readFileSync as readFileSync34,
54447
54523
  statSync as statSync10,
54448
54524
  unlinkSync as unlinkSync12,
54449
54525
  utimesSync as utimesSync2,
54450
- writeFileSync as writeFileSync23
54526
+ writeFileSync as writeFileSync24
54451
54527
  } from "node:fs";
54452
- import { join as join32 } from "node:path";
54528
+ import { join as join33 } from "node:path";
54453
54529
  var TURN_ACTIVE_MARKER_FILE2 = "turn-active.json";
54454
54530
  function writeTurnActiveMarker(stateDir, marker) {
54455
54531
  try {
54456
- mkdirSync23(stateDir, { recursive: true });
54457
- writeFileSync23(join32(stateDir, TURN_ACTIVE_MARKER_FILE2), JSON.stringify(marker, null, 2) + `
54532
+ mkdirSync24(stateDir, { recursive: true });
54533
+ writeFileSync24(join33(stateDir, TURN_ACTIVE_MARKER_FILE2), JSON.stringify(marker, null, 2) + `
54458
54534
  `, { mode: 384 });
54459
54535
  } catch {}
54460
54536
  }
54461
54537
  function touchTurnActiveMarker2(stateDir) {
54462
- const path = join32(stateDir, TURN_ACTIVE_MARKER_FILE2);
54538
+ const path = join33(stateDir, TURN_ACTIVE_MARKER_FILE2);
54463
54539
  if (!existsSync35(path))
54464
54540
  return;
54465
54541
  const now = new Date;
@@ -54474,11 +54550,11 @@ function touchTurnActiveMarker2(stateDir) {
54474
54550
  }
54475
54551
  function removeTurnActiveMarker(stateDir) {
54476
54552
  try {
54477
- unlinkSync12(join32(stateDir, TURN_ACTIVE_MARKER_FILE2));
54553
+ unlinkSync12(join33(stateDir, TURN_ACTIVE_MARKER_FILE2));
54478
54554
  } catch {}
54479
54555
  }
54480
54556
  function sweepStaleTurnActiveMarker(stateDir, opts) {
54481
- const path = join32(stateDir, TURN_ACTIVE_MARKER_FILE2);
54557
+ const path = join33(stateDir, TURN_ACTIVE_MARKER_FILE2);
54482
54558
  if (!existsSync35(path))
54483
54559
  return false;
54484
54560
  const now = opts.now ?? Date.now();
@@ -54509,7 +54585,7 @@ function sweepStaleTurnActiveMarker(stateDir, opts) {
54509
54585
  }
54510
54586
  }
54511
54587
  function readTurnActiveMarkerAgeMs(stateDir, now) {
54512
- const path = join32(stateDir, TURN_ACTIVE_MARKER_FILE2);
54588
+ const path = join33(stateDir, TURN_ACTIVE_MARKER_FILE2);
54513
54589
  try {
54514
54590
  const st = statSync10(path);
54515
54591
  return (now ?? Date.now()) - st.mtimeMs;
@@ -54519,11 +54595,11 @@ function readTurnActiveMarkerAgeMs(stateDir, now) {
54519
54595
  }
54520
54596
 
54521
54597
  // ../src/build-info.ts
54522
- var VERSION = "0.15.40";
54523
- var COMMIT_SHA = "fd53cf02";
54524
- var COMMIT_DATE = "2026-06-18T13:00:59+10:00";
54525
- var LATEST_PR = null;
54526
- var COMMITS_AHEAD_OF_TAG = 4;
54598
+ var VERSION = "0.15.42";
54599
+ var COMMIT_SHA = "2c7e12da";
54600
+ var COMMIT_DATE = "2026-06-19T09:09:26+10:00";
54601
+ var LATEST_PR = 2434;
54602
+ var COMMITS_AHEAD_OF_TAG = 5;
54527
54603
 
54528
54604
  // gateway/boot-version.ts
54529
54605
  function formatRelativeAgo(iso) {
@@ -54596,11 +54672,11 @@ init_peercred();
54596
54672
  import * as net5 from "node:net";
54597
54673
  import * as fs2 from "node:fs";
54598
54674
  import { homedir as homedir13 } from "node:os";
54599
- import { join as join33 } from "node:path";
54675
+ import { join as join34 } from "node:path";
54600
54676
  var DEFAULT_TIMEOUT_MS4 = 2000;
54601
54677
  var UNLOCK_TIMEOUT_MS = 30000;
54602
- var LEGACY_SOCKET_PATH2 = join33(homedir13(), ".switchroom", "vault-broker.sock");
54603
- var OPERATOR_SOCKET_PATH2 = join33(homedir13(), ".switchroom", "broker-operator", "sock");
54678
+ var LEGACY_SOCKET_PATH2 = join34(homedir13(), ".switchroom", "vault-broker.sock");
54679
+ var OPERATOR_SOCKET_PATH2 = join34(homedir13(), ".switchroom", "broker-operator", "sock");
54604
54680
  function defaultBrokerSocketPath2() {
54605
54681
  if (fs2.existsSync(OPERATOR_SOCKET_PATH2))
54606
54682
  return OPERATOR_SOCKET_PATH2;
@@ -55533,8 +55609,8 @@ function matchesAdminOnlyKey(key, patterns) {
55533
55609
  }
55534
55610
 
55535
55611
  // registry/turns-schema.ts
55536
- import { chmodSync as chmodSync5, mkdirSync as mkdirSync24 } from "fs";
55537
- import { join as join34 } from "path";
55612
+ import { chmodSync as chmodSync5, mkdirSync as mkdirSync25 } from "fs";
55613
+ import { join as join35 } from "path";
55538
55614
  var DatabaseClass2 = null;
55539
55615
  function loadDatabaseClass2() {
55540
55616
  if (DatabaseClass2 != null)
@@ -55597,9 +55673,9 @@ function applySchema(db2) {
55597
55673
  }
55598
55674
  function openTurnsDb(agentDir) {
55599
55675
  const Database = loadDatabaseClass2();
55600
- const dir = join34(agentDir, "telegram");
55601
- mkdirSync24(dir, { recursive: true, mode: 448 });
55602
- const path = join34(dir, "registry.db");
55676
+ const dir = join35(agentDir, "telegram");
55677
+ mkdirSync25(dir, { recursive: true, mode: 448 });
55678
+ const path = join35(dir, "registry.db");
55603
55679
  const db2 = new Database(path, { create: true });
55604
55680
  applySchema(db2);
55605
55681
  try {
@@ -55915,11 +55991,11 @@ installGlobalErrorHandlers();
55915
55991
  process.on("beforeExit", () => {
55916
55992
  shutdownAnalytics();
55917
55993
  });
55918
- var STATE_DIR = process.env.TELEGRAM_STATE_DIR ?? join35(homedir14(), ".claude", "channels", "telegram");
55919
- var ACCESS_FILE = join35(STATE_DIR, "access.json");
55920
- var APPROVED_DIR = join35(STATE_DIR, "approved");
55921
- var ENV_FILE = join35(STATE_DIR, ".env");
55922
- var INBOX_DIR = join35(STATE_DIR, "inbox");
55994
+ var STATE_DIR = process.env.TELEGRAM_STATE_DIR ?? join36(homedir14(), ".claude", "channels", "telegram");
55995
+ var ACCESS_FILE = join36(STATE_DIR, "access.json");
55996
+ var APPROVED_DIR = join36(STATE_DIR, "approved");
55997
+ var ENV_FILE = join36(STATE_DIR, ".env");
55998
+ var INBOX_DIR = join36(STATE_DIR, "inbox");
55923
55999
  function triggerSelfRestart(targetAgent, reason, delayMs = 300) {
55924
56000
  const isDocker = process.env.SWITCHROOM_RUNTIME === "docker";
55925
56001
  const selfAgent = process.env.SWITCHROOM_AGENT_NAME;
@@ -56111,7 +56187,7 @@ function assertSendable(f) {
56111
56187
  } catch {
56112
56188
  throw new Error(`refusing to send file \u2014 cannot resolve real path: ${f}`);
56113
56189
  }
56114
- const inbox = join35(stateReal, "inbox");
56190
+ const inbox = join36(stateReal, "inbox");
56115
56191
  if (real.startsWith(stateReal + sep3) && !real.startsWith(inbox + sep3)) {
56116
56192
  throw new Error(`refusing to send channel state: ${f}`);
56117
56193
  }
@@ -56198,9 +56274,9 @@ function assertAllowedChat(chat_id) {
56198
56274
  function saveAccess(a) {
56199
56275
  if (STATIC)
56200
56276
  return;
56201
- mkdirSync26(STATE_DIR, { recursive: true, mode: 448 });
56277
+ mkdirSync27(STATE_DIR, { recursive: true, mode: 448 });
56202
56278
  const tmp = ACCESS_FILE + ".tmp";
56203
- writeFileSync25(tmp, JSON.stringify(a, null, 2) + `
56279
+ writeFileSync26(tmp, JSON.stringify(a, null, 2) + `
56204
56280
  `, { mode: 384 });
56205
56281
  renameSync12(tmp, ACCESS_FILE);
56206
56282
  }
@@ -56220,7 +56296,7 @@ var HISTORY_ENABLED = HISTORY_ACCESS.historyEnabled !== false;
56220
56296
  if (HISTORY_ENABLED) {
56221
56297
  try {
56222
56298
  initHistory(STATE_DIR, HISTORY_ACCESS.historyRetentionDays ?? 30);
56223
- process.stderr.write(`telegram gateway: history capture enabled at ${join35(STATE_DIR, "history.db")}
56299
+ process.stderr.write(`telegram gateway: history capture enabled at ${join36(STATE_DIR, "history.db")}
56224
56300
  `);
56225
56301
  } catch (err) {
56226
56302
  process.stderr.write(`telegram gateway: history init failed (${err.message}) \u2014 capture disabled
@@ -56236,7 +56312,7 @@ try {
56236
56312
  let markerTurnKey = null;
56237
56313
  let markerAgeMs = null;
56238
56314
  try {
56239
- const markerPath = join35(STATE_DIR, TURN_ACTIVE_MARKER_FILE2);
56315
+ const markerPath = join36(STATE_DIR, TURN_ACTIVE_MARKER_FILE2);
56240
56316
  if (existsSync38(markerPath)) {
56241
56317
  const st = statSync13(markerPath);
56242
56318
  markerAgeMs = Date.now() - st.mtimeMs;
@@ -56261,7 +56337,7 @@ try {
56261
56337
  process.stderr.write(`telegram gateway: turn-registry boot-reaper stamped ${reaped} orphaned turn(s)${timeoutTurnKey ? ` (turnKey=${timeoutTurnKey} as 'timeout', markerAgeMs=${markerAgeMs})` : " as 'restart'"}
56262
56338
  `);
56263
56339
  } else {
56264
- process.stderr.write(`telegram gateway: turn-registry initialized at ${join35(agentDir, "telegram", "registry.db")}
56340
+ process.stderr.write(`telegram gateway: turn-registry initialized at ${join36(agentDir, "telegram", "registry.db")}
56265
56341
  `);
56266
56342
  }
56267
56343
  const pending2 = findLatestTurnIfInterrupted(turnsDb);
@@ -56298,7 +56374,7 @@ try {
56298
56374
  `);
56299
56375
  }
56300
56376
  }
56301
- const pendingEnvPath = join35(agentDir, ".pending-turn.env");
56377
+ const pendingEnvPath = join36(agentDir, ".pending-turn.env");
56302
56378
  try {
56303
56379
  if (pending2 != null) {
56304
56380
  const lines = [
@@ -56312,7 +56388,7 @@ try {
56312
56388
  pending2.interrupt_reason != null ? `SWITCHROOM_PENDING_INTERRUPT_REASON=${pending2.interrupt_reason}` : `SWITCHROOM_PENDING_INTERRUPT_REASON=`
56313
56389
  ];
56314
56390
  const pendingEnvTmp = `${pendingEnvPath}.tmp-${process.pid}`;
56315
- writeFileSync25(pendingEnvTmp, lines.join(`
56391
+ writeFileSync26(pendingEnvTmp, lines.join(`
56316
56392
  `) + `
56317
56393
  `, { mode: 384 });
56318
56394
  renameSync12(pendingEnvTmp, pendingEnvPath);
@@ -56391,7 +56467,7 @@ function checkApprovals() {
56391
56467
  return;
56392
56468
  }
56393
56469
  for (const senderId of files) {
56394
- const file = join35(APPROVED_DIR, senderId);
56470
+ const file = join36(APPROVED_DIR, senderId);
56395
56471
  bot.api.sendMessage(senderId, "Paired! Say hi to Claude.").then(() => rmSync4(file, { force: true }), (err) => {
56396
56472
  process.stderr.write(`telegram gateway: failed to send approval confirm: ${err}
56397
56473
  `);
@@ -56401,6 +56477,9 @@ function checkApprovals() {
56401
56477
  }
56402
56478
  if (!STATIC)
56403
56479
  setInterval(checkApprovals, 5000).unref();
56480
+ var IDLE_CLEAR_CHECK_MS = Number(process.env.SWITCHROOM_IDLE_CLEAR_CHECK_MS ?? 60000);
56481
+ if (!STATIC && IDLE_CLEAR_CHECK_MS > 0)
56482
+ setInterval(maybeIdleClear, IDLE_CLEAR_CHECK_MS).unref();
56404
56483
  var chatThreadMap = new Map;
56405
56484
  var activeStatusReactions = new Map;
56406
56485
  var activeReactionMsgIds = new Map;
@@ -56471,10 +56550,10 @@ function noteAgentOutputAt(key, ts) {
56471
56550
  lastAgentOutputAt.delete(oldest);
56472
56551
  }
56473
56552
  }
56474
- var OBLIGATION_STORE_PATH = join35(STATE_DIR, "obligations.json");
56553
+ var OBLIGATION_STORE_PATH = join36(STATE_DIR, "obligations.json");
56475
56554
  var obligationStoreFs = {
56476
56555
  readFileSync: (p) => readFileSync36(p, "utf8"),
56477
- writeFileSync: (p, d) => writeFileSync25(p, d),
56556
+ writeFileSync: (p, d) => writeFileSync26(p, d),
56478
56557
  renameSync: (a, b) => renameSync12(a, b),
56479
56558
  existsSync: (p) => existsSync38(p)
56480
56559
  };
@@ -56862,8 +56941,33 @@ function purgeReactionTracking(key, endingTurn) {
56862
56941
  } else {
56863
56942
  maybeProactiveCompact();
56864
56943
  }
56944
+ snapshotContextOccupancy();
56865
56945
  }
56866
56946
  }
56947
+ function snapshotContextOccupancy() {
56948
+ try {
56949
+ const agentName3 = process.env.SWITCHROOM_AGENT_NAME;
56950
+ const file = lastSessionActiveFile;
56951
+ if (!agentName3 || !file)
56952
+ return;
56953
+ const turns = readTurnUsages(file, 1);
56954
+ if (turns.length === 0)
56955
+ return;
56956
+ const t = turns[0];
56957
+ const occupancy = t.input + t.cacheRead + t.cacheCreate;
56958
+ let cap = null;
56959
+ try {
56960
+ const cfg = loadConfig2();
56961
+ const rawAgent = cfg.agents?.[agentName3] ?? {};
56962
+ const resolved = resolveAgentConfig2(cfg.defaults, cfg.profiles, rawAgent);
56963
+ cap = resolved.session?.max_context_tokens ?? null;
56964
+ } catch {
56965
+ cap = null;
56966
+ }
56967
+ const stateDir = process.env.SWITCHROOM_AGENT_STATE_DIR ?? "/state/agent";
56968
+ writeContextOccupancySnapshot(stateDir, buildContextOccupancy(occupancy, cap, Date.now()));
56969
+ } catch {}
56970
+ }
56867
56971
  function releaseTurnBufferGate(key, endingTurn) {
56868
56972
  if (!activeTurnStartedAt.has(key))
56869
56973
  return;
@@ -56955,6 +57059,81 @@ function maybeProactiveCompact() {
56955
57059
  compactDispatching = false;
56956
57060
  });
56957
57061
  }
57062
+ var lastIdleActivityAt = Date.now();
57063
+ var idleAutoCleared = false;
57064
+ var idleClearDispatching = false;
57065
+ function markIdleActivity() {
57066
+ lastIdleActivityAt = Date.now();
57067
+ idleAutoCleared = false;
57068
+ }
57069
+ function resolveIdleClearMs() {
57070
+ const env = process.env.SWITCHROOM_IDLE_CLEAR_MS;
57071
+ if (env != null && env !== "") {
57072
+ const n = Number(env);
57073
+ return Number.isFinite(n) && n >= 0 ? n : DEFAULT_IDLE_CLEAR_MS;
57074
+ }
57075
+ try {
57076
+ const agentName3 = process.env.SWITCHROOM_AGENT_NAME;
57077
+ if (!agentName3)
57078
+ return DEFAULT_IDLE_CLEAR_MS;
57079
+ const cfg = loadConfig2();
57080
+ const rawAgent = cfg.agents?.[agentName3] ?? {};
57081
+ const resolved = resolveAgentConfig2(cfg.defaults, cfg.profiles, rawAgent);
57082
+ const raw = resolved.session?.idle_clear_after;
57083
+ if (raw == null)
57084
+ return DEFAULT_IDLE_CLEAR_MS;
57085
+ const ms = idleDurationToMs(raw);
57086
+ return ms == null ? DEFAULT_IDLE_CLEAR_MS : ms;
57087
+ } catch {
57088
+ return DEFAULT_IDLE_CLEAR_MS;
57089
+ }
57090
+ }
57091
+ function maybeIdleClear() {
57092
+ if (idleClearDispatching)
57093
+ return;
57094
+ const agentName3 = process.env.SWITCHROOM_AGENT_NAME;
57095
+ if (!agentName3)
57096
+ return;
57097
+ const idleClearMs = resolveIdleClearMs();
57098
+ const decision = decideIdleClear({
57099
+ lastActivityAt: lastIdleActivityAt,
57100
+ idleClearMs,
57101
+ alreadyCleared: idleAutoCleared,
57102
+ turnInFlight: turnInFlightForGate()
57103
+ }, Date.now());
57104
+ if (!decision.clear)
57105
+ return;
57106
+ idleAutoCleared = true;
57107
+ idleClearDispatching = true;
57108
+ process.stderr.write(`telegram gateway: idle auto-/clear for ${agentName3} (idle >= ${Math.round(idleClearMs / 60000)}m)
57109
+ `);
57110
+ injectSlashCommand(agentName3, "/clear").then(() => {
57111
+ postIdleClearNotice(idleClearMs);
57112
+ }).catch((err) => {
57113
+ process.stderr.write(`telegram gateway: idle /clear inject failed for ${agentName3}: ${err instanceof Error ? err.message : String(err)}
57114
+ `);
57115
+ }).finally(() => {
57116
+ idleClearDispatching = false;
57117
+ });
57118
+ }
57119
+ async function postIdleClearNotice(idleClearMs) {
57120
+ try {
57121
+ const chatId = loadAccess().allowFrom[0];
57122
+ if (!chatId)
57123
+ return;
57124
+ const threadId = topicForRecipient({
57125
+ recipientChatId: chatId,
57126
+ resolvedTopic: resolveAgentOutboundTopic({ kind: "compact-watchdog" }) ?? chatThreadMap.get(chatId),
57127
+ supergroupChatId: resolveAgentSupergroupChatId()
57128
+ });
57129
+ const hrs = Math.round(idleClearMs / 3600000 * 10) / 10;
57130
+ const text2 = `\uD83E\uDDF9 <b>Cleared after ${hrs}h idle</b> \u2014 fresh slate next message; ` + `long-term memory is in Hindsight.`;
57131
+ await swallowingApiCall(() => bot.api.sendMessage(chatId, text2, {
57132
+ parse_mode: "HTML",
57133
+ ...threadId != null ? { message_thread_id: threadId } : {}
57134
+ }), { chat_id: chatId, verb: "idleAutoClear.notice" });
57135
+ } catch {}
57136
+ }
56958
57137
  async function postCompactCard(occ, cap) {
56959
57138
  try {
56960
57139
  const chatId = loadAccess().allowFrom[0];
@@ -57758,11 +57937,11 @@ var unpinProgressCardForChat = null;
57758
57937
  var getPinnedProgressCardMessageId = null;
57759
57938
  var completeProgressCardTurn = null;
57760
57939
  var subagentWatcher = null;
57761
- var SOCKET_PATH = process.env.SWITCHROOM_GATEWAY_SOCKET ?? join35(STATE_DIR, "gateway.sock");
57762
- mkdirSync26(STATE_DIR, { recursive: true, mode: 448 });
57763
- var GATEWAY_PID_PATH = process.env.SWITCHROOM_GATEWAY_PID_FILE ?? join35(STATE_DIR, "gateway.pid.json");
57764
- var GATEWAY_SESSION_MARKER_PATH = process.env.SWITCHROOM_GATEWAY_SESSION_MARKER ?? join35(STATE_DIR, "gateway-session.json");
57765
- var GATEWAY_CLEAN_SHUTDOWN_MARKER_PATH = process.env.SWITCHROOM_GATEWAY_CLEAN_SHUTDOWN_MARKER ?? join35(STATE_DIR, "clean-shutdown.json");
57940
+ var SOCKET_PATH = process.env.SWITCHROOM_GATEWAY_SOCKET ?? join36(STATE_DIR, "gateway.sock");
57941
+ mkdirSync27(STATE_DIR, { recursive: true, mode: 448 });
57942
+ var GATEWAY_PID_PATH = process.env.SWITCHROOM_GATEWAY_PID_FILE ?? join36(STATE_DIR, "gateway.pid.json");
57943
+ var GATEWAY_SESSION_MARKER_PATH = process.env.SWITCHROOM_GATEWAY_SESSION_MARKER ?? join36(STATE_DIR, "gateway-session.json");
57944
+ var GATEWAY_CLEAN_SHUTDOWN_MARKER_PATH = process.env.SWITCHROOM_GATEWAY_CLEAN_SHUTDOWN_MARKER ?? join36(STATE_DIR, "clean-shutdown.json");
57766
57945
  var GATEWAY_STARTED_AT_MS = Date.now();
57767
57946
  var BOOT_CARD_ENABLED = process.env.SWITCHROOM_BOOT_CARD !== "false";
57768
57947
  var activeBootCard = null;
@@ -57791,7 +57970,7 @@ function ensureIssuesCard(chatId, threadId) {
57791
57970
  bot: botApi,
57792
57971
  log: (msg) => process.stderr.write(`telegram gateway: ${msg}
57793
57972
  `),
57794
- persistPath: join35(stateDir, "issues-card.json")
57973
+ persistPath: join36(stateDir, "issues-card.json")
57795
57974
  });
57796
57975
  activeIssuesWatcher = startIssuesWatcher({
57797
57976
  stateDir,
@@ -58020,11 +58199,11 @@ startTimer2({
58020
58199
  }
58021
58200
  });
58022
58201
  var inboundSpool = STATIC ? undefined : createInboundSpool({
58023
- path: join35(STATE_DIR, "inbound-spool.jsonl"),
58202
+ path: join36(STATE_DIR, "inbound-spool.jsonl"),
58024
58203
  fs: {
58025
58204
  appendFileSync: (p, d) => appendFileSync5(p, d),
58026
58205
  readFileSync: (p) => readFileSync36(p, "utf8"),
58027
- writeFileSync: (p, d) => writeFileSync25(p, d),
58206
+ writeFileSync: (p, d) => writeFileSync26(p, d),
58028
58207
  renameSync: (a, b) => renameSync12(a, b),
58029
58208
  existsSync: (p) => existsSync38(p),
58030
58209
  statSizeSync: (p) => statSync13(p).size
@@ -58240,8 +58419,8 @@ var ipcServer = createIpcServer({
58240
58419
  probeQuotaViaBroker: (t) => probeQuotaForBootCard(agentSlug, t),
58241
58420
  tmuxSupervisor: process.env.SWITCHROOM_TMUX_SUPERVISOR === "1",
58242
58421
  dockerMode: process.env.SWITCHROOM_RUNTIME === "docker",
58243
- configSnapshotPath: join35(resolvedAgentDirForCard, ".config-snapshot.json"),
58244
- bootCardStatePath: join35(resolvedAgentDirForCard, ".boot-card-msgid.json"),
58422
+ configSnapshotPath: join36(resolvedAgentDirForCard, ".config-snapshot.json"),
58423
+ bootCardStatePath: join36(resolvedAgentDirForCard, ".boot-card-msgid.json"),
58245
58424
  ...updateOutcomeLine ? { updateOutcomeLine } : {}
58246
58425
  }, ackMsgId).then((handle) => {
58247
58426
  activeBootCard = handle;
@@ -58665,6 +58844,7 @@ var ipcServer = createIpcServer({
58665
58844
  });
58666
58845
  },
58667
58846
  onInjectInbound(_client, msg) {
58847
+ markIdleActivity();
58668
58848
  const promptKey = typeof msg.inbound.meta?.prompt_key === "string" ? msg.inbound.meta.prompt_key : "unknown";
58669
58849
  const source = typeof msg.inbound.meta?.source === "string" ? msg.inbound.meta.source : "unknown";
58670
58850
  const { target, delivered, fellBackToMain } = deliverInjectWithFallback(msg.agentName, msg.inbound.meta, (t) => ipcServer.sendToAgent(t, msg.inbound));
@@ -58737,7 +58917,7 @@ var ipcServer = createIpcServer({
58737
58917
  const receiverUid = receiverUidRaw ? Number(receiverUidRaw) : NaN;
58738
58918
  if (Number.isInteger(receiverUid))
58739
58919
  allowedUids.push(receiverUid);
58740
- const socketPath = join35(STATE_DIR, "webhook.sock");
58920
+ const socketPath = join36(STATE_DIR, "webhook.sock");
58741
58921
  const webhookInject = (agentName3, inbound) => {
58742
58922
  const msg = inbound;
58743
58923
  const delivered = ipcServer.sendToAgent(agentName3, msg);
@@ -59848,7 +60028,7 @@ async function executeSendGif(rawArgs) {
59848
60028
  };
59849
60029
  }
59850
60030
  async function publishToTelegraph(text2, shortName, authorName) {
59851
- const accountPath = join35(STATE_DIR, "telegraph-account.json");
60031
+ const accountPath = join36(STATE_DIR, "telegraph-account.json");
59852
60032
  let account = null;
59853
60033
  try {
59854
60034
  if (existsSync38(accountPath)) {
@@ -59871,8 +60051,8 @@ async function publishToTelegraph(text2, shortName, authorName) {
59871
60051
  }
59872
60052
  account = created.value;
59873
60053
  try {
59874
- mkdirSync26(STATE_DIR, { recursive: true, mode: 448 });
59875
- writeFileSync25(accountPath, JSON.stringify(account, null, 2), { mode: 384 });
60054
+ mkdirSync27(STATE_DIR, { recursive: true, mode: 448 });
60055
+ writeFileSync26(accountPath, JSON.stringify(account, null, 2), { mode: 384 });
59876
60056
  } catch (err) {
59877
60057
  process.stderr.write(`telegram gateway: telegraph cache write failed: ${err.message}
59878
60058
  `);
@@ -60332,9 +60512,9 @@ async function executeDownloadAttachment(args) {
60332
60512
  fileUniqueId: file.file_unique_id,
60333
60513
  now: Date.now()
60334
60514
  });
60335
- mkdirSync26(INBOX_DIR, { recursive: true, mode: 448 });
60515
+ mkdirSync27(INBOX_DIR, { recursive: true, mode: 448 });
60336
60516
  assertInsideInbox(INBOX_DIR, dlPath);
60337
- writeFileSync25(dlPath, buf, { mode: 384 });
60517
+ writeFileSync26(dlPath, buf, { mode: 384 });
60338
60518
  return { content: [{ type: "text", text: dlPath }] };
60339
60519
  }
60340
60520
  async function executeEditMessage(args) {
@@ -60709,6 +60889,7 @@ function handleSessionEvent(ev) {
60709
60889
  isDm: isDmChatId(ev.chatId)
60710
60890
  };
60711
60891
  currentTurn = next;
60892
+ markIdleActivity();
60712
60893
  process.stderr.write(`telegram gateway: ${formatTurnLifecycle("set", "enqueue", next, startedAt)}
60713
60894
  `);
60714
60895
  rememberRecentTurn(next);
@@ -61495,6 +61676,7 @@ function maybeEarlyAckReaction(ctx, from) {
61495
61676
  bot.api.sendChatAction(chatId, "typing").catch(() => {});
61496
61677
  }
61497
61678
  async function handleInbound(ctx, text2, downloadImage, attachment, extraAttachments) {
61679
+ markIdleActivity();
61498
61680
  const isTopicMessage = ctx.message?.is_topic_message ?? false;
61499
61681
  const messageThreadId = ctx.message?.message_thread_id;
61500
61682
  if (TOPIC_ID != null) {
@@ -62319,14 +62501,14 @@ function restartMarkerPath() {
62319
62501
  const agentDir = resolveAgentDirFromEnv();
62320
62502
  if (!agentDir)
62321
62503
  return null;
62322
- return join35(agentDir, "restart-pending.json");
62504
+ return join36(agentDir, "restart-pending.json");
62323
62505
  }
62324
62506
  function writeRestartMarker(marker) {
62325
62507
  const p = restartMarkerPath();
62326
62508
  if (!p)
62327
62509
  return;
62328
62510
  try {
62329
- writeFileSync25(p, JSON.stringify(marker));
62511
+ writeFileSync26(p, JSON.stringify(marker));
62330
62512
  lastPlannedRestartAt = Date.now();
62331
62513
  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}
62332
62514
  `);
@@ -62511,12 +62693,12 @@ function _resetDockerReachableCache() {
62511
62693
  }
62512
62694
  function spawnSwitchroomDetached(args, onFailure) {
62513
62695
  const fullArgs = SWITCHROOM_CONFIG ? ["--config", SWITCHROOM_CONFIG, ...args] : args;
62514
- const logPath = join35(STATE_DIR, "detached-spawn.log");
62696
+ const logPath = join36(STATE_DIR, "detached-spawn.log");
62515
62697
  let outFd = null;
62516
62698
  try {
62517
- mkdirSync26(STATE_DIR, { recursive: true });
62699
+ mkdirSync27(STATE_DIR, { recursive: true });
62518
62700
  outFd = openSync8(logPath, "a");
62519
- writeFileSync25(logPath, `
62701
+ writeFileSync26(logPath, `
62520
62702
  [${new Date().toISOString()}] spawn ${SWITCHROOM_CLI} ${fullArgs.join(" ")}
62521
62703
  `, { flag: "a" });
62522
62704
  } catch {}
@@ -62904,7 +63086,7 @@ bot.use(async (ctx, next) => {
62904
63086
  });
62905
63087
  function readRecentDenialsForAgent(agentName3, windowMs, limit) {
62906
63088
  try {
62907
- const auditPath = join35(homedir14(), ".switchroom", "vault-audit.log");
63089
+ const auditPath = join36(homedir14(), ".switchroom", "vault-audit.log");
62908
63090
  if (!existsSync38(auditPath))
62909
63091
  return [];
62910
63092
  const raw = readFileSync36(auditPath, "utf8");
@@ -62959,7 +63141,7 @@ async function buildAgentMetadata(agentName3) {
62959
63141
  try {
62960
63142
  const agentDir = resolveAgentDirFromEnv();
62961
63143
  if (agentDir) {
62962
- const raw = readFileSync36(join35(agentDir, ".claude", ".claude.json"), "utf8");
63144
+ const raw = readFileSync36(join36(agentDir, ".claude", ".claude.json"), "utf8");
62963
63145
  claudeJson = JSON.parse(raw);
62964
63146
  }
62965
63147
  } catch {}
@@ -63074,17 +63256,26 @@ bot.command("agents", async (ctx) => {
63074
63256
  `);
63075
63257
  });
63076
63258
  });
63077
- bot.command("inject", async (ctx) => {
63078
- await handleInjectCommand(ctx, {
63079
- isAuthorized: isAuthorizedSender,
63259
+ function buildInjectDeps(opts) {
63260
+ return {
63261
+ isAuthorized: opts?.open ? () => true : isAuthorizedSender,
63080
63262
  inject: injectSlashCommand,
63081
- reply: async (ctx2, text2, opts) => switchroomReply(ctx2, text2, { html: opts?.html }),
63263
+ reply: async (ctx, text2, replyOpts) => switchroomReply(ctx, text2, { html: replyOpts?.html }),
63082
63264
  getAgentName: getMyAgentName,
63083
- getArgs: getCommandArgs,
63265
+ getArgs: opts?.fixedVerb != null ? () => opts.fixedVerb : getCommandArgs,
63084
63266
  escapeHtml: escapeHtmlForTg,
63085
63267
  preBlock,
63086
63268
  formatOutput: formatSwitchroomOutput
63087
- });
63269
+ };
63270
+ }
63271
+ bot.command("inject", async (ctx) => {
63272
+ await handleInjectCommand(ctx, buildInjectDeps());
63273
+ });
63274
+ bot.command("compact", async (ctx) => {
63275
+ await handleInjectCommand(ctx, buildInjectDeps({ open: true, fixedVerb: "/compact" }));
63276
+ });
63277
+ bot.command("clear", async (ctx) => {
63278
+ await handleInjectCommand(ctx, buildInjectDeps({ open: true, fixedVerb: "/clear" }));
63088
63279
  });
63089
63280
  function buildModelDeps() {
63090
63281
  return {
@@ -63101,7 +63292,7 @@ function buildModelDeps() {
63101
63292
  try {
63102
63293
  const agentDir = resolveAgentDirFromEnv();
63103
63294
  if (agentDir) {
63104
- const local = await fetchQuota2({ claudeConfigDir: join35(agentDir, ".claude") });
63295
+ const local = await fetchQuota2({ claudeConfigDir: join36(agentDir, ".claude") });
63105
63296
  if (local.ok)
63106
63297
  return formatQuotaLine2(local.data);
63107
63298
  }
@@ -63267,7 +63458,7 @@ bot.command("restart", async (ctx) => {
63267
63458
  function flushAgentHandoff(agentDir) {
63268
63459
  let removed = 0;
63269
63460
  for (const fname of [".handoff.md", ".handoff-topic"]) {
63270
- const p = join35(agentDir, fname);
63461
+ const p = join36(agentDir, fname);
63271
63462
  try {
63272
63463
  if (existsSync38(p)) {
63273
63464
  unlinkSync14(p);
@@ -63280,7 +63471,8 @@ function flushAgentHandoff(agentDir) {
63280
63471
  }
63281
63472
  return removed;
63282
63473
  }
63283
- async function handleNewOrResetCommand(ctx, kind) {
63474
+ async function handleNewCommand(ctx) {
63475
+ const kind = "new";
63284
63476
  if (!isAuthorizedSender(ctx))
63285
63477
  return;
63286
63478
  const name = (typeof ctx.match === "string" ? ctx.match : "").trim() || getMyAgentName();
@@ -63307,7 +63499,7 @@ async function handleNewOrResetCommand(ctx, kind) {
63307
63499
  const flushed = agentDir != null ? flushAgentHandoff(agentDir) : 0;
63308
63500
  const chatId = String(ctx.chat.id);
63309
63501
  const threadId = resolveThreadId(chatId, ctx.message?.message_thread_id);
63310
- const ackText = kind === "new" ? newSessionAckText(name, flushed > 0) : resetSessionAckText(name, flushed > 0);
63502
+ const ackText = newSessionAckText(name, flushed > 0);
63311
63503
  let ackId = null;
63312
63504
  try {
63313
63505
  const sent = await retryWithThreadFallback(robustApiCall, (tid) => lockedBot.api.sendMessage(chatId, ackText, {
@@ -63325,7 +63517,7 @@ async function handleNewOrResetCommand(ctx, kind) {
63325
63517
  writeRestartMarker({ chat_id: chatId, thread_id: threadId ?? null, ack_message_id: ackId, ts: Date.now() });
63326
63518
  if (agentDir != null) {
63327
63519
  try {
63328
- writeFileSync25(join35(agentDir, ".force-fresh-session"), `${kind} at ${new Date().toISOString()}
63520
+ writeFileSync26(join36(agentDir, ".force-fresh-session"), `${kind} at ${new Date().toISOString()}
63329
63521
  `, "utf8");
63330
63522
  } catch (err) {
63331
63523
  process.stderr.write(`telegram gateway: failed to write force-fresh marker: ${err}
@@ -63352,8 +63544,7 @@ async function handleNewOrResetCommand(ctx, kind) {
63352
63544
  await switchroomReply(ctx, `\u274C <b>${escapeHtmlForTg(kind)} ${escapeHtmlForTg(name)} failed via hostd</b> (result=${escapeHtmlForTg(hostdResp.result)}):
63353
63545
  ` + preBlock(hostdResp.error ?? "(no error message)"), { html: true });
63354
63546
  }
63355
- bot.command("new", async (ctx) => handleNewOrResetCommand(ctx, "new"));
63356
- bot.command("reset", async (ctx) => handleNewOrResetCommand(ctx, "reset"));
63547
+ bot.command("new", async (ctx) => handleNewCommand(ctx));
63357
63548
  bot.command("update", async (ctx) => {
63358
63549
  if (!isAuthorizedSender(ctx))
63359
63550
  return;
@@ -63691,10 +63882,10 @@ bot.command("interrupt", async (ctx) => {
63691
63882
  });
63692
63883
  var lockoutOps = {
63693
63884
  readFileSync: (p, enc) => readFileSync36(p, enc),
63694
- writeFileSync: (p, data, opts) => writeFileSync25(p, data, opts),
63885
+ writeFileSync: (p, data, opts) => writeFileSync26(p, data, opts),
63695
63886
  existsSync: (p) => existsSync38(p),
63696
- mkdirSync: (p, opts) => mkdirSync26(p, opts),
63697
- joinPath: (...parts) => join35(...parts)
63887
+ mkdirSync: (p, opts) => mkdirSync27(p, opts),
63888
+ joinPath: (...parts) => join36(...parts)
63698
63889
  };
63699
63890
  var FLEET_FALLBACK_DEDUP_MS = 30000;
63700
63891
  function isAuthBrokerSocketReachable() {
@@ -63784,7 +63975,7 @@ async function runCreditWatch() {
63784
63975
  if (!agentDir)
63785
63976
  return;
63786
63977
  const agentName3 = getMyAgentName();
63787
- const claudeConfigDir = join35(agentDir, ".claude");
63978
+ const claudeConfigDir = join36(agentDir, ".claude");
63788
63979
  const stateDir = STATE_DIR;
63789
63980
  const reason = readClaudeJsonOverage(claudeConfigDir);
63790
63981
  const prev = loadCreditState(stateDir);
@@ -64310,10 +64501,10 @@ async function handleVaultRecentDenialCallback(ctx, data) {
64310
64501
  return;
64311
64502
  }
64312
64503
  const { token, id } = result;
64313
- const tokenPath = join35(homedir14(), ".switchroom", "agents", agentName3, ".vault-token");
64504
+ const tokenPath = join36(homedir14(), ".switchroom", "agents", agentName3, ".vault-token");
64314
64505
  try {
64315
- mkdirSync26(join35(homedir14(), ".switchroom", "agents", agentName3), { recursive: true });
64316
- writeFileSync25(tokenPath, token, { mode: 384 });
64506
+ mkdirSync27(join36(homedir14(), ".switchroom", "agents", agentName3), { recursive: true });
64507
+ writeFileSync26(tokenPath, token, { mode: 384 });
64317
64508
  } catch (err) {
64318
64509
  await switchroomReply(ctx, `<b>Grant created (${escapeHtmlForTg(id)}) but token write failed:</b> ${escapeHtmlForTg(String(err))}
64319
64510
  <i>Recover with: <code>switchroom vault grant ${escapeHtmlForTg(agentName3)} --keys ${escapeHtmlForTg(keyName)} --duration 30d</code> on the host.</i>`, { html: true });
@@ -64389,10 +64580,10 @@ async function performVaultAccessApproval(ctx, pending2, stageId, senderId, atte
64389
64580
  return;
64390
64581
  }
64391
64582
  const { token, id } = result;
64392
- const tokenPath = join35(homedir14(), ".switchroom", "agents", pending2.agent, ".vault-token");
64583
+ const tokenPath = join36(homedir14(), ".switchroom", "agents", pending2.agent, ".vault-token");
64393
64584
  try {
64394
- mkdirSync26(join35(homedir14(), ".switchroom", "agents", pending2.agent), { recursive: true });
64395
- writeFileSync25(tokenPath, token, { mode: 384 });
64585
+ mkdirSync27(join36(homedir14(), ".switchroom", "agents", pending2.agent), { recursive: true });
64586
+ writeFileSync26(tokenPath, token, { mode: 384 });
64396
64587
  } catch (err) {
64397
64588
  await switchroomReply(ctx, `<b>Grant created (${escapeHtmlForTg(id)}) but token write failed:</b> ${escapeHtmlForTg(String(err))}
64398
64589
  <i>Recover with: <code>switchroom vault grant ${escapeHtmlForTg(pending2.agent)} --keys ${escapeHtmlForTg(pending2.key)} --duration ${Math.round(pending2.ttl_seconds / 86400)}d</code> on the host.</i>`, { html: true });
@@ -64871,10 +65062,10 @@ async function executeGrantWizard(ctx, chatId, state4) {
64871
65062
  return;
64872
65063
  }
64873
65064
  const { token, id } = result;
64874
- const tokenPath = join35(homedir14(), ".switchroom", "agents", state4.agent, ".vault-token");
65065
+ const tokenPath = join36(homedir14(), ".switchroom", "agents", state4.agent, ".vault-token");
64875
65066
  try {
64876
- mkdirSync26(join35(homedir14(), ".switchroom", "agents", state4.agent), { recursive: true });
64877
- writeFileSync25(tokenPath, token, { mode: 384 });
65067
+ mkdirSync27(join36(homedir14(), ".switchroom", "agents", state4.agent), { recursive: true });
65068
+ writeFileSync26(tokenPath, token, { mode: 384 });
64878
65069
  } catch (err) {
64879
65070
  await switchroomReply(ctx, `<b>Grant created but token write failed:</b> ${escapeHtmlForTg(String(err))}`, { html: true });
64880
65071
  return;
@@ -65722,7 +65913,7 @@ bot.command("usage", async (ctx) => {
65722
65913
  await switchroomReply(ctx, "<b>/usage:</b> cannot resolve agent dir.", { html: true });
65723
65914
  return;
65724
65915
  }
65725
- const result = await fetchQuota2({ claudeConfigDir: join35(agentDir, ".claude") });
65916
+ const result = await fetchQuota2({ claudeConfigDir: join36(agentDir, ".claude") });
65726
65917
  if (!result.ok) {
65727
65918
  await switchroomReply(ctx, `<b>/usage:</b> ${escapeHtmlForTg(result.reason)}`, { html: true });
65728
65919
  return;
@@ -66462,9 +66653,9 @@ bot.on("message:photo", async (ctx) => {
66462
66653
  fileUniqueId: best.file_unique_id,
66463
66654
  now: Date.now()
66464
66655
  });
66465
- mkdirSync26(INBOX_DIR, { recursive: true, mode: 448 });
66656
+ mkdirSync27(INBOX_DIR, { recursive: true, mode: 448 });
66466
66657
  assertInsideInbox(INBOX_DIR, dlPath);
66467
- writeFileSync25(dlPath, buf, { mode: 384 });
66658
+ writeFileSync26(dlPath, buf, { mode: 384 });
66468
66659
  return dlPath;
66469
66660
  } catch (err) {
66470
66661
  const msg = err instanceof Error ? err.message : "unknown error";
@@ -67243,25 +67434,14 @@ process.on("uncaughtException", (err) => {
67243
67434
  shutdown("uncaughtException");
67244
67435
  });
67245
67436
  var runnerHandle = null;
67246
- var POLL_HEALTH_INTERVAL_MS = Number(process.env.SWITCHROOM_POLL_HEALTH_INTERVAL_MS ?? 300000);
67437
+ var POLL_HEALTH_INTERVAL_MS = Number(process.env.SWITCHROOM_POLL_HEALTH_INTERVAL_MS ?? 60000);
67247
67438
  var POLL_HEALTH_THRESHOLD = Number(process.env.SWITCHROOM_POLL_HEALTH_THRESHOLD ?? 3);
67248
67439
  var pollHealthCheck = null;
67249
67440
  if (POLL_HEALTH_INTERVAL_MS > 0) {
67250
67441
  pollHealthCheck = createPollHealthCheck({
67251
67442
  ping: () => bot.api.getMe(),
67252
67443
  onStall: async () => {
67253
- const agentName3 = process.env.SWITCHROOM_AGENT_NAME ?? "-";
67254
- process.stderr.write(`telegram gateway: poll.health_check.stall_recovery stopping runner agent=${agentName3}
67255
- `);
67256
- if (runnerHandle != null && runnerHandle.isRunning()) {
67257
- try {
67258
- await runnerHandle.stop();
67259
- } catch (err) {
67260
- process.stderr.write(`telegram gateway: poll.health_check.stall_recovery runner.stop error: ${err.message}
67261
- `);
67262
- }
67263
- }
67264
- runnerHandle = null;
67444
+ recoverFromPollStall({ agentName: process.env.SWITCHROOM_AGENT_NAME ?? "-" });
67265
67445
  },
67266
67446
  intervalMs: POLL_HEALTH_INTERVAL_MS,
67267
67447
  failureThreshold: POLL_HEALTH_THRESHOLD,
@@ -67449,7 +67629,7 @@ var didOneTimeSetup = false;
67449
67629
  return;
67450
67630
  }
67451
67631
  })();
67452
- const resolvedAgentDirForBootCard = agentDir ?? join35(homedir14(), ".switchroom", "agents", agentSlug);
67632
+ const resolvedAgentDirForBootCard = agentDir ?? join36(homedir14(), ".switchroom", "agents", agentSlug);
67453
67633
  const handle = await startBootCard(chatId, threadId, botApiForCard, {
67454
67634
  agentName: agentDisplayName,
67455
67635
  agentSlug,
@@ -67463,8 +67643,8 @@ var didOneTimeSetup = false;
67463
67643
  probeQuotaViaBroker: (t) => probeQuotaForBootCard(agentSlug, t),
67464
67644
  tmuxSupervisor: process.env.SWITCHROOM_TMUX_SUPERVISOR === "1",
67465
67645
  dockerMode: process.env.SWITCHROOM_RUNTIME === "docker",
67466
- configSnapshotPath: join35(resolvedAgentDirForBootCard, ".config-snapshot.json"),
67467
- bootCardStatePath: join35(resolvedAgentDirForBootCard, ".boot-card-msgid.json"),
67646
+ configSnapshotPath: join36(resolvedAgentDirForBootCard, ".config-snapshot.json"),
67647
+ bootCardStatePath: join36(resolvedAgentDirForBootCard, ".boot-card-msgid.json"),
67468
67648
  ...updateOutcomeLine ? { updateOutcomeLine } : {}
67469
67649
  }, ackMsgId);
67470
67650
  activeBootCard = handle;
@@ -67851,13 +68031,6 @@ var didOneTimeSetup = false;
67851
68031
  pollHealthCheck?.stop();
67852
68032
  pollHealthCheck?.start();
67853
68033
  await runnerHandle.task();
67854
- if (runnerHandle === null) {
67855
- const agentName3 = process.env.SWITCHROOM_AGENT_NAME ?? "-";
67856
- process.stderr.write(`telegram gateway: poll.health_check.stall_recovery restarting runner agent=${agentName3}
67857
- `);
67858
- await new Promise((r) => setTimeout(r, 2000));
67859
- continue;
67860
- }
67861
68034
  return;
67862
68035
  } catch (err) {
67863
68036
  if (err instanceof import_grammy9.GrammyError && err.error_code === 409) {