switchroom 0.21.7 → 0.21.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 (34) hide show
  1. package/bin/tmp-reaper.sh +234 -0
  2. package/dist/agent-scheduler/index.js +1 -1
  3. package/dist/auth-broker/index.js +2 -2
  4. package/dist/cli/notion-write-pretool.mjs +1 -1
  5. package/dist/cli/switchroom.js +3421 -2744
  6. package/dist/host-control/main.js +177 -13
  7. package/dist/vault/approvals/kernel-server.js +2 -2
  8. package/dist/vault/broker/server.js +2 -2
  9. package/package.json +5 -4
  10. package/profiles/_base/start.sh.hbs +115 -0
  11. package/profiles/_shared/local-time.md.hbs +6 -0
  12. package/profiles/default/CLAUDE.md.hbs +0 -12
  13. package/telegram-plugin/dist/gateway/gateway.js +1017 -465
  14. package/telegram-plugin/gateway/agent-process-liveness.ts +558 -0
  15. package/telegram-plugin/gateway/approval-hold.ts +32 -1
  16. package/telegram-plugin/gateway/approval-outcome-sources.ts +274 -0
  17. package/telegram-plugin/gateway/bridge-dead-watchdog.ts +21 -9
  18. package/telegram-plugin/gateway/callback-query-handlers.ts +87 -15
  19. package/telegram-plugin/gateway/eval-case-proposal-inbound-builders.ts +197 -0
  20. package/telegram-plugin/gateway/gateway.ts +12 -10
  21. package/telegram-plugin/gateway/pending-inbound-buffer.ts +167 -11
  22. package/telegram-plugin/gateway/self-improve-proposal-wiring.test.ts +333 -0
  23. package/telegram-plugin/gateway/self-improve-proposal-wiring.ts +152 -3
  24. package/telegram-plugin/gateway/subagent-handback-marker.ts +19 -0
  25. package/telegram-plugin/tests/agent-process-liveness.test.ts +406 -0
  26. package/telegram-plugin/tests/approval-hold-record.test.ts +21 -8
  27. package/telegram-plugin/tests/boot-resume-gateway-only-respawn.test.ts +752 -0
  28. package/telegram-plugin/tests/boot-resume-guard-wiring.test.ts +203 -0
  29. package/telegram-plugin/tests/callback-query-handlers.test.ts +143 -1
  30. package/telegram-plugin/tests/eval-case-proposal-inbound-builders.test.ts +144 -0
  31. package/telegram-plugin/tests/hermes-messages-paging.test.ts +149 -0
  32. package/telegram-plugin/tests/hermes-session-search.test.ts +146 -0
  33. package/telegram-plugin/tests/pending-inbound-buffer.test.ts +443 -2
  34. package/telegram-plugin/tests/subagent-handback-marker.test.ts +14 -0
@@ -22355,7 +22355,7 @@ var init_schema = __esm(() => {
22355
22355
  repos: exports_external.record(exports_external.string().regex(/^[a-z0-9][a-z0-9-]*$/, "Repo slug must be kebab-case ASCII: start with a lowercase letter or digit, contain only lowercase letters, digits, and hyphens"), exports_external.object({
22356
22356
  url: exports_external.string().min(1).describe("Git remote URL for the repo (e.g. 'git@github.com:org/repo.git' or " + "'https://github.com/org/repo.git'). Used verbatim for git clone."),
22357
22357
  branch_default: exports_external.string().optional().describe("Default branch to track (defaults to the remote's HEAD, typically 'main'). " + "The per-agent branch 'agent/<agentName>/main' fast-forwards to this branch " + "when the worktree is clean on session start.")
22358
- })).optional().describe("Repos this agent operates on. Switchroom provisions a dedicated worktree for each " + "repo at <agentDir>/work/<slug>/ on branch agent/<agentName>/main, backed by a " + "shared bare clone at ~/.switchroom/repos/<slug>.git. The worktree path is injected " + "into the agent's environment as SWITCHROOM_REPO_<SLUG_UPPER>. " + "Agents without this field continue to work unchanged."),
22358
+ })).optional().describe("Repos this agent operates on. Switchroom provisions a dedicated standing tree for " + "each repo at <agentDir>/work/<slug>/ on branch agent/<agentName>/main \u2014 an " + "independent clone (own refs/stash, hardlinked objects) seeded from a shared bare " + "clone at ~/.switchroom/repos/<slug>.git. The tree's path is injected " + "into the agent's environment as SWITCHROOM_REPO_<SLUG_UPPER>. " + "Agents without this field continue to work unchanged."),
22359
22359
  experimental: exports_external.object({
22360
22360
  legacy_pty: exports_external.boolean().optional().describe("Opt out of the default tmux supervisor (#725) and run the agent " + "under the legacy PTY supervisor instead. Default: false."),
22361
22361
  legacy_autoaccept_expect: exports_external.boolean().optional().describe("Opt the autoaccept gateway back into the legacy expect-script " + "behaviour instead of the tmux send-keys path. Default: false.")
@@ -24113,7 +24113,7 @@ var init_approval_card = __esm(() => {
24113
24113
  });
24114
24114
 
24115
24115
  // flood-429-ledger.ts
24116
- import { readFileSync as readFileSync15, writeFileSync as writeFileSync13, mkdirSync as mkdirSync17, chmodSync as chmodSync4, unlinkSync as unlinkSync9 } from "node:fs";
24116
+ import { readFileSync as readFileSync15, writeFileSync as writeFileSync14, mkdirSync as mkdirSync17, chmodSync as chmodSync4, unlinkSync as unlinkSync10 } from "node:fs";
24117
24117
  import { dirname as dirname9, join as join20 } from "node:path";
24118
24118
  function flood429LedgerPathFromFloodState(floodStatePath) {
24119
24119
  return join20(dirname9(floodStatePath), FLOOD_429_LEDGER_FILE);
@@ -24217,7 +24217,7 @@ function writeFlood429Ledger(path2, episodes, log = (l) => process.stderr.write(
24217
24217
  mkdirSync17(dirname9(path2), { recursive: true });
24218
24218
  } catch {}
24219
24219
  try {
24220
- writeFileSync13(path2, payload, { mode: FLOOD_429_LEDGER_MODE });
24220
+ writeFileSync14(path2, payload, { mode: FLOOD_429_LEDGER_MODE });
24221
24221
  try {
24222
24222
  chmodSync4(path2, FLOOD_429_LEDGER_MODE);
24223
24223
  } catch {}
@@ -24230,8 +24230,8 @@ function writeFlood429Ledger(path2, episodes, log = (l) => process.stderr.write(
24230
24230
  return;
24231
24231
  }
24232
24232
  try {
24233
- unlinkSync9(path2);
24234
- writeFileSync13(path2, payload, { mode: FLOOD_429_LEDGER_MODE });
24233
+ unlinkSync10(path2);
24234
+ writeFileSync14(path2, payload, { mode: FLOOD_429_LEDGER_MODE });
24235
24235
  } catch (err2) {
24236
24236
  log(`telegram gateway: 429-ledger: cannot persist the 429 pressure ledger to ${path2} ` + `(${code2}, recreate failed: ${err2?.message ?? String(err2)}). ` + `Flood-pressure history will be missing from \`switchroom doctor\`
24237
24237
  `);
@@ -24253,11 +24253,11 @@ var init_flood_429_ledger = __esm(() => {
24253
24253
  import {
24254
24254
  existsSync as existsSync14,
24255
24255
  readFileSync as readFileSync16,
24256
- writeFileSync as writeFileSync14,
24256
+ writeFileSync as writeFileSync15,
24257
24257
  mkdirSync as mkdirSync18,
24258
24258
  chmodSync as chmodSync5,
24259
- unlinkSync as unlinkSync10,
24260
- renameSync as renameSync10
24259
+ unlinkSync as unlinkSync11,
24260
+ renameSync as renameSync11
24261
24261
  } from "node:fs";
24262
24262
  import { dirname as dirname10, join as join21 } from "node:path";
24263
24263
  function floodStatePath(stateDir) {
@@ -24313,7 +24313,7 @@ function writeFloodState(path2, state, log = (l) => process.stderr.write(l)) {
24313
24313
  mkdirSync18(dirname10(path2), { recursive: true });
24314
24314
  } catch {}
24315
24315
  try {
24316
- writeFileSync14(path2, payload, { mode: FLOOD_STATE_MODE });
24316
+ writeFileSync15(path2, payload, { mode: FLOOD_STATE_MODE });
24317
24317
  try {
24318
24318
  chmodSync5(path2, FLOOD_STATE_MODE);
24319
24319
  } catch {}
@@ -24326,8 +24326,8 @@ function writeFloodState(path2, state, log = (l) => process.stderr.write(l)) {
24326
24326
  return;
24327
24327
  }
24328
24328
  try {
24329
- unlinkSync10(path2);
24330
- writeFileSync14(path2, payload, { mode: FLOOD_STATE_MODE });
24329
+ unlinkSync11(path2);
24330
+ writeFileSync15(path2, payload, { mode: FLOOD_STATE_MODE });
24331
24331
  log(`telegram gateway: flood-breaker: recreated ${path2} \u2014 it was owned by another uid ` + `and could not be updated (${code2}); the breaker was blind (issue #3106)
24332
24332
  `);
24333
24333
  } catch (err2) {
@@ -30636,7 +30636,7 @@ var init_turn_flush_suppression = () => {};
30636
30636
 
30637
30637
  // ../src/agents/generation-stamp.ts
30638
30638
  import { createHash as createHash6 } from "node:crypto";
30639
- import { existsSync as existsSync36, readFileSync as readFileSync35, writeFileSync as writeFileSync30 } from "node:fs";
30639
+ import { existsSync as existsSync36, readFileSync as readFileSync35, writeFileSync as writeFileSync31 } from "node:fs";
30640
30640
  import { join as join44 } from "node:path";
30641
30641
  function sha256Text(s) {
30642
30642
  return createHash6("sha256").update(s, "utf-8").digest("hex");
@@ -36473,7 +36473,7 @@ function renderAuthLine(state7, agentName3, now = Date.now()) {
36473
36473
  }
36474
36474
 
36475
36475
  // gateway/quota-cache.ts
36476
- import { existsSync as existsSync49, readFileSync as readFileSync49, writeFileSync as writeFileSync40, mkdirSync as mkdirSync42 } from "fs";
36476
+ import { existsSync as existsSync49, readFileSync as readFileSync49, writeFileSync as writeFileSync41, mkdirSync as mkdirSync42 } from "fs";
36477
36477
  import { join as join59, dirname as dirname25 } from "path";
36478
36478
  function defaultCachePath() {
36479
36479
  return process.env.SWITCHROOM_QUOTA_CACHE_PATH ?? join59(process.env.HOME ?? "/tmp", ".switchroom", "quota-cache.json");
@@ -36512,7 +36512,7 @@ function writeQuotaCache(result, opts = {}) {
36512
36512
  };
36513
36513
  try {
36514
36514
  mkdirSync42(dirname25(path3), { recursive: true });
36515
- writeFileSync40(path3, JSON.stringify(entry, null, 2), { mode: 384 });
36515
+ writeFileSync41(path3, JSON.stringify(entry, null, 2), { mode: 384 });
36516
36516
  } catch {}
36517
36517
  }
36518
36518
  var DEFAULT_TTL_MS4, RATE_LIMIT_TTL_MS;
@@ -37383,7 +37383,7 @@ var init_boot_probes = __esm(() => {
37383
37383
  });
37384
37384
 
37385
37385
  // gateway/boot-issue-cache.ts
37386
- import { existsSync as existsSync51, readFileSync as readFileSync51, writeFileSync as writeFileSync41, mkdirSync as mkdirSync43, renameSync as renameSync24 } from "fs";
37386
+ import { existsSync as existsSync51, readFileSync as readFileSync51, writeFileSync as writeFileSync42, mkdirSync as mkdirSync43, renameSync as renameSync25 } from "fs";
37387
37387
  import { dirname as dirname26 } from "path";
37388
37388
  function fingerprintProbe(key, r) {
37389
37389
  if (r.status === "ok")
@@ -37476,7 +37476,7 @@ function loadCache(path3, now = Date.now) {
37476
37476
  parsed = JSON.parse(raw);
37477
37477
  } catch {
37478
37478
  try {
37479
- renameSync24(path3, `${path3}.corrupt-${now()}`);
37479
+ renameSync25(path3, `${path3}.corrupt-${now()}`);
37480
37480
  } catch {}
37481
37481
  return { ...EMPTY_CACHE, probes: {} };
37482
37482
  }
@@ -37512,8 +37512,8 @@ function applyAndSave(path3, cache, diff) {
37512
37512
  try {
37513
37513
  mkdirSync43(dirname26(path3), { recursive: true });
37514
37514
  const tmp = `${path3}.tmp`;
37515
- writeFileSync41(tmp, JSON.stringify(next), { mode: 384 });
37516
- renameSync24(tmp, path3);
37515
+ writeFileSync42(tmp, JSON.stringify(next), { mode: 384 });
37516
+ renameSync25(tmp, path3);
37517
37517
  } catch {}
37518
37518
  return next;
37519
37519
  }
@@ -37526,7 +37526,7 @@ var init_boot_issue_cache = __esm(() => {
37526
37526
 
37527
37527
  // gateway/config-snapshot.ts
37528
37528
  import { createHash as createHash7 } from "crypto";
37529
- import { existsSync as existsSync52, readFileSync as readFileSync52, writeFileSync as writeFileSync42, mkdirSync as mkdirSync44, renameSync as renameSync25 } from "fs";
37529
+ import { existsSync as existsSync52, readFileSync as readFileSync52, writeFileSync as writeFileSync43, mkdirSync as mkdirSync44, renameSync as renameSync26 } from "fs";
37530
37530
  import { dirname as dirname27 } from "path";
37531
37531
  function hashStringArray(items) {
37532
37532
  if (!items || items.length === 0)
@@ -37604,7 +37604,7 @@ function loadSnapshot(path3, now = Date.now) {
37604
37604
  parsed = JSON.parse(raw);
37605
37605
  } catch {
37606
37606
  try {
37607
- renameSync25(path3, `${path3}.corrupt-${now()}`);
37607
+ renameSync26(path3, `${path3}.corrupt-${now()}`);
37608
37608
  } catch {}
37609
37609
  return null;
37610
37610
  }
@@ -37627,8 +37627,8 @@ function persistSnapshot(path3, snapshot) {
37627
37627
  try {
37628
37628
  mkdirSync44(dirname27(path3), { recursive: true });
37629
37629
  const tmp = `${path3}.tmp`;
37630
- writeFileSync42(tmp, JSON.stringify(snapshot), { mode: 384 });
37631
- renameSync25(tmp, path3);
37630
+ writeFileSync43(tmp, JSON.stringify(snapshot), { mode: 384 });
37631
+ renameSync26(tmp, path3);
37632
37632
  } catch {}
37633
37633
  }
37634
37634
  var init_config_snapshot = __esm(() => {
@@ -37636,7 +37636,7 @@ var init_config_snapshot = __esm(() => {
37636
37636
  });
37637
37637
 
37638
37638
  // gateway/boot-card-msgid.ts
37639
- import { readFileSync as readFileSync53, writeFileSync as writeFileSync43 } from "node:fs";
37639
+ import { readFileSync as readFileSync53, writeFileSync as writeFileSync44 } from "node:fs";
37640
37640
  function bootCardChatKey(chatId, threadId) {
37641
37641
  return `${chatId}:${threadId ?? ""}`;
37642
37642
  }
@@ -37661,7 +37661,7 @@ function saveBootCardMsgId(path3, chatKey3, messageId) {
37661
37661
  if (store3[chatKey3] === messageId)
37662
37662
  return;
37663
37663
  store3[chatKey3] = messageId;
37664
- writeFileSync43(path3, JSON.stringify(store3), "utf8");
37664
+ writeFileSync44(path3, JSON.stringify(store3), "utf8");
37665
37665
  } catch {}
37666
37666
  }
37667
37667
  var init_boot_card_msgid = () => {};
@@ -38695,7 +38695,7 @@ __export(exports_tmux2, {
38695
38695
  captureAgentPane: () => captureAgentPane2
38696
38696
  });
38697
38697
  import { execFileSync as execFileSync8 } from "node:child_process";
38698
- import { chmodSync as chmodSync13, mkdirSync as mkdirSync49, readdirSync as readdirSync17, statSync as statSync24, unlinkSync as unlinkSync30, writeFileSync as writeFileSync52 } from "node:fs";
38698
+ import { chmodSync as chmodSync13, mkdirSync as mkdirSync49, readdirSync as readdirSync18, statSync as statSync24, unlinkSync as unlinkSync32, writeFileSync as writeFileSync54 } from "node:fs";
38699
38699
  import { resolve as resolve12 } from "node:path";
38700
38700
  function captureAgentPane2(opts) {
38701
38701
  const { agentName: agentName3, agentDir, reason } = opts;
@@ -38746,7 +38746,7 @@ function captureAgentPane2(opts) {
38746
38746
  ` + `
38747
38747
  `;
38748
38748
  try {
38749
- writeFileSync52(outPath, Buffer.concat([Buffer.from(header, "utf8"), body]), {
38749
+ writeFileSync54(outPath, Buffer.concat([Buffer.from(header, "utf8"), body]), {
38750
38750
  mode: 384
38751
38751
  });
38752
38752
  } catch (err) {
@@ -38816,7 +38816,7 @@ function sleepSync3(ms) {
38816
38816
  function pruneOldReports2(dir, retain) {
38817
38817
  let entries;
38818
38818
  try {
38819
- entries = readdirSync17(dir);
38819
+ entries = readdirSync18(dir);
38820
38820
  } catch {
38821
38821
  return;
38822
38822
  }
@@ -38830,7 +38830,7 @@ function pruneOldReports2(dir, retain) {
38830
38830
  }).sort((a, b) => b.mtimeMs - a.mtimeMs);
38831
38831
  for (const stale of files.slice(retain)) {
38832
38832
  try {
38833
- unlinkSync30(stale.full);
38833
+ unlinkSync32(stale.full);
38834
38834
  } catch {}
38835
38835
  }
38836
38836
  }
@@ -39640,7 +39640,7 @@ __export(exports_materialize_bot_token, {
39640
39640
  materializeBotToken: () => materializeBotToken,
39641
39641
  BotTokenMaterializeError: () => BotTokenMaterializeError
39642
39642
  });
39643
- import { existsSync as existsSync63 } from "node:fs";
39643
+ import { existsSync as existsSync64 } from "node:fs";
39644
39644
  function pickConfiguredToken(config, agentName3) {
39645
39645
  if (agentName3) {
39646
39646
  const agent = config.agents?.[agentName3];
@@ -39654,7 +39654,7 @@ function tryDirectVaultRead4(ref, config, passphrase) {
39654
39654
  if (!passphrase)
39655
39655
  return null;
39656
39656
  const vaultPath = resolvePath(config.vault?.path ?? "~/.switchroom/vault.enc");
39657
- if (!existsSync63(vaultPath))
39657
+ if (!existsSync64(vaultPath))
39658
39658
  return null;
39659
39659
  try {
39660
39660
  const secrets = openVault(passphrase, vaultPath);
@@ -39809,19 +39809,19 @@ var import_runner3 = __toESM(require_mod3(), 1);
39809
39809
  import { randomBytes as randomBytes12, createHash as createHash8 } from "crypto";
39810
39810
  import { execFileSync as execFileSync9, execSync as execSync2, spawn as spawn2 } from "child_process";
39811
39811
  import {
39812
- readFileSync as readFileSync66,
39813
- writeFileSync as writeFileSync53,
39812
+ readFileSync as readFileSync67,
39813
+ writeFileSync as writeFileSync55,
39814
39814
  mkdirSync as mkdirSync50,
39815
- readdirSync as readdirSync18,
39815
+ readdirSync as readdirSync19,
39816
39816
  rmSync as rmSync9,
39817
39817
  statSync as statSync25,
39818
- renameSync as renameSync30,
39818
+ renameSync as renameSync32,
39819
39819
  realpathSync as realpathSync6,
39820
39820
  chmodSync as chmodSync14,
39821
39821
  openSync as openSync18,
39822
39822
  closeSync as closeSync18,
39823
- existsSync as existsSync64,
39824
- unlinkSync as unlinkSync31,
39823
+ existsSync as existsSync65,
39824
+ unlinkSync as unlinkSync33,
39825
39825
  appendFileSync as appendFileSync11
39826
39826
  } from "fs";
39827
39827
 
@@ -39839,7 +39839,7 @@ function fsyncPathSync(path) {
39839
39839
 
39840
39840
  // gateway/gateway.ts
39841
39841
  import { homedir as homedir22 } from "os";
39842
- import { join as join76, sep as sep6, basename as basename18 } from "path";
39842
+ import { join as join77, sep as sep6, basename as basename19 } from "path";
39843
39843
 
39844
39844
  // plugin-logger.ts
39845
39845
  import { appendFileSync, mkdirSync, renameSync as renameSync2, statSync, existsSync } from "fs";
@@ -50974,7 +50974,7 @@ function createSweepableCardStore(deps) {
50974
50974
  init_rich_send();
50975
50975
  var import_grammy4 = __toESM(require_mod2(), 1);
50976
50976
  import { execFileSync as execFileSync3 } from "child_process";
50977
- import { mkdirSync as mkdirSync15, writeFileSync as writeFileSync10 } from "fs";
50977
+ import { mkdirSync as mkdirSync15, writeFileSync as writeFileSync11 } from "fs";
50978
50978
  import { dirname as dirname7 } from "path";
50979
50979
  init_client2();
50980
50980
 
@@ -51754,11 +51754,15 @@ import {
51754
51754
  mkdirSync as mkdirSync14,
51755
51755
  openSync as openSync8,
51756
51756
  readFileSync as readFileSync13,
51757
+ renameSync as renameSync10,
51758
+ unlinkSync as unlinkSync6,
51759
+ writeFileSync as writeFileSync10,
51757
51760
  writeSync as writeSync7
51758
51761
  } from "node:fs";
51759
51762
  import { join as join15 } from "node:path";
51760
51763
  import { randomUUID as randomUUID4 } from "node:crypto";
51761
51764
  var EVAL_CASE_PROPOSALS_FILE = "eval-case-proposals.jsonl";
51765
+ var MAX_EVAL_CASE_PROPOSALS = 200;
51762
51766
  function proposalsPath2(stateDir) {
51763
51767
  return join15(stateDir, EVAL_CASE_PROPOSALS_FILE);
51764
51768
  }
@@ -51801,6 +51805,49 @@ function readEvalCaseProposals(stateDir) {
51801
51805
  }
51802
51806
  return [...byId.values()];
51803
51807
  }
51808
+ function withinBound(all, now) {
51809
+ const isLiveRejection = (r) => {
51810
+ if (r.status !== "rejected")
51811
+ return false;
51812
+ const age = now - new Date(r.created_at).getTime();
51813
+ return !Number.isFinite(age) || age <= REJECTION_TTL_MS;
51814
+ };
51815
+ const keepTail = (rows) => rows.length > MAX_EVAL_CASE_PROPOSALS ? rows.slice(rows.length - MAX_EVAL_CASE_PROPOSALS) : rows;
51816
+ const keep = new Set([
51817
+ ...keepTail(all.filter(isLiveRejection)).map((r) => r.id),
51818
+ ...keepTail(all.filter((r) => !isLiveRejection(r))).map((r) => r.id)
51819
+ ]);
51820
+ return all.filter((r) => keep.has(r.id));
51821
+ }
51822
+ function compactIfOversized(stateDir, now) {
51823
+ const path = proposalsPath2(stateDir);
51824
+ let raw;
51825
+ try {
51826
+ raw = readFileSync13(path, "utf-8");
51827
+ } catch {
51828
+ return;
51829
+ }
51830
+ const lines = raw.split(`
51831
+ `).filter((l) => l.trim().length > 0);
51832
+ if (lines.length <= MAX_EVAL_CASE_PROPOSALS * 2)
51833
+ return;
51834
+ const kept = withinBound(readEvalCaseProposals(stateDir), now);
51835
+ if (kept.length >= lines.length)
51836
+ return;
51837
+ const tmp = `${path}.tmp.${process.pid}`;
51838
+ try {
51839
+ writeFileSync10(tmp, kept.map((r) => JSON.stringify(r) + `
51840
+ `).join(""), {
51841
+ encoding: "utf-8",
51842
+ mode: 420
51843
+ });
51844
+ renameSync10(tmp, path);
51845
+ } catch {
51846
+ try {
51847
+ unlinkSync6(tmp);
51848
+ } catch {}
51849
+ }
51850
+ }
51804
51851
  function getEvalCaseProposal(stateDir, id) {
51805
51852
  return readEvalCaseProposals(stateDir).find((p) => p.id === id);
51806
51853
  }
@@ -51814,6 +51861,7 @@ function enqueueEvalCaseProposal(stateDir, input, opts = {}) {
51814
51861
  ...input
51815
51862
  };
51816
51863
  appendLine2(proposalsPath2(stateDir), proposal);
51864
+ compactIfOversized(stateDir, now());
51817
51865
  return proposal;
51818
51866
  }
51819
51867
  function setEvalCaseProposalStatus(stateDir, id, status) {
@@ -51822,8 +51870,94 @@ function setEvalCaseProposalStatus(stateDir, id, status) {
51822
51870
  return;
51823
51871
  const next = { ...cur, status };
51824
51872
  appendLine2(proposalsPath2(stateDir), next);
51873
+ compactIfOversized(stateDir, Date.now());
51825
51874
  return next;
51826
51875
  }
51876
+
51877
+ // gateway/eval-case-proposal-inbound-builders.ts
51878
+ function commonMeta(opts) {
51879
+ return {
51880
+ agent: opts.ctx.agent,
51881
+ ...opts.ctx.threadId != null ? { message_thread_id: String(opts.ctx.threadId) } : {},
51882
+ proposal_id: opts.proposalId,
51883
+ skill_slug: opts.skillSlug,
51884
+ held_out: String(opts.heldOut),
51885
+ operator_id: opts.operatorId
51886
+ };
51887
+ }
51888
+ function buildEvalCaseAppliedInbound(opts) {
51889
+ const ts = opts.nowMs ?? Date.now();
51890
+ const sink2 = opts.heldOut ? "the held-out sink" : `\`${opts.skillSlug}\`'s \`evals.json\``;
51891
+ return {
51892
+ type: "inbound",
51893
+ chatId: opts.ctx.chat_id,
51894
+ ...opts.ctx.threadId != null ? { threadId: opts.ctx.threadId } : {},
51895
+ messageId: ts,
51896
+ user: "self-improve",
51897
+ userId: 0,
51898
+ ts,
51899
+ text: `\u2705 Operator approved your proposed eval case for \`${opts.skillSlug}\` ` + `(proposal ${opts.proposalId}). The case was applied DETERMINISTICALLY by ` + `the gateway and is already written to ${sink2} \u2014 do NOT write it yourself ` + `and do NOT re-propose it. Resume whatever you were doing.`,
51900
+ meta: {
51901
+ source: "eval_case_applied",
51902
+ ...commonMeta({
51903
+ ctx: opts.ctx,
51904
+ proposalId: opts.proposalId,
51905
+ skillSlug: opts.skillSlug,
51906
+ heldOut: opts.heldOut,
51907
+ operatorId: opts.operatorId
51908
+ })
51909
+ }
51910
+ };
51911
+ }
51912
+ function buildEvalCaseRejectedInbound(opts) {
51913
+ const ts = opts.nowMs ?? Date.now();
51914
+ return {
51915
+ type: "inbound",
51916
+ chatId: opts.ctx.chat_id,
51917
+ ...opts.ctx.threadId != null ? { threadId: opts.ctx.threadId } : {},
51918
+ messageId: ts,
51919
+ user: "self-improve",
51920
+ userId: 0,
51921
+ ts,
51922
+ text: `\uD83D\uDEAB Operator dismissed your proposed eval case for \`${opts.skillSlug}\` ` + `(proposal ${opts.proposalId}). NOTHING was written \u2014 no eval case was ` + `added. Carry on with the original task without it. Do NOT re-propose the ` + `same case without first asking the user.`,
51923
+ meta: {
51924
+ source: "eval_case_rejected",
51925
+ ...commonMeta({
51926
+ ctx: opts.ctx,
51927
+ proposalId: opts.proposalId,
51928
+ skillSlug: opts.skillSlug,
51929
+ heldOut: opts.heldOut,
51930
+ operatorId: opts.operatorId
51931
+ })
51932
+ }
51933
+ };
51934
+ }
51935
+ function buildEvalCaseApplyFailedInbound(opts) {
51936
+ const ts = opts.nowMs ?? Date.now();
51937
+ const tail = opts.applyOut.trim().slice(0, 500);
51938
+ return {
51939
+ type: "inbound",
51940
+ chatId: opts.ctx.chat_id,
51941
+ ...opts.ctx.threadId != null ? { threadId: opts.ctx.threadId } : {},
51942
+ messageId: ts,
51943
+ user: "self-improve",
51944
+ userId: 0,
51945
+ ts,
51946
+ text: `\u26a0\ufe0f The operator approved your proposed eval case for \`${opts.skillSlug}\` ` + `(proposal ${opts.proposalId}) but the applier FAILED. The case was NOT ` + `written \u2014 do NOT assume the regression test exists. Applier output:
51947
+ ` + `${tail.length > 0 ? tail : "(no output)"}
51948
+ ` + `Carry on with the original task; report the failure to the operator if it ` + `still matters.`,
51949
+ meta: {
51950
+ source: "eval_case_apply_failed",
51951
+ ...commonMeta({
51952
+ ctx: opts.ctx,
51953
+ proposalId: opts.proposalId,
51954
+ skillSlug: opts.skillSlug,
51955
+ heldOut: opts.heldOut,
51956
+ operatorId: opts.operatorId
51957
+ })
51958
+ }
51959
+ };
51960
+ }
51827
51961
  // gateway/callback-query-handlers.ts
51828
51962
  init_auth_snapshot_format();
51829
51963
 
@@ -52003,13 +52137,14 @@ var AgentSmokeRequestSchema = exports_external.object({
52003
52137
  deep: exports_external.boolean().optional()
52004
52138
  })
52005
52139
  });
52140
+ var CANONICAL_CONFIG_PATH = "/state/config/switchroom.yaml";
52006
52141
  var ConfigProposeEditRequestSchema = exports_external.object({
52007
52142
  ...RequestEnvelope,
52008
52143
  op: exports_external.literal("config_propose_edit"),
52009
52144
  args: exports_external.object({
52010
52145
  unified_diff: exports_external.string().min(1).max(MAX_FRAME_BYTES3 - 1024),
52011
52146
  reason: exports_external.string().min(1).max(500),
52012
- target_path: exports_external.literal("/state/config/switchroom.yaml")
52147
+ target_path: exports_external.literal(CANONICAL_CONFIG_PATH)
52013
52148
  })
52014
52149
  });
52015
52150
  var RequestSchema3 = exports_external.discriminatedUnion("op", [
@@ -52335,6 +52470,20 @@ function createCallbackQueryHandlers(deps) {
52335
52470
  const listViaBroker2 = deps.brokerList ?? listViaBroker;
52336
52471
  const listGrantsViaBroker2 = deps.brokerListGrants ?? listGrantsViaBroker;
52337
52472
  const vaultTokenFilePath2 = deps.brokerVaultTokenFilePath ?? vaultTokenFilePath;
52473
+ const runEvalCaseApply = deps.runEvalCaseApply ?? ((id) => {
52474
+ const cli = process.env.SWITCHROOM_CLI_PATH ?? "switchroom";
52475
+ try {
52476
+ const out = execFileSync3(cli, ["self-improve", "apply-eval-case", "--id", id], { encoding: "utf8", timeout: 15000, env: process.env }).trim();
52477
+ return { ok: true, out };
52478
+ } catch (err) {
52479
+ const e = err;
52480
+ return {
52481
+ ok: false,
52482
+ out: [e.stdout, e.stderr, e.message].filter(Boolean).join(`
52483
+ `).trim()
52484
+ };
52485
+ }
52486
+ });
52338
52487
  async function handleVaultRecentDenialCallback(ctx, data) {
52339
52488
  const senderId = String(ctx.from?.id ?? "");
52340
52489
  const access = loadAccess();
@@ -52375,7 +52524,7 @@ function createCallbackQueryHandlers(deps) {
52375
52524
  const tokenPath = vaultTokenFilePath2(agentName);
52376
52525
  try {
52377
52526
  mkdirSync15(dirname7(tokenPath), { recursive: true });
52378
- writeFileSync10(tokenPath, token, { mode: 384 });
52527
+ writeFileSync11(tokenPath, token, { mode: 384 });
52379
52528
  } catch (err) {
52380
52529
  await switchroomReply(ctx, `**Grant created (${escapeHtmlForTg2(id)}) but token write failed:** ` + `${escapeHtmlForTg2(String(err))}
52381
52530
  ` + `_Recover with: \`switchroom vault grant ${escapeHtmlForTg2(agentName)} ` + `--keys ${escapeHtmlForTg2(keyName)} --duration 30d\` on the host._`, { html: true });
@@ -52527,7 +52676,7 @@ function createCallbackQueryHandlers(deps) {
52527
52676
  const tokenPath = vaultTokenFilePath2(pending.agent);
52528
52677
  try {
52529
52678
  mkdirSync15(dirname7(tokenPath), { recursive: true });
52530
- writeFileSync10(tokenPath, token, { mode: 384 });
52679
+ writeFileSync11(tokenPath, token, { mode: 384 });
52531
52680
  } catch (err) {
52532
52681
  await switchroomReply(ctx, `**Grant created (${escapeHtmlForTg2(id)}) but token write failed:** ` + `${escapeHtmlForTg2(String(err))}
52533
52682
  ` + `_Recover with: \`switchroom vault grant ${escapeHtmlForTg2(pending.agent)} ` + `--keys ${escapeHtmlForTg2(pending.key)} --duration ${Math.round(pending.ttl_seconds / 86400)}d\` on the host._`, { html: true });
@@ -52668,36 +52817,57 @@ _Approver verified by Telegram identity \u2014 broker auto-unlocked at startup._
52668
52817
  }
52669
52818
  return;
52670
52819
  }
52820
+ const cbChatId = String(ctx.chat?.id ?? ctx.from?.id ?? "");
52821
+ const cbThreadId = resolveThreadId(cbChatId, ctx.callbackQuery?.message?.message_thread_id);
52822
+ const inboundCtx = {
52823
+ agent,
52824
+ chat_id: cbChatId,
52825
+ ...cbThreadId != null ? { threadId: cbThreadId } : {}
52826
+ };
52671
52827
  if (parsed.action === "deny") {
52672
52828
  setEvalCaseProposalStatus(stateDir, parsed.id, "rejected");
52829
+ const denied = deliverResumeSyntheticOrBuffer(agent, buildEvalCaseRejectedInbound({
52830
+ ctx: inboundCtx,
52831
+ proposalId: proposal.id,
52832
+ skillSlug: proposal.skill_slug,
52833
+ heldOut: proposal.held_out,
52834
+ operatorId: senderId
52835
+ }));
52673
52836
  await ctx.answerCallbackQuery({ text: "\uD83D\uDEAB Dismissed." }).catch(() => {});
52674
52837
  if (ctx.callbackQuery?.message && "text" in ctx.callbackQuery.message) {
52675
52838
  await ctx.editMessageText(`${escapeHtmlForTg2(ctx.callbackQuery.message.text ?? "")}
52676
52839
 
52677
52840
  \uD83D\uDEAB <i>Dismissed.</i>`, { parse_mode: "HTML", reply_markup: { inline_keyboard: [] } }).catch(() => {});
52678
52841
  }
52842
+ process.stderr.write(`telegram gateway: eval_case_rejected agent=${agent} proposal=${proposal.id} ` + `slug=${proposal.skill_slug} delivered=${denied}
52843
+ `);
52679
52844
  return;
52680
52845
  }
52681
52846
  setEvalCaseProposalStatus(stateDir, parsed.id, "approved");
52682
52847
  await ctx.answerCallbackQuery({ text: "\u2705 Adding the eval case\u2026" }).catch(() => {});
52683
- const cli = process.env.SWITCHROOM_CLI_PATH ?? "switchroom";
52684
- let applyOk = true;
52685
- let applyOut = "";
52686
- try {
52687
- applyOut = execFileSync3(cli, ["self-improve", "apply-eval-case", "--id", parsed.id], { encoding: "utf8", timeout: 15000, env: process.env }).trim();
52688
- } catch (err) {
52689
- applyOk = false;
52690
- const e = err;
52691
- applyOut = [e.stdout, e.stderr, e.message].filter(Boolean).join(`
52692
- `).trim();
52693
- }
52848
+ const { ok: applyOk, out: applyOut } = runEvalCaseApply(parsed.id);
52694
52849
  const footer = applyOk ? "\u2705 <i>Added as a regression test.</i>" : `\u26a0\ufe0f <i>Apply failed:</i> ${escapeHtmlForTg2(applyOut.slice(0, 200))}`;
52695
52850
  if (ctx.callbackQuery?.message && "text" in ctx.callbackQuery.message) {
52696
52851
  await ctx.editMessageText(`${escapeHtmlForTg2(ctx.callbackQuery.message.text ?? "")}
52697
52852
 
52698
52853
  ${footer}`, { parse_mode: "HTML", reply_markup: { inline_keyboard: [] } }).catch(() => {});
52699
52854
  }
52700
- process.stderr.write(`telegram gateway: eval_case_apply agent=${agent} proposal=${proposal.id} ` + `slug=${proposal.skill_slug} ok=${applyOk}
52855
+ const outcomeInbound = applyOk ? buildEvalCaseAppliedInbound({
52856
+ ctx: inboundCtx,
52857
+ proposalId: proposal.id,
52858
+ skillSlug: proposal.skill_slug,
52859
+ heldOut: proposal.held_out,
52860
+ operatorId: senderId
52861
+ }) : buildEvalCaseApplyFailedInbound({
52862
+ ctx: inboundCtx,
52863
+ proposalId: proposal.id,
52864
+ skillSlug: proposal.skill_slug,
52865
+ heldOut: proposal.held_out,
52866
+ operatorId: senderId,
52867
+ applyOut
52868
+ });
52869
+ const delivered = deliverResumeSyntheticOrBuffer(agent, outcomeInbound);
52870
+ process.stderr.write(`telegram gateway: eval_case_apply agent=${agent} proposal=${proposal.id} ` + `slug=${proposal.skill_slug} ok=${applyOk} delivered=${delivered}
52701
52871
  `);
52702
52872
  }
52703
52873
  async function handleMentalModelProposeCallback(ctx, data) {
@@ -53421,7 +53591,7 @@ ${keyList}`,
53421
53591
  const tokenPath = vaultTokenFilePath2(state.agent);
53422
53592
  try {
53423
53593
  mkdirSync15(dirname7(tokenPath), { recursive: true });
53424
- writeFileSync10(tokenPath, token, { mode: 384 });
53594
+ writeFileSync11(tokenPath, token, { mode: 384 });
53425
53595
  } catch (err) {
53426
53596
  await switchroomReply(ctx, `**Grant created but token write failed:** ${escapeHtmlForTg2(String(err))}`, { html: true });
53427
53597
  return;
@@ -54480,7 +54650,7 @@ function hangStalenessMs(env = process.env) {
54480
54650
 
54481
54651
  // gateway/missed-approvals-store.ts
54482
54652
  init_state_owner();
54483
- import { unlinkSync as unlinkSync6 } from "node:fs";
54653
+ import { unlinkSync as unlinkSync7 } from "node:fs";
54484
54654
  import { join as join17 } from "node:path";
54485
54655
  var MAX_PENDING = 50;
54486
54656
  var MAX_DELIVERED = 20;
@@ -54565,7 +54735,7 @@ function createMissedApprovalsStore(stateDir, log = (l) => process.stderr.write(
54565
54735
  },
54566
54736
  clear() {
54567
54737
  try {
54568
- unlinkSync6(filePath);
54738
+ unlinkSync7(filePath);
54569
54739
  } catch {}
54570
54740
  }
54571
54741
  };
@@ -54573,7 +54743,7 @@ function createMissedApprovalsStore(stateDir, log = (l) => process.stderr.write(
54573
54743
 
54574
54744
  // gateway/always-allow-persist-queue.ts
54575
54745
  init_state_owner();
54576
- import { unlinkSync as unlinkSync7 } from "node:fs";
54746
+ import { unlinkSync as unlinkSync8 } from "node:fs";
54577
54747
  import { join as join18 } from "node:path";
54578
54748
  var MAX_ATTEMPTS = 5;
54579
54749
  var MAX_AGE_MS = 24 * 60 * 60 * 1000;
@@ -54710,7 +54880,7 @@ function createAlwaysAllowPersistQueue(stateDir, writeFileSyncFn = atomicWriteSe
54710
54880
  },
54711
54881
  clear() {
54712
54882
  try {
54713
- unlinkSync7(filePath);
54883
+ unlinkSync8(filePath);
54714
54884
  } catch {}
54715
54885
  }
54716
54886
  };
@@ -54873,7 +55043,7 @@ function pickRecoveredPermissionOrigin(recentTurns, now, maxAgeMs) {
54873
55043
  }
54874
55044
 
54875
55045
  // gateway/approval-hold.ts
54876
- import { mkdirSync as mkdirSync16, writeFileSync as writeFileSync12, readFileSync as readFileSync14, unlinkSync as unlinkSync8, chmodSync as chmodSync3, lstatSync as lstatSync2 } from "node:fs";
55046
+ import { mkdirSync as mkdirSync16, writeFileSync as writeFileSync13, readFileSync as readFileSync14, unlinkSync as unlinkSync9, chmodSync as chmodSync3, lstatSync as lstatSync2 } from "node:fs";
54877
55047
  import { join as join19, dirname as dirname8 } from "node:path";
54878
55048
  var BLOCKED_APPROVAL_FILE_MODE = 420;
54879
55049
  var BLOCKED_APPROVAL_DIR_MODE = 1023;
@@ -54902,7 +55072,8 @@ var ACTION_SAFE_WITH_INPUT2 = new Set([
54902
55072
  function safeActionForRecord(naturalAction2, toolName, inputPreview) {
54903
55073
  return ACTION_SAFE_WITH_INPUT2.has(toolName) ? naturalAction2(toolName, inputPreview) : naturalAction2(toolName, undefined);
54904
55074
  }
54905
- function createBlockedApprovalStore(dir, agent, fallbackDir) {
55075
+ function createBlockedApprovalStore(dir, agent, fallbackDir, deps = {}) {
55076
+ const write_ = deps.writeFileSync ?? writeFileSync13;
54906
55077
  const primary = join19(dir, `${agent}.json`);
54907
55078
  const fallback = fallbackDir != null ? join19(fallbackDir, "blocked-approval.json") : null;
54908
55079
  let active = primary;
@@ -54919,7 +55090,7 @@ function createBlockedApprovalStore(dir, agent, fallbackDir) {
54919
55090
  chmodSync3(parent, dirMode);
54920
55091
  } catch {}
54921
55092
  }
54922
- writeFileSync12(target, body, { encoding: "utf-8", mode: BLOCKED_APPROVAL_FILE_MODE });
55093
+ write_(target, body, { encoding: "utf-8", mode: BLOCKED_APPROVAL_FILE_MODE });
54923
55094
  chmodSync3(target, BLOCKED_APPROVAL_FILE_MODE);
54924
55095
  return true;
54925
55096
  } catch {
@@ -54937,7 +55108,7 @@ function createBlockedApprovalStore(dir, agent, fallbackDir) {
54937
55108
  active = primary;
54938
55109
  if (fallback != null) {
54939
55110
  try {
54940
- unlinkSync8(fallback);
55111
+ unlinkSync9(fallback);
54941
55112
  } catch {}
54942
55113
  }
54943
55114
  return;
@@ -54956,7 +55127,7 @@ function createBlockedApprovalStore(dir, agent, fallbackDir) {
54956
55127
  if (p == null)
54957
55128
  continue;
54958
55129
  try {
54959
- unlinkSync8(p);
55130
+ unlinkSync9(p);
54960
55131
  } catch {}
54961
55132
  }
54962
55133
  active = primary;
@@ -67336,11 +67507,11 @@ init_flood_429_ledger();
67336
67507
  import {
67337
67508
  existsSync as existsSync15,
67338
67509
  readFileSync as readFileSync17,
67339
- writeFileSync as writeFileSync15,
67510
+ writeFileSync as writeFileSync16,
67340
67511
  mkdirSync as mkdirSync19,
67341
67512
  chmodSync as chmodSync6,
67342
- unlinkSync as unlinkSync11,
67343
- renameSync as renameSync11
67513
+ unlinkSync as unlinkSync12,
67514
+ renameSync as renameSync12
67344
67515
  } from "node:fs";
67345
67516
  import { dirname as dirname11, join as join22 } from "node:path";
67346
67517
  var FLOOD_STATE_FILE2 = "flood-wait.json";
@@ -67398,7 +67569,7 @@ function writeFloodState2(path2, state, log = (l) => process.stderr.write(l)) {
67398
67569
  mkdirSync19(dirname11(path2), { recursive: true });
67399
67570
  } catch {}
67400
67571
  try {
67401
- writeFileSync15(path2, payload, { mode: FLOOD_STATE_MODE2 });
67572
+ writeFileSync16(path2, payload, { mode: FLOOD_STATE_MODE2 });
67402
67573
  try {
67403
67574
  chmodSync6(path2, FLOOD_STATE_MODE2);
67404
67575
  } catch {}
@@ -67411,8 +67582,8 @@ function writeFloodState2(path2, state, log = (l) => process.stderr.write(l)) {
67411
67582
  return;
67412
67583
  }
67413
67584
  try {
67414
- unlinkSync11(path2);
67415
- writeFileSync15(path2, payload, { mode: FLOOD_STATE_MODE2 });
67585
+ unlinkSync12(path2);
67586
+ writeFileSync16(path2, payload, { mode: FLOOD_STATE_MODE2 });
67416
67587
  log(`telegram gateway: flood-breaker: recreated ${path2} \u2014 it was owned by another uid ` + `and could not be updated (${code2}); the breaker was blind (issue #3106)
67417
67588
  `);
67418
67589
  } catch (err2) {
@@ -67546,8 +67717,8 @@ function writeFloodWindow(path2, record, now) {
67546
67717
  }
67547
67718
  const arr = [...byScope.values()];
67548
67719
  const tmp = `${path2}.tmp-${process.pid}`;
67549
- writeFileSync15(tmp, JSON.stringify(arr), { mode: FLOOD_STATE_MODE2 });
67550
- renameSync11(tmp, path2);
67720
+ writeFileSync16(tmp, JSON.stringify(arr), { mode: FLOOD_STATE_MODE2 });
67721
+ renameSync12(tmp, path2);
67551
67722
  try {
67552
67723
  chmodSync6(path2, FLOOD_STATE_MODE2);
67553
67724
  } catch {}
@@ -72144,7 +72315,7 @@ class PostHog extends PostHogBackendClient {
72144
72315
  }
72145
72316
 
72146
72317
  // analytics-posthog.ts
72147
- import { existsSync as existsSync16, mkdirSync as mkdirSync20, readFileSync as readFileSync18, writeFileSync as writeFileSync16 } from "node:fs";
72318
+ import { existsSync as existsSync16, mkdirSync as mkdirSync20, readFileSync as readFileSync18, writeFileSync as writeFileSync17 } from "node:fs";
72148
72319
  import { dirname as dirname13, join as join24 } from "node:path";
72149
72320
  import { randomUUID as randomUUID5 } from "node:crypto";
72150
72321
  var DEFAULT_KEY = "phc_qKY87cKWZm6ZyCtk7LcRd2cU8Sg42u7Ywhui5stYCegd";
@@ -72185,7 +72356,7 @@ function getDistinctId() {
72185
72356
  cachedDistinctId = id;
72186
72357
  try {
72187
72358
  mkdirSync20(dirname13(fallbackPath), { recursive: true });
72188
- writeFileSync16(fallbackPath, id, "utf-8");
72359
+ writeFileSync17(fallbackPath, id, "utf-8");
72189
72360
  } catch {}
72190
72361
  return id;
72191
72362
  }
@@ -72260,7 +72431,7 @@ import { mkdirSync as mkdirSync22, appendFileSync as appendFileSync4 } from "nod
72260
72431
  import { dirname as dirname15, join as join26 } from "node:path";
72261
72432
 
72262
72433
  // analytics-posthog.ts
72263
- import { existsSync as existsSync17, mkdirSync as mkdirSync21, readFileSync as readFileSync19, writeFileSync as writeFileSync17 } from "node:fs";
72434
+ import { existsSync as existsSync17, mkdirSync as mkdirSync21, readFileSync as readFileSync19, writeFileSync as writeFileSync18 } from "node:fs";
72264
72435
  import { dirname as dirname14, join as join25 } from "node:path";
72265
72436
  import { randomUUID as randomUUID6 } from "node:crypto";
72266
72437
  var DEFAULT_KEY2 = "phc_qKY87cKWZm6ZyCtk7LcRd2cU8Sg42u7Ywhui5stYCegd";
@@ -72300,7 +72471,7 @@ function getDistinctId2() {
72300
72471
  cachedDistinctId2 = id;
72301
72472
  try {
72302
72473
  mkdirSync21(dirname14(fallbackPath), { recursive: true });
72303
- writeFileSync17(fallbackPath, id, "utf-8");
72474
+ writeFileSync18(fallbackPath, id, "utf-8");
72304
72475
  } catch {}
72305
72476
  return id;
72306
72477
  }
@@ -73036,7 +73207,7 @@ function __resetAllForTests3() {
73036
73207
  }
73037
73208
 
73038
73209
  // silent-end.ts
73039
- import { existsSync as existsSync19, readFileSync as readFileSync20, writeFileSync as writeFileSync18, unlinkSync as unlinkSync12, mkdirSync as mkdirSync23 } from "node:fs";
73210
+ import { existsSync as existsSync19, readFileSync as readFileSync20, writeFileSync as writeFileSync19, unlinkSync as unlinkSync13, mkdirSync as mkdirSync23 } from "node:fs";
73040
73211
  import { dirname as dirname16, join as join27 } from "node:path";
73041
73212
  import { homedir as homedir5 } from "node:os";
73042
73213
  var SILENT_END_MAX_RETRIES = 2;
@@ -73081,7 +73252,7 @@ function writeSilentEndState(args, deps) {
73081
73252
  };
73082
73253
  try {
73083
73254
  mkdirSync23(dirname16(statePath), { recursive: true });
73084
- writeFileSync18(statePath, JSON.stringify(state3), "utf8");
73255
+ writeFileSync19(statePath, JSON.stringify(state3), "utf8");
73085
73256
  emitLog(deps, `silent-end: wrote state file turnKey=${args.turnKey} retryCount=${retryCount}
73086
73257
  `);
73087
73258
  } catch (err) {
@@ -73097,7 +73268,7 @@ function clearSilentEndState(turnKey3, deps) {
73097
73268
  const prev = JSON.parse(readFileSync20(statePath, "utf8"));
73098
73269
  if (prev.turnKey != null && prev.turnKey !== turnKey3)
73099
73270
  return;
73100
- unlinkSync12(statePath);
73271
+ unlinkSync13(statePath);
73101
73272
  emitLog(deps, `silent-end: cleared state file turnKey=${turnKey3}
73102
73273
  `);
73103
73274
  } catch {}
@@ -74028,7 +74199,7 @@ async function gatewayStartupRetry(fn, opts = {}) {
74028
74199
  }
74029
74200
 
74030
74201
  // gateway/quarantine.ts
74031
- import { mkdirSync as mkdirSync24, writeFileSync as writeFileSync19 } from "node:fs";
74202
+ import { mkdirSync as mkdirSync24, writeFileSync as writeFileSync20 } from "node:fs";
74032
74203
  import { join as join30 } from "node:path";
74033
74204
  var QUARANTINE_FILENAME = "quarantine.json";
74034
74205
  function writeQuarantineMarker(telegramStateDir, reason, detail, nowFn = Date.now) {
@@ -74039,7 +74210,7 @@ function writeQuarantineMarker(telegramStateDir, reason, detail, nowFn = Date.no
74039
74210
  ts: nowFn(),
74040
74211
  detail
74041
74212
  };
74042
- writeFileSync19(join30(telegramStateDir, QUARANTINE_FILENAME), JSON.stringify(marker) + `
74213
+ writeFileSync20(join30(telegramStateDir, QUARANTINE_FILENAME), JSON.stringify(marker) + `
74043
74214
  `, "utf-8");
74044
74215
  }
74045
74216
 
@@ -75512,7 +75683,7 @@ async function materializeSidecarToken2(opts = {}, logger2 = (line) => process.s
75512
75683
  }
75513
75684
 
75514
75685
  // ../src/setup/host-capabilities.ts
75515
- import { mkdirSync as mkdirSync25, readFileSync as readFileSync22, writeFileSync as writeFileSync20 } from "node:fs";
75686
+ import { mkdirSync as mkdirSync25, readFileSync as readFileSync22, writeFileSync as writeFileSync21 } from "node:fs";
75516
75687
  init_paths();
75517
75688
  function hostCapabilitiesPath() {
75518
75689
  return resolveStatePath("host-capabilities.json");
@@ -75676,7 +75847,7 @@ function resolveExhaustUntil(resetAtMs, now = Date.now()) {
75676
75847
 
75677
75848
  // gateway/auth-add-flow.ts
75678
75849
  import { execFileSync as execFileSync4 } from "node:child_process";
75679
- import { existsSync as existsSync24, mkdirSync as mkdirSync27, readFileSync as readFileSync24, readdirSync as readdirSync7, rmSync as rmSync6, statSync as statSync10, writeFileSync as writeFileSync22 } from "node:fs";
75850
+ import { existsSync as existsSync24, mkdirSync as mkdirSync27, readFileSync as readFileSync24, readdirSync as readdirSync7, rmSync as rmSync6, statSync as statSync10, writeFileSync as writeFileSync23 } from "node:fs";
75680
75851
  import { homedir as homedir8 } from "node:os";
75681
75852
  import { join as join32 } from "node:path";
75682
75853
  import { randomBytes as randomBytes8 } from "node:crypto";
@@ -75686,7 +75857,7 @@ import {
75686
75857
  readFileSync as readFileSync23,
75687
75858
  readdirSync as readdirSync6,
75688
75859
  existsSync as existsSync23,
75689
- writeFileSync as writeFileSync21,
75860
+ writeFileSync as writeFileSync22,
75690
75861
  mkdirSync as mkdirSync26,
75691
75862
  mkdtempSync as mkdtempSync2,
75692
75863
  rmSync as rmSync5,
@@ -75847,7 +76018,7 @@ async function startAccountAuthSession(label, opts = {}) {
75847
76018
  const tmuxSocket = `switchroom-${agentName3}`;
75848
76019
  const tmuxSession = `auth-add-${label}-${hexSuffix}`.slice(0, 64);
75849
76020
  try {
75850
- writeFileSync22(join32(scratchDir, AUTH_TMUX_SESSION_FILE), `${tmuxSocket}
76021
+ writeFileSync23(join32(scratchDir, AUTH_TMUX_SESSION_FILE), `${tmuxSocket}
75851
76022
  ${tmuxSession}`, "utf8");
75852
76023
  } catch {}
75853
76024
  const sessionEnv = {
@@ -78836,7 +79007,7 @@ function startRestartWatchdog(config) {
78836
79007
  }
78837
79008
 
78838
79009
  // gateway/access-store.ts
78839
- import { mkdirSync as mkdirSync28, readFileSync as readFileSync25, renameSync as renameSync12, writeFileSync as writeFileSync23 } from "node:fs";
79010
+ import { mkdirSync as mkdirSync28, readFileSync as readFileSync25, renameSync as renameSync13, writeFileSync as writeFileSync24 } from "node:fs";
78840
79011
 
78841
79012
  // gateway/access-validator.ts
78842
79013
  function validateStringArray(field, value, tag = "gateway") {
@@ -78900,7 +79071,7 @@ function createAccessStore(deps) {
78900
79071
  if (err.code === "ENOENT")
78901
79072
  return defaultAccess();
78902
79073
  try {
78903
- renameSync12(accessFile, `${accessFile}.corrupt-${Date.now()}`);
79074
+ renameSync13(accessFile, `${accessFile}.corrupt-${Date.now()}`);
78904
79075
  } catch {}
78905
79076
  process.stderr.write(`telegram gateway: access.json is corrupt, moved aside. Starting fresh.
78906
79077
  `);
@@ -78945,9 +79116,9 @@ function createAccessStore(deps) {
78945
79116
  return;
78946
79117
  mkdirSync28(stateDir, { recursive: true, mode: 448 });
78947
79118
  const tmp = accessFile + ".tmp";
78948
- writeFileSync23(tmp, JSON.stringify(a, null, 2) + `
79119
+ writeFileSync24(tmp, JSON.stringify(a, null, 2) + `
78949
79120
  `, { mode: 384 });
78950
- renameSync12(tmp, accessFile);
79121
+ renameSync13(tmp, accessFile);
78951
79122
  }
78952
79123
  function pruneExpired(a) {
78953
79124
  const now = Date.now();
@@ -79784,10 +79955,10 @@ import {
79784
79955
  mkdirSync as mkdirSync29,
79785
79956
  readFileSync as readFileSync26,
79786
79957
  readdirSync as readdirSync8,
79787
- renameSync as renameSync13,
79958
+ renameSync as renameSync14,
79788
79959
  statSync as statSync11,
79789
- unlinkSync as unlinkSync13,
79790
- writeFileSync as writeFileSync24,
79960
+ unlinkSync as unlinkSync14,
79961
+ writeFileSync as writeFileSync25,
79791
79962
  appendFileSync as appendFileSync5
79792
79963
  } from "node:fs";
79793
79964
  import { join as join34 } from "node:path";
@@ -79891,8 +80062,8 @@ function writeOutboxRecordAtomic(record, stateDir) {
79891
80062
  if (existsSync26(finalPath))
79892
80063
  return true;
79893
80064
  const tmpPath = join34(dir, `.${record.turnNonce}.${process.pid}.tmp`);
79894
- writeFileSync24(tmpPath, JSON.stringify(record), { mode: 384 });
79895
- renameSync13(tmpPath, finalPath);
80065
+ writeFileSync25(tmpPath, JSON.stringify(record), { mode: 384 });
80066
+ renameSync14(tmpPath, finalPath);
79896
80067
  return true;
79897
80068
  } catch {
79898
80069
  return false;
@@ -79919,7 +80090,7 @@ function claimRecord(nonce, stateDir) {
79919
80090
  const from = join34(dir, `${nonce}.json`);
79920
80091
  const to = join34(dir, `${nonce}.sending`);
79921
80092
  try {
79922
- renameSync13(from, to);
80093
+ renameSync14(from, to);
79923
80094
  return to;
79924
80095
  } catch {
79925
80096
  return null;
@@ -79928,12 +80099,12 @@ function claimRecord(nonce, stateDir) {
79928
80099
  function releaseClaim(nonce, stateDir) {
79929
80100
  const dir = resolveOutboxDir(stateDir);
79930
80101
  try {
79931
- renameSync13(join34(dir, `${nonce}.sending`), join34(dir, `${nonce}.json`));
80102
+ renameSync14(join34(dir, `${nonce}.sending`), join34(dir, `${nonce}.json`));
79932
80103
  } catch {}
79933
80104
  }
79934
80105
  function removeClaimed(nonce, stateDir) {
79935
80106
  try {
79936
- unlinkSync13(join34(resolveOutboxDir(stateDir), `${nonce}.sending`));
80107
+ unlinkSync14(join34(resolveOutboxDir(stateDir), `${nonce}.sending`));
79937
80108
  } catch {}
79938
80109
  }
79939
80110
  function reclaimStaleSending(stateDir, now = Date.now()) {
@@ -79948,7 +80119,7 @@ function reclaimStaleSending(stateDir, now = Date.now()) {
79948
80119
  try {
79949
80120
  const st = statSync11(join34(dir, f));
79950
80121
  if (now - st.mtimeMs > OUTBOX_SENDING_TIMEOUT_MS) {
79951
- renameSync13(join34(dir, f), join34(dir, f.replace(/\.sending$/, ".json")));
80122
+ renameSync14(join34(dir, f), join34(dir, f.replace(/\.sending$/, ".json")));
79952
80123
  }
79953
80124
  } catch {}
79954
80125
  }
@@ -80019,17 +80190,17 @@ function compactJournalIfLarge(path2) {
80019
80190
  return;
80020
80191
  const kept = lines.slice(lines.length - JOURNAL_KEEP);
80021
80192
  const tmp = `${path2}.${process.pid}.compact`;
80022
- writeFileSync24(tmp, kept.join(`
80193
+ writeFileSync25(tmp, kept.join(`
80023
80194
  `) + `
80024
80195
  `, { mode: 384 });
80025
- renameSync13(tmp, path2);
80196
+ renameSync14(tmp, path2);
80026
80197
  } catch {}
80027
80198
  }
80028
80199
  function clearOutboxRecord(nonce, stateDir) {
80029
80200
  const dir = resolveOutboxDir(stateDir);
80030
80201
  for (const suffix of [".json", ".sending"]) {
80031
80202
  try {
80032
- unlinkSync13(join34(dir, `${nonce}${suffix}`));
80203
+ unlinkSync14(join34(dir, `${nonce}${suffix}`));
80033
80204
  } catch {}
80034
80205
  }
80035
80206
  }
@@ -80097,8 +80268,8 @@ import {
80097
80268
  existsSync as existsSync27,
80098
80269
  mkdirSync as mkdirSync30,
80099
80270
  readFileSync as readFileSync27,
80100
- renameSync as renameSync14,
80101
- writeFileSync as writeFileSync25,
80271
+ renameSync as renameSync15,
80272
+ writeFileSync as writeFileSync26,
80102
80273
  appendFileSync as appendFileSync6
80103
80274
  } from "node:fs";
80104
80275
  import { join as join35 } from "node:path";
@@ -80227,10 +80398,10 @@ function compactIfLarge(path2) {
80227
80398
  return;
80228
80399
  const kept = lines.slice(lines.length - SHOWN_LEDGER_KEEP);
80229
80400
  const tmp = `${path2}.${process.pid}.compact`;
80230
- writeFileSync25(tmp, kept.join(`
80401
+ writeFileSync26(tmp, kept.join(`
80231
80402
  `) + `
80232
80403
  `, { mode: 384 });
80233
- renameSync14(tmp, path2);
80404
+ renameSync15(tmp, path2);
80234
80405
  } catch {}
80235
80406
  }
80236
80407
 
@@ -80750,6 +80921,10 @@ var INBOUND_SOURCE_CLASSIFICATION = {
80750
80921
  mental_model_proposal_failed: { decoupledCompletion: false },
80751
80922
  webhook: { decoupledCompletion: false },
80752
80923
  linear: { decoupledCompletion: false },
80924
+ eval_case_applied: { decoupledCompletion: false },
80925
+ eval_case_rejected: { decoupledCompletion: false },
80926
+ eval_case_apply_failed: { decoupledCompletion: false },
80927
+ eval_case_suppressed: { decoupledCompletion: false },
80753
80928
  buzz: { decoupledCompletion: false },
80754
80929
  boot_briefing: { decoupledCompletion: false }
80755
80930
  };
@@ -80988,7 +81163,7 @@ function monotonicNowMs2() {
80988
81163
  }
80989
81164
 
80990
81165
  // silent-end.ts
80991
- import { existsSync as existsSync28, readFileSync as readFileSync28, writeFileSync as writeFileSync26, unlinkSync as unlinkSync14, mkdirSync as mkdirSync31 } from "node:fs";
81166
+ import { existsSync as existsSync28, readFileSync as readFileSync28, writeFileSync as writeFileSync27, unlinkSync as unlinkSync15, mkdirSync as mkdirSync31 } from "node:fs";
80992
81167
  import { dirname as dirname17, join as join36 } from "node:path";
80993
81168
  import { homedir as homedir10 } from "node:os";
80994
81169
  var SILENT_END_MAX_RETRIES2 = 2;
@@ -81037,7 +81212,7 @@ function writeSilentEndState2(args, deps) {
81037
81212
  };
81038
81213
  try {
81039
81214
  mkdirSync31(dirname17(statePath), { recursive: true });
81040
- writeFileSync26(statePath, JSON.stringify(state3), "utf8");
81215
+ writeFileSync27(statePath, JSON.stringify(state3), "utf8");
81041
81216
  emitLog2(deps, `silent-end: wrote state file turnKey=${args.turnKey} retryCount=${retryCount}
81042
81217
  `);
81043
81218
  } catch (err) {
@@ -81053,7 +81228,7 @@ function clearSilentEndState2(turnKey3, deps) {
81053
81228
  const prev = JSON.parse(readFileSync28(statePath, "utf8"));
81054
81229
  if (prev.turnKey != null && prev.turnKey !== turnKey3)
81055
81230
  return;
81056
- unlinkSync14(statePath);
81231
+ unlinkSync15(statePath);
81057
81232
  emitLog2(deps, `silent-end: cleared state file turnKey=${turnKey3}
81058
81233
  `);
81059
81234
  } catch {}
@@ -83416,7 +83591,7 @@ import {
83416
83591
  openSync as openSync10,
83417
83592
  readFileSync as readFileSync29,
83418
83593
  statSync as statSync13,
83419
- unlinkSync as unlinkSync15,
83594
+ unlinkSync as unlinkSync16,
83420
83595
  utimesSync
83421
83596
  } from "node:fs";
83422
83597
  import { join as join39 } from "node:path";
@@ -83445,7 +83620,7 @@ function touchTurnActiveMarker(stateDir) {
83445
83620
  }
83446
83621
  function removeTurnActiveMarker(stateDir) {
83447
83622
  try {
83448
- unlinkSync15(join39(stateDir, TURN_ACTIVE_MARKER_FILE));
83623
+ unlinkSync16(join39(stateDir, TURN_ACTIVE_MARKER_FILE));
83449
83624
  } catch {}
83450
83625
  }
83451
83626
  function readTurnActiveMarkerAgeMs(stateDir, now) {
@@ -85770,7 +85945,7 @@ function resolveAgentDirFromEnv() {
85770
85945
  }
85771
85946
 
85772
85947
  // active-reactions.ts
85773
- import { readFileSync as readFileSync30, writeFileSync as writeFileSync27, renameSync as renameSync15, existsSync as existsSync32, unlinkSync as unlinkSync16 } from "node:fs";
85948
+ import { readFileSync as readFileSync30, writeFileSync as writeFileSync28, renameSync as renameSync16, existsSync as existsSync32, unlinkSync as unlinkSync17 } from "node:fs";
85774
85949
  import { join as join40 } from "node:path";
85775
85950
  var ACTIVE_REACTIONS_FILENAME = ".active-reactions.json";
85776
85951
  function reactionsPath(agentDir) {
@@ -85808,15 +85983,15 @@ function writeActiveReactions(agentDir, reactions) {
85808
85983
  const p = reactionsPath(agentDir);
85809
85984
  if (reactions.length === 0) {
85810
85985
  try {
85811
- unlinkSync16(p);
85986
+ unlinkSync17(p);
85812
85987
  } catch {}
85813
85988
  return;
85814
85989
  }
85815
85990
  const tmp = `${p}.tmp-${process.pid}-${Date.now()}`;
85816
85991
  try {
85817
- writeFileSync27(tmp, JSON.stringify(reactions) + `
85992
+ writeFileSync28(tmp, JSON.stringify(reactions) + `
85818
85993
  `, "utf-8");
85819
- renameSync15(tmp, p);
85994
+ renameSync16(tmp, p);
85820
85995
  } catch {}
85821
85996
  }
85822
85997
  function addActiveReaction(agentDir, reaction) {
@@ -85833,12 +86008,12 @@ function removeActiveReaction(agentDir, chatId, messageId) {
85833
86008
  }
85834
86009
  function clearActiveReactions(agentDir) {
85835
86010
  try {
85836
- unlinkSync16(reactionsPath(agentDir));
86011
+ unlinkSync17(reactionsPath(agentDir));
85837
86012
  } catch {}
85838
86013
  }
85839
86014
 
85840
86015
  // active-reactions.ts
85841
- import { readFileSync as readFileSync31, writeFileSync as writeFileSync28, renameSync as renameSync16, existsSync as existsSync33, unlinkSync as unlinkSync17 } from "node:fs";
86016
+ import { readFileSync as readFileSync31, writeFileSync as writeFileSync29, renameSync as renameSync17, existsSync as existsSync33, unlinkSync as unlinkSync18 } from "node:fs";
85842
86017
  import { join as join41 } from "node:path";
85843
86018
  var ACTIVE_REACTIONS_FILENAME2 = ".active-reactions.json";
85844
86019
  function reactionsPath2(agentDir) {
@@ -85874,7 +86049,7 @@ function readActiveReactions2(agentDir) {
85874
86049
  }
85875
86050
  function clearActiveReactions2(agentDir) {
85876
86051
  try {
85877
- unlinkSync17(reactionsPath2(agentDir));
86052
+ unlinkSync18(reactionsPath2(agentDir));
85878
86053
  } catch {}
85879
86054
  }
85880
86055
 
@@ -88116,15 +88291,15 @@ async function menuWithBannerStatic(deps, banner) {
88116
88291
  }
88117
88292
 
88118
88293
  // gateway/session-model-file.ts
88119
- import { readFileSync as readFileSync33, writeFileSync as writeFileSync29, renameSync as renameSync17, rmSync as rmSync7 } from "node:fs";
88294
+ import { readFileSync as readFileSync33, writeFileSync as writeFileSync30, renameSync as renameSync18, rmSync as rmSync7 } from "node:fs";
88120
88295
  import { join as join43 } from "node:path";
88121
88296
  var SESSION_MODEL_FILE = ".session-model";
88122
88297
  var CONFIGURED_DEFAULT_MODEL_FILE = ".configured-default-model";
88123
88298
  var SESSION_MODEL_BOOT_ATTEMPTS_FILE = ".session-model-boot-attempts";
88124
88299
  function atomicWrite(path2, content3) {
88125
88300
  const tmp = `${path2}.tmp-${process.pid}-${Date.now()}`;
88126
- writeFileSync29(tmp, content3, "utf8");
88127
- renameSync17(tmp, path2);
88301
+ writeFileSync30(tmp, content3, "utf8");
88302
+ renameSync18(tmp, path2);
88128
88303
  }
88129
88304
  function serializeSessionModel(rec) {
88130
88305
  return `${JSON.stringify({
@@ -88652,7 +88827,7 @@ var AUDIT_ROOT = join45(homedir11(), ".switchroom", "audit");
88652
88827
 
88653
88828
  // ../src/agents/profiles.ts
88654
88829
  var import_handlebars = __toESM(require_lib(), 1);
88655
- import { readFileSync as readFileSync36, writeFileSync as writeFileSync31, existsSync as existsSync37, readdirSync as readdirSync9, statSync as statSync14, copyFileSync, mkdirSync as mkdirSync33, realpathSync as realpathSync4 } from "node:fs";
88830
+ import { readFileSync as readFileSync36, writeFileSync as writeFileSync32, existsSync as existsSync37, readdirSync as readdirSync9, statSync as statSync14, copyFileSync, mkdirSync as mkdirSync33, realpathSync as realpathSync4 } from "node:fs";
88656
88831
  import { resolve as resolve8, join as join46, sep as pathSep } from "node:path";
88657
88832
  function resolveProfilesRootDetailed() {
88658
88833
  return resolveShippedAsset(PROFILES_ASSET, {
@@ -88670,7 +88845,7 @@ import_handlebars.default.registerHelper("isNumber", (value) => {
88670
88845
  return typeof value === "number" && Number.isFinite(value);
88671
88846
  });
88672
88847
  var SHARED_FRAGMENTS_DIR = resolve8(PROFILES_ROOT, "_shared");
88673
- var SHARED_FRAGMENTS = ["vault-protocol", "agent-self-service", "execution-discipline", "reply-discipline", "dev-protocol"];
88848
+ var SHARED_FRAGMENTS = ["vault-protocol", "agent-self-service", "execution-discipline", "reply-discipline", "dev-protocol", "local-time"];
88674
88849
  for (const name of SHARED_FRAGMENTS) {
88675
88850
  const fragPath = join46(SHARED_FRAGMENTS_DIR, `${name}.md.hbs`);
88676
88851
  if (existsSync37(fragPath)) {
@@ -90623,7 +90798,7 @@ function numField(obj, key) {
90623
90798
  }
90624
90799
 
90625
90800
  // gateway/context-occupancy.ts
90626
- import { mkdirSync as mkdirSync34, writeFileSync as writeFileSync32 } from "node:fs";
90801
+ import { mkdirSync as mkdirSync34, writeFileSync as writeFileSync33 } from "node:fs";
90627
90802
  import { join as join47 } from "node:path";
90628
90803
  var CONTEXT_OCCUPANCY_FILENAME = "context-occupancy.json";
90629
90804
  var TIGHT_FRACTION = 0.8;
@@ -90649,7 +90824,7 @@ function writeContextOccupancySnapshot(stateDir, snapshot, deps) {
90649
90824
  try {
90650
90825
  const path2 = join47(stateDir, CONTEXT_OCCUPANCY_FILENAME);
90651
90826
  (deps?.mkdir ?? ((p, o) => mkdirSync34(p, o)))(stateDir, { recursive: true });
90652
- (deps?.writeFile ?? ((p, d) => writeFileSync32(p, d)))(path2, JSON.stringify(snapshot, null, 2) + `
90827
+ (deps?.writeFile ?? ((p, d) => writeFileSync33(p, d)))(path2, JSON.stringify(snapshot, null, 2) + `
90653
90828
  `);
90654
90829
  } catch {}
90655
90830
  }
@@ -92031,7 +92206,7 @@ import {
92031
92206
  openSync as openSync11,
92032
92207
  readdirSync as readdirSync10,
92033
92208
  statSync as statSync15,
92034
- writeFileSync as writeFileSync33
92209
+ writeFileSync as writeFileSync34
92035
92210
  } from "node:fs";
92036
92211
  import { dirname as dirname21, join as join51, relative as relative2, sep as sep5 } from "node:path";
92037
92212
  var NOFOLLOW2 = constants5.O_NOFOLLOW ?? 0;
@@ -92114,7 +92289,7 @@ function mkdirStateSync2(path2, options = { recursive: true }, log = defaultLog3
92114
92289
  function writeStateFileSync2(path2, data, options, log = defaultLog3) {
92115
92290
  const owner = resolveStateOwner2(dirname21(path2));
92116
92291
  if (!owner) {
92117
- writeFileSync33(path2, data, options);
92292
+ writeFileSync34(path2, data, options);
92118
92293
  return;
92119
92294
  }
92120
92295
  const mode = options?.mode ?? 438;
@@ -92122,7 +92297,7 @@ function writeStateFileSync2(path2, data, options, log = defaultLog3) {
92122
92297
  let fd = null;
92123
92298
  try {
92124
92299
  fd = openSync11(path2, constants5.O_WRONLY | constants5.O_CREAT | constants5.O_TRUNC | NOFOLLOW2, mode);
92125
- writeFileSync33(fd, buf);
92300
+ writeFileSync34(fd, buf);
92126
92301
  try {
92127
92302
  fchownSync4(fd, owner.uid, owner.gid);
92128
92303
  } catch (err) {
@@ -92136,7 +92311,7 @@ function writeStateFileSync2(path2, data, options, log = defaultLog3) {
92136
92311
  fd = null;
92137
92312
  }
92138
92313
  noteFailure2("write", path2, err, log);
92139
- writeFileSync33(path2, data, options);
92314
+ writeFileSync34(path2, data, options);
92140
92315
  return;
92141
92316
  } finally {
92142
92317
  if (fd !== null) {
@@ -92261,7 +92436,7 @@ function adoptAndReport(path2, owner, log) {
92261
92436
 
92262
92437
  // gateway/webhook-ingest-server.ts
92263
92438
  import net4 from "node:net";
92264
- import { chmodSync as chmodSync10, existsSync as existsSync43, unlinkSync as unlinkSync18 } from "node:fs";
92439
+ import { chmodSync as chmodSync10, existsSync as existsSync43, unlinkSync as unlinkSync19 } from "node:fs";
92265
92440
  var MAX_REQUEST_BYTES = 1024 * 1024;
92266
92441
  function fdOf(conn) {
92267
92442
  const handle = conn._handle;
@@ -92274,7 +92449,7 @@ function startWebhookIngestServer(opts) {
92274
92449
  const allowed = new Set(opts.allowedUids);
92275
92450
  try {
92276
92451
  if (existsSync43(opts.socketPath))
92277
- unlinkSync18(opts.socketPath);
92452
+ unlinkSync19(opts.socketPath);
92278
92453
  } catch (err) {
92279
92454
  log(`webhook-ingest-server: could not unlink stale socket: ${err.message}
92280
92455
  `);
@@ -92367,7 +92542,7 @@ function startWebhookIngestServer(opts) {
92367
92542
  } catch {}
92368
92543
  try {
92369
92544
  if (existsSync43(opts.socketPath))
92370
- unlinkSync18(opts.socketPath);
92545
+ unlinkSync19(opts.socketPath);
92371
92546
  } catch {}
92372
92547
  }
92373
92548
  };
@@ -92379,7 +92554,7 @@ import { join as join55 } from "path";
92379
92554
  import { homedir as homedir16 } from "os";
92380
92555
 
92381
92556
  // ../src/web/webhook-handler.ts
92382
- import { appendFileSync as appendFileSync9, existsSync as existsSync45, mkdirSync as mkdirSync38, readFileSync as readFileSync41, writeFileSync as writeFileSync34 } from "fs";
92557
+ import { appendFileSync as appendFileSync9, existsSync as existsSync45, mkdirSync as mkdirSync38, readFileSync as readFileSync41, writeFileSync as writeFileSync35 } from "fs";
92383
92558
  import { join as join53 } from "path";
92384
92559
 
92385
92560
  // ../src/util/log-rotation.ts
@@ -92734,7 +92909,7 @@ function saveDedupFile(path3, deliveries, now) {
92734
92909
  }
92735
92910
  const sorted = Object.entries(pruned).sort((a, b) => b[1] - a[1]).slice(0, DEDUP_MAX);
92736
92911
  const final = Object.fromEntries(sorted);
92737
- writeFileSync34(path3, JSON.stringify({ deliveries: final }), {
92912
+ writeFileSync35(path3, JSON.stringify({ deliveries: final }), {
92738
92913
  mode: 384
92739
92914
  });
92740
92915
  }
@@ -92764,7 +92939,7 @@ var tokenBuckets = new Map;
92764
92939
  var throttleIssueWindow = new Map;
92765
92940
 
92766
92941
  // ../src/web/webhook-dispatch.ts
92767
- import { existsSync as existsSync46, mkdirSync as mkdirSync39, readFileSync as readFileSync42, writeFileSync as writeFileSync35 } from "fs";
92942
+ import { existsSync as existsSync46, mkdirSync as mkdirSync39, readFileSync as readFileSync42, writeFileSync as writeFileSync36 } from "fs";
92768
92943
  import { join as join54 } from "path";
92769
92944
  import { homedir as homedir15 } from "os";
92770
92945
 
@@ -93074,7 +93249,7 @@ function loadCooldownFile(path3) {
93074
93249
  }
93075
93250
  function saveCooldownFile(path3, dispatches) {
93076
93251
  try {
93077
- writeFileSync35(path3, JSON.stringify({ dispatches }), {
93252
+ writeFileSync36(path3, JSON.stringify({ dispatches }), {
93078
93253
  mode: 384
93079
93254
  });
93080
93255
  } catch {}
@@ -93420,7 +93595,7 @@ function recordWebhookEvent(rec, deps = {}) {
93420
93595
 
93421
93596
  // gateway/ipc-server.ts
93422
93597
  init_format();
93423
- import { renameSync as renameSync19, unlinkSync as unlinkSync20, chmodSync as chmodSync11 } from "fs";
93598
+ import { renameSync as renameSync20, unlinkSync as unlinkSync21, chmodSync as chmodSync11 } from "fs";
93424
93599
  var MAX_BUFFER_SIZE = 1024 * 1024;
93425
93600
  var VALID_OPERATOR_KINDS = new Set(OPERATOR_EVENT_KINDS);
93426
93601
  var AGENT_NAME_RE3 = /^[a-z0-9][a-z0-9_-]{0,50}$/;
@@ -93674,10 +93849,10 @@ function createIpcServer(options) {
93674
93849
  return false;
93675
93850
  };
93676
93851
  try {
93677
- renameSync19(socketPath, socketPath + ".bak");
93852
+ renameSync20(socketPath, socketPath + ".bak");
93678
93853
  } catch {}
93679
93854
  try {
93680
- unlinkSync20(socketPath + ".bak");
93855
+ unlinkSync21(socketPath + ".bak");
93681
93856
  } catch {}
93682
93857
  const clients = new Set;
93683
93858
  const agentIndex = new Map;
@@ -94131,7 +94306,7 @@ function createIpcServer(options) {
94131
94306
  clientBySocketId.clear();
94132
94307
  server.stop(true);
94133
94308
  try {
94134
- renameSync19(socketPath, socketPath + ".bak");
94309
+ renameSync20(socketPath, socketPath + ".bak");
94135
94310
  } catch {}
94136
94311
  }
94137
94312
  };
@@ -94705,8 +94880,132 @@ function buildDiffPreviewCard(input) {
94705
94880
  return { text: text4, reply_markup: kb };
94706
94881
  }
94707
94882
 
94883
+ // gateway/approval-outcome-sources.ts
94884
+ var APPROVAL_OUTCOME_SOURCES = Object.freeze(new Set([
94885
+ "vault_grant_approved",
94886
+ "vault_grant_denied",
94887
+ "vault_grant_timeout",
94888
+ "vault_save_completed",
94889
+ "vault_save_discarded",
94890
+ "vault_save_failed",
94891
+ "vault_save_timeout",
94892
+ "secret_provided",
94893
+ "secret_declined",
94894
+ "secret_provide_failed",
94895
+ "secret_request_timeout",
94896
+ "mental_model_proposal_applied",
94897
+ "mental_model_proposal_denied",
94898
+ "mental_model_proposal_failed",
94899
+ "mental_model_propose_timeout",
94900
+ "skill_proposal_apply",
94901
+ "eval_case_applied",
94902
+ "eval_case_rejected",
94903
+ "eval_case_apply_failed",
94904
+ "eval_case_suppressed"
94905
+ ]));
94906
+ var APPROVAL_OUTCOME_DROPPED_SOURCE = "approval_outcome_dropped";
94907
+ function isApprovalOutcome(msg) {
94908
+ const meta = msg.meta;
94909
+ if (meta == null)
94910
+ return false;
94911
+ if (meta.source != null && APPROVAL_OUTCOME_SOURCES.has(meta.source))
94912
+ return true;
94913
+ return meta.button_callback === "true";
94914
+ }
94915
+ function approvalOutcomeLabel(msg) {
94916
+ const src = msg.meta?.source;
94917
+ if (src != null && src !== "")
94918
+ return src;
94919
+ if (msg.meta?.button_callback === "true")
94920
+ return "button_callback";
94921
+ return "-";
94922
+ }
94923
+ function buildApprovalOutcomeDroppedInbound(opts) {
94924
+ const ts = opts.nowMs ?? Date.now();
94925
+ const list2 = opts.sources.length > 0 ? opts.sources.join(", ") : "unknown";
94926
+ const plural = opts.sources.length === 1 ? "an approval outcome" : "approval outcomes";
94927
+ const msg = {
94928
+ type: "inbound",
94929
+ chatId: opts.chatId,
94930
+ ...opts.threadId != null ? { threadId: opts.threadId } : {},
94931
+ messageId: ts,
94932
+ user: "gateway",
94933
+ userId: 0,
94934
+ ts,
94935
+ text: `\u26a0\ufe0f Your inbound buffer overflowed while every queued message was ${plural}, ` + `so the oldest had to be dropped: ${list2}. Do NOT keep waiting on it \u2014 ` + `re-check the underlying state directly (e.g. retry the vault read to see ` + `whether the grant exists) and tell the operator plainly what was lost.`,
94936
+ meta: {
94937
+ source: APPROVAL_OUTCOME_DROPPED_SOURCE,
94938
+ agent: opts.agent,
94939
+ ...opts.threadId != null ? { message_thread_id: String(opts.threadId) } : {},
94940
+ dropped_sources: list2,
94941
+ dropped_count: String(opts.sources.length)
94942
+ }
94943
+ };
94944
+ return msg;
94945
+ }
94946
+ var APPROVAL_OUTCOME_NOTICE_HOPS = 2;
94947
+ function createApprovalOutcomeDropNotifier(opts) {
94948
+ const log = opts.log ?? ((line) => process.stderr.write(line));
94949
+ const defer = opts.defer ?? ((fn) => queueMicrotask(fn));
94950
+ const hops = opts.hops ?? APPROVAL_OUTCOME_NOTICE_HOPS;
94951
+ const pending = new Map;
94952
+ const scheduled = new Set;
94953
+ return (agent, evicted) => {
94954
+ const label = approvalOutcomeLabel(evicted);
94955
+ log(`telegram gateway: APPROVAL-OUTCOME-DROPPED agent=${agent} source=${label} ` + `chat=${evicted.chatId} ts=${evicted.ts}
94956
+ `);
94957
+ const list2 = pending.get(agent) ?? [];
94958
+ list2.push(label);
94959
+ pending.set(agent, list2);
94960
+ if (scheduled.has(agent))
94961
+ return;
94962
+ scheduled.add(agent);
94963
+ const chatId = evicted.chatId;
94964
+ const threadId = evicted.threadId;
94965
+ const flush = (budget) => {
94966
+ const sources = [...pending.get(agent) ?? []];
94967
+ const before = sources.length;
94968
+ try {
94969
+ opts.push(agent, buildApprovalOutcomeDroppedInbound({
94970
+ agent,
94971
+ chatId,
94972
+ ...threadId != null ? { threadId } : {},
94973
+ sources,
94974
+ ...opts.nowMs != null ? { nowMs: opts.nowMs() } : {}
94975
+ }));
94976
+ } catch (e) {
94977
+ log(`telegram gateway: APPROVAL-OUTCOME-DROPPED notice push failed: ${String(e)}
94978
+ `);
94979
+ } finally {
94980
+ scheduled.delete(agent);
94981
+ }
94982
+ if (budget > 0 && (pending.get(agent)?.length ?? 0) > before) {
94983
+ scheduled.add(agent);
94984
+ defer(() => flush(budget - 1));
94985
+ } else {
94986
+ pending.delete(agent);
94987
+ }
94988
+ };
94989
+ defer(() => flush(hops));
94990
+ };
94991
+ }
94992
+
94708
94993
  // gateway/pending-inbound-buffer.ts
94709
94994
  var DEFAULT_PENDING_INBOUND_CAP = 32;
94995
+ var APPROVAL_OUTCOME_PROTECTION_MS = 15 * 60 * 1000;
94996
+ function selectEvictionVictim(q, nowMs3, protectMaxAgeMs = APPROVAL_OUTCOME_PROTECTION_MS) {
94997
+ for (let i = 0;i < q.length; i++) {
94998
+ if (!isApprovalOutcome(q[i]))
94999
+ return { index: i, reason: "non-outcome" };
95000
+ }
95001
+ for (let i = 0;i < q.length; i++) {
95002
+ const ts = q[i].ts;
95003
+ if (typeof ts === "number" && Number.isFinite(ts) && nowMs3 - ts >= protectMaxAgeMs) {
95004
+ return { index: i, reason: "stale-outcome" };
95005
+ }
95006
+ }
95007
+ return { index: 0, reason: "all-outcomes" };
95008
+ }
94710
95009
  function redeliverBufferedInbound(buffer, agent, send, spool, onDelivered) {
94711
95010
  const pending = buffer.drain(agent);
94712
95011
  let redelivered = 0;
@@ -94815,8 +95114,10 @@ function createPendingInboundBuffer(opts = {}) {
94815
95114
  const cap = opts.capPerAgent ?? DEFAULT_PENDING_INBOUND_CAP;
94816
95115
  const log = opts.log ?? ((line) => process.stderr.write(line));
94817
95116
  const spool = opts.spool;
95117
+ const now = opts.now ?? (() => Date.now());
95118
+ const protectionMs = opts.approvalOutcomeProtectionMs ?? APPROVAL_OUTCOME_PROTECTION_MS;
94818
95119
  const queues = new Map;
94819
- return {
95120
+ const buffer = {
94820
95121
  push(agent, msg) {
94821
95122
  let q = queues.get(agent);
94822
95123
  if (q == null) {
@@ -94825,14 +95126,19 @@ function createPendingInboundBuffer(opts = {}) {
94825
95126
  }
94826
95127
  let evicted = false;
94827
95128
  if (q.length >= cap) {
94828
- const dropped = q.shift();
95129
+ const { index: index2, reason } = selectEvictionVictim(q, now(), protectionMs);
95130
+ const dropped = q.splice(index2, 1)[0];
94829
95131
  evicted = true;
94830
- log(`pending-inbound-buffer: agent=${agent} cap=${cap} reached \u2014 ` + `dropped oldest entry source=${dropped?.meta?.source ?? "-"} ts=${dropped?.ts ?? "-"}
95132
+ log(`pending-inbound-buffer: agent=${agent} cap=${cap} reached \u2014 ` + `dropped entry idx=${index2} reason=${reason} ` + `source=${dropped?.meta?.source ?? "-"} ts=${dropped?.ts ?? "-"}
94831
95133
  `);
94832
- if (dropped != null && opts.onEvict != null) {
94833
- try {
94834
- opts.onEvict(agent, dropped);
94835
- } catch {}
95134
+ if (dropped != null) {
95135
+ const critical = reason !== "non-outcome";
95136
+ const cb = critical ? onEvictCritical : opts.onEvict;
95137
+ if (cb != null) {
95138
+ try {
95139
+ cb(agent, dropped);
95140
+ } catch {}
95141
+ }
94836
95142
  }
94837
95143
  }
94838
95144
  q.push(msg);
@@ -94866,6 +95172,13 @@ function createPendingInboundBuffer(opts = {}) {
94866
95172
  },
94867
95173
  beforeRedeliver: opts.beforeRedeliver
94868
95174
  };
95175
+ const onEvictCritical = opts.onEvictCritical ?? createApprovalOutcomeDropNotifier({
95176
+ push: (a, m) => {
95177
+ buffer.push(a, m);
95178
+ },
95179
+ log
95180
+ });
95181
+ return buffer;
94869
95182
  }
94870
95183
 
94871
95184
  // gateway/represent-guard.ts
@@ -94928,6 +95241,7 @@ function makeSessionBusyDrainDeferral(boundMs, staleGapMs = DEFAULT_DRAIN_DEFER_
94928
95241
  }
94929
95242
 
94930
95243
  // gateway/pending-inbound-buffer.ts
95244
+ var APPROVAL_OUTCOME_PROTECTION_MS2 = 15 * 60 * 1000;
94931
95245
  function redeliverBufferedInbound2(buffer, agent, send, spool, onDelivered) {
94932
95246
  const pending = buffer.drain(agent);
94933
95247
  let redelivered = 0;
@@ -97832,7 +98146,7 @@ function purgeStaleTurnsForChat(chatId, keys, purger, isStale = () => true) {
97832
98146
  }
97833
98147
 
97834
98148
  // gateway/compaction-marker.ts
97835
- import { statSync as statSync17, unlinkSync as unlinkSync21 } from "node:fs";
98149
+ import { statSync as statSync17, unlinkSync as unlinkSync22 } from "node:fs";
97836
98150
  import { homedir as homedir17 } from "node:os";
97837
98151
  import { join as join56 } from "node:path";
97838
98152
  var COMPACTION_MARKER_FILE = "compaction-in-flight.json";
@@ -97849,7 +98163,7 @@ function readCompactionMarkerAgeMs(stateDir, now) {
97849
98163
  }
97850
98164
  function removeCompactionMarker(stateDir) {
97851
98165
  try {
97852
- unlinkSync21(join56(stateDir, COMPACTION_MARKER_FILE));
98166
+ unlinkSync22(join56(stateDir, COMPACTION_MARKER_FILE));
97853
98167
  } catch {}
97854
98168
  }
97855
98169
 
@@ -98609,6 +98923,26 @@ function maybeFireWarmup(ctx) {
98609
98923
  }
98610
98924
 
98611
98925
  // gateway/self-improve-proposal-wiring.ts
98926
+ function buildEvalCaseSuppressedInbound(opts) {
98927
+ const ts = opts.nowMs ?? Date.now();
98928
+ return {
98929
+ type: "inbound",
98930
+ chatId: opts.chatId,
98931
+ ...opts.threadId != null ? { threadId: opts.threadId } : {},
98932
+ messageId: ts,
98933
+ user: "self-improve",
98934
+ userId: 0,
98935
+ ts,
98936
+ text: `\u2139\ufe0f Your proposed eval case for \`${opts.skillSlug}\` was NOT posted as a ` + `card: the operator already dismissed this exact case, and the dismissal ` + `is still in effect. This is EXPECTED and is not an error or a failure on ` + `your part \u2014 nothing was written and no card is pending, so do NOT wait ` + `for an approval tap. Do NOT re-propose this case. Carry on with the ` + `original task without it.`,
98937
+ meta: {
98938
+ source: "eval_case_suppressed",
98939
+ agent: opts.agent,
98940
+ ...opts.threadId != null ? { message_thread_id: String(opts.threadId) } : {},
98941
+ skill_slug: opts.skillSlug,
98942
+ fingerprint: opts.fingerprint
98943
+ }
98944
+ };
98945
+ }
98612
98946
  function handlePostSkillProposal(msg, deps) {
98613
98947
  const { bot, assertAllowedChat, swallowingApiCall } = deps;
98614
98948
  const self = process.env.SWITCHROOM_AGENT_NAME;
@@ -98665,8 +98999,28 @@ function handlePostSkillProposal(msg, deps) {
98665
98999
  process.stderr.write(`telegram gateway: post_skill_proposal agent=${msg.agentName} chat=${msg.chatId} ` + `proposal=${proposal.id} slug=${proposal.skill_slug} new=${proposal.is_new}
98666
99000
  `);
98667
99001
  }
99002
+ function isEvalCaseProposalSuppressed(stateDir, candidate, opts = {}) {
99003
+ if (typeof candidate.fingerprint !== "string" || candidate.fingerprint.length === 0) {
99004
+ return false;
99005
+ }
99006
+ const now = (opts.now ?? Date.now)();
99007
+ const ttl = opts.ttlMs ?? REJECTION_TTL_MS;
99008
+ for (const p of readEvalCaseProposals(stateDir)) {
99009
+ if (p.status !== "rejected")
99010
+ continue;
99011
+ if (p.skill_slug !== candidate.skillSlug)
99012
+ continue;
99013
+ if (p.fingerprint !== candidate.fingerprint)
99014
+ continue;
99015
+ const age = now - new Date(p.created_at).getTime();
99016
+ if (!Number.isFinite(age) || age > ttl)
99017
+ continue;
99018
+ return true;
99019
+ }
99020
+ return false;
99021
+ }
98668
99022
  function handlePostEvalCaseProposal(msg, deps) {
98669
- const { bot, assertAllowedChat, swallowingApiCall } = deps;
99023
+ const { bot, assertAllowedChat, swallowingApiCall, deliverResumeSyntheticOrBuffer } = deps;
98670
99024
  const self = process.env.SWITCHROOM_AGENT_NAME;
98671
99025
  if (self && msg.agentName !== self) {
98672
99026
  process.stderr.write(`telegram gateway: post_eval_case_proposal rejected \u2014 agent mismatch (${msg.agentName} != ${self})
@@ -98683,6 +99037,21 @@ function handlePostEvalCaseProposal(msg, deps) {
98683
99037
  const stateDir = process.env.TELEGRAM_STATE_DIR;
98684
99038
  if (stateDir == null || stateDir.length === 0) {
98685
99039
  process.stderr.write(`telegram gateway: post_eval_case_proposal: TELEGRAM_STATE_DIR unset, skipping
99040
+ `);
99041
+ return;
99042
+ }
99043
+ if (isEvalCaseProposalSuppressed(stateDir, {
99044
+ skillSlug: msg.skillSlug,
99045
+ fingerprint: msg.fingerprint
99046
+ })) {
99047
+ const delivered = deliverResumeSyntheticOrBuffer(msg.agentName, buildEvalCaseSuppressedInbound({
99048
+ agent: msg.agentName,
99049
+ chatId: msg.chatId,
99050
+ ...msg.threadId != null ? { threadId: msg.threadId } : {},
99051
+ skillSlug: msg.skillSlug,
99052
+ fingerprint: msg.fingerprint
99053
+ }));
99054
+ process.stderr.write(`telegram gateway: post_eval_case_proposal suppressed (dismissed before) ` + `slug=${msg.skillSlug} fp=${msg.fingerprint} delivered=${delivered}
98686
99055
  `);
98687
99056
  return;
98688
99057
  }
@@ -99214,15 +99583,15 @@ function escapeBody2(s) {
99214
99583
  }
99215
99584
 
99216
99585
  // gateway/pid-file.ts
99217
- import { writeFileSync as writeFileSync36, readFileSync as readFileSync43, unlinkSync as unlinkSync22, renameSync as renameSync20 } from "node:fs";
99586
+ import { writeFileSync as writeFileSync37, readFileSync as readFileSync43, unlinkSync as unlinkSync23, renameSync as renameSync21 } from "node:fs";
99218
99587
  function writePidFile(path3, record) {
99219
99588
  const tmp = `${path3}.tmp-${process.pid}-${Date.now()}`;
99220
- writeFileSync36(tmp, JSON.stringify(record), "utf-8");
99221
- renameSync20(tmp, path3);
99589
+ writeFileSync37(tmp, JSON.stringify(record), "utf-8");
99590
+ renameSync21(tmp, path3);
99222
99591
  }
99223
99592
  function clearPidFile(path3) {
99224
99593
  try {
99225
- unlinkSync22(path3);
99594
+ unlinkSync23(path3);
99226
99595
  } catch {}
99227
99596
  }
99228
99597
 
@@ -99439,11 +99808,11 @@ function safeCount(fn) {
99439
99808
  }
99440
99809
 
99441
99810
  // gateway/session-marker.ts
99442
- import { writeFileSync as writeFileSync37, readFileSync as readFileSync45, renameSync as renameSync21, unlinkSync as unlinkSync23 } from "node:fs";
99811
+ import { writeFileSync as writeFileSync38, readFileSync as readFileSync45, renameSync as renameSync22, unlinkSync as unlinkSync24 } from "node:fs";
99443
99812
  function writeSessionMarker(path3, marker) {
99444
99813
  const tmp = `${path3}.tmp-${process.pid}-${Date.now()}`;
99445
- writeFileSync37(tmp, JSON.stringify(marker), "utf-8");
99446
- renameSync21(tmp, path3);
99814
+ writeFileSync38(tmp, JSON.stringify(marker), "utf-8");
99815
+ renameSync22(tmp, path3);
99447
99816
  }
99448
99817
  function readSessionMarker(path3) {
99449
99818
  try {
@@ -99469,12 +99838,12 @@ function shouldFireRestartBanner(input) {
99469
99838
  }
99470
99839
 
99471
99840
  // gateway/clean-shutdown-marker.ts
99472
- import { writeFileSync as writeFileSync38, readFileSync as readFileSync46, renameSync as renameSync22, unlinkSync as unlinkSync24 } from "node:fs";
99841
+ import { writeFileSync as writeFileSync39, readFileSync as readFileSync46, renameSync as renameSync23, unlinkSync as unlinkSync25 } from "node:fs";
99473
99842
  var DEFAULT_MAX_AGE_MS = 60000;
99474
99843
  function writeCleanShutdownMarker(path3, marker) {
99475
99844
  const tmp = `${path3}.tmp-${process.pid}-${Date.now()}`;
99476
- writeFileSync38(tmp, JSON.stringify(marker), "utf-8");
99477
- renameSync22(tmp, path3);
99845
+ writeFileSync39(tmp, JSON.stringify(marker), "utf-8");
99846
+ renameSync23(tmp, path3);
99478
99847
  }
99479
99848
  function readCleanShutdownMarker(path3) {
99480
99849
  try {
@@ -99493,7 +99862,7 @@ function readCleanShutdownMarker(path3) {
99493
99862
  }
99494
99863
  function clearCleanShutdownMarker(path3) {
99495
99864
  try {
99496
- unlinkSync24(path3);
99865
+ unlinkSync25(path3);
99497
99866
  } catch {}
99498
99867
  }
99499
99868
  function shouldSuppressRecoveryBanner(marker, now, maxAgeMs = DEFAULT_MAX_AGE_MS) {
@@ -101409,31 +101778,31 @@ function startSubagentWatcher(config) {
101409
101778
  // ../src/worktree/registry.ts
101410
101779
  import {
101411
101780
  mkdirSync as mkdirSync41,
101412
- writeFileSync as writeFileSync39,
101781
+ writeFileSync as writeFileSync40,
101413
101782
  readFileSync as readFileSync48,
101414
101783
  readdirSync as readdirSync13,
101415
- unlinkSync as unlinkSync25,
101784
+ unlinkSync as unlinkSync26,
101416
101785
  existsSync as existsSync48,
101417
- renameSync as renameSync23
101786
+ renameSync as renameSync24
101418
101787
  } from "node:fs";
101419
101788
  import { join as join58, resolve as resolve10 } from "node:path";
101420
101789
  import { homedir as homedir18 } from "node:os";
101421
- function registryDir() {
101790
+ function registryDir2() {
101422
101791
  return resolve10(process.env.SWITCHROOM_WORKTREE_DIR ?? join58(homedir18(), ".switchroom", "worktrees"));
101423
101792
  }
101424
101793
  function recordPath(id) {
101425
- return join58(registryDir(), `${id}.json`);
101794
+ return join58(registryDir2(), `${id}.json`);
101426
101795
  }
101427
101796
  function ensureDir4() {
101428
- mkdirSync41(registryDir(), { recursive: true });
101797
+ mkdirSync41(registryDir2(), { recursive: true });
101429
101798
  }
101430
- function writeRecord(record) {
101799
+ function writeRecord2(record) {
101431
101800
  ensureDir4();
101432
101801
  const target = recordPath(record.id);
101433
101802
  const tmp = `${target}.tmp${process.pid}`;
101434
- writeFileSync39(tmp, JSON.stringify(record, null, 2) + `
101803
+ writeFileSync40(tmp, JSON.stringify(record, null, 2) + `
101435
101804
  `, { mode: 384 });
101436
- renameSync23(tmp, target);
101805
+ renameSync24(tmp, target);
101437
101806
  }
101438
101807
  function readRecord(id) {
101439
101808
  const path3 = recordPath(id);
@@ -101446,7 +101815,7 @@ function readRecord(id) {
101446
101815
  }
101447
101816
  function listRecords() {
101448
101817
  ensureDir4();
101449
- const dir = registryDir();
101818
+ const dir = registryDir2();
101450
101819
  const records = [];
101451
101820
  for (const entry of readdirSync13(dir)) {
101452
101821
  if (!entry.endsWith(".json"))
@@ -101465,7 +101834,7 @@ function touchHeartbeat(id, onAfterRead) {
101465
101834
  onAfterRead?.();
101466
101835
  if (!recordExists(id))
101467
101836
  return;
101468
- writeRecord({ ...rec, heartbeatAt: new Date().toISOString() });
101837
+ writeRecord2({ ...rec, heartbeatAt: new Date().toISOString() });
101469
101838
  }
101470
101839
  function recordExists(id) {
101471
101840
  return existsSync48(recordPath(id));
@@ -101845,7 +102214,7 @@ function maybeRenderUpdateAnnouncement(opts = {}) {
101845
102214
 
101846
102215
  // issues-card.ts
101847
102216
  init_card_format();
101848
- import { readFileSync as readFileSync56, writeFileSync as writeFileSync44 } from "node:fs";
102217
+ import { readFileSync as readFileSync56, writeFileSync as writeFileSync45 } from "node:fs";
101849
102218
  var SEVERITY_EMOJI = {
101850
102219
  info: "\u2139\ufe0f",
101851
102220
  warn: "\u26a0\ufe0f",
@@ -101953,7 +102322,7 @@ function readPersistedMessageId(path3, log) {
101953
102322
  }
101954
102323
  function writePersistedMessageId(path3, messageId, log) {
101955
102324
  try {
101956
- writeFileSync44(path3, JSON.stringify({ messageId }) + `
102325
+ writeFileSync45(path3, JSON.stringify({ messageId }) + `
101957
102326
  `, { mode: 384 });
101958
102327
  } catch (err) {
101959
102328
  log(`issues-card: persist write failed (${err.message})`);
@@ -102057,10 +102426,10 @@ import {
102057
102426
  openSync as openSync15,
102058
102427
  readdirSync as readdirSync15,
102059
102428
  readFileSync as readFileSync57,
102060
- renameSync as renameSync26,
102429
+ renameSync as renameSync27,
102061
102430
  statSync as statSync20,
102062
- unlinkSync as unlinkSync26,
102063
- writeFileSync as writeFileSync45,
102431
+ unlinkSync as unlinkSync27,
102432
+ writeFileSync as writeFileSync46,
102064
102433
  writeSync as writeSync8
102065
102434
  } from "node:fs";
102066
102435
  import { join as join64 } from "node:path";
@@ -102143,8 +102512,8 @@ function writeAll(stateDir, events) {
102143
102512
  const body = events.length === 0 ? "" : events.map((e) => JSON.stringify(e)).join(`
102144
102513
  `) + `
102145
102514
  `;
102146
- writeFileSync45(tmp, body, "utf-8");
102147
- renameSync26(tmp, path3);
102515
+ writeFileSync46(tmp, body, "utf-8");
102516
+ renameSync27(tmp, path3);
102148
102517
  }
102149
102518
  var ORPHAN_TMP_TTL_MS = 60000;
102150
102519
  var TMP_PREFIX = `${ISSUES_FILE}.tmp-`;
@@ -102163,7 +102532,7 @@ function sweepOrphanTmpFiles(stateDir) {
102163
102532
  try {
102164
102533
  const stat = statSync20(tmpPath2);
102165
102534
  if (stat.mtimeMs < cutoff) {
102166
- unlinkSync26(tmpPath2);
102535
+ unlinkSync27(tmpPath2);
102167
102536
  }
102168
102537
  } catch {}
102169
102538
  }
@@ -102199,7 +102568,7 @@ function withLock(stateDir, fn) {
102199
102568
  closeSync15(fd);
102200
102569
  } catch {}
102201
102570
  try {
102202
- unlinkSync26(lockPath);
102571
+ unlinkSync27(lockPath);
102203
102572
  } catch {}
102204
102573
  }
102205
102574
  }
@@ -102213,13 +102582,13 @@ function tryStealStaleLock(lockPath) {
102213
102582
  const pid = Number(pidStr);
102214
102583
  if (!Number.isFinite(pid) || pid <= 0) {
102215
102584
  try {
102216
- unlinkSync26(lockPath);
102585
+ unlinkSync27(lockPath);
102217
102586
  } catch {}
102218
102587
  return true;
102219
102588
  }
102220
102589
  if (pid === process.pid) {
102221
102590
  try {
102222
- unlinkSync26(lockPath);
102591
+ unlinkSync27(lockPath);
102223
102592
  } catch {}
102224
102593
  return true;
102225
102594
  }
@@ -102234,7 +102603,7 @@ function tryStealStaleLock(lockPath) {
102234
102603
  return false;
102235
102604
  }
102236
102605
  try {
102237
- unlinkSync26(lockPath);
102606
+ unlinkSync27(lockPath);
102238
102607
  } catch {}
102239
102608
  return true;
102240
102609
  }
@@ -103436,7 +103805,7 @@ function isDiffPreApproved(agentName3, unifiedDiff, deps) {
103436
103805
 
103437
103806
  // credits-watch.ts
103438
103807
  init_card_format();
103439
- import { readFileSync as readFileSync58, writeFileSync as writeFileSync46, existsSync as existsSync56, mkdirSync as mkdirSync47 } from "fs";
103808
+ import { readFileSync as readFileSync58, writeFileSync as writeFileSync47, existsSync as existsSync56, mkdirSync as mkdirSync47 } from "fs";
103440
103809
  import { join as join67 } from "path";
103441
103810
  var STATE_FILE = "credits-watch.json";
103442
103811
  var DEFAULT_CREDIT_FATAL_REASONS = new Set;
@@ -103560,14 +103929,14 @@ function loadCreditState(stateDir) {
103560
103929
  function saveCreditState(stateDir, state7) {
103561
103930
  mkdirSync47(stateDir, { recursive: true });
103562
103931
  const path3 = join67(stateDir, STATE_FILE);
103563
- writeFileSync46(path3, JSON.stringify(state7, null, 2) + `
103932
+ writeFileSync47(path3, JSON.stringify(state7, null, 2) + `
103564
103933
  `, { mode: 384 });
103565
103934
  }
103566
103935
 
103567
103936
  // quota-watch.ts
103568
103937
  init_auth_snapshot_format();
103569
103938
  init_card_format();
103570
- import { readFileSync as readFileSync59, writeFileSync as writeFileSync47, existsSync as existsSync57, mkdirSync as mkdirSync48 } from "fs";
103939
+ import { readFileSync as readFileSync59, writeFileSync as writeFileSync48, existsSync as existsSync57, mkdirSync as mkdirSync48 } from "fs";
103571
103940
  import { join as join68 } from "path";
103572
103941
  var STATE_FILE2 = "quota-watch.json";
103573
103942
  function emptyQuotaWatchState() {
@@ -103829,7 +104198,7 @@ function loadQuotaWatchState(stateDir) {
103829
104198
  function saveQuotaWatchState(stateDir, state7) {
103830
104199
  mkdirSync48(stateDir, { recursive: true });
103831
104200
  const path3 = join68(stateDir, STATE_FILE2);
103832
- writeFileSync47(path3, JSON.stringify(state7, null, 2) + `
104201
+ writeFileSync48(path3, JSON.stringify(state7, null, 2) + `
103833
104202
  `, { mode: 384 });
103834
104203
  }
103835
104204
  function patchQuotaWatchState(current, accountLabel, accountState) {
@@ -103882,7 +104251,7 @@ import {
103882
104251
  openSync as openSync16,
103883
104252
  readFileSync as readFileSync60,
103884
104253
  statSync as statSync22,
103885
- unlinkSync as unlinkSync27,
104254
+ unlinkSync as unlinkSync28,
103886
104255
  utimesSync as utimesSync2
103887
104256
  } from "node:fs";
103888
104257
  import { join as join69 } from "node:path";
@@ -103891,7 +104260,7 @@ var TURN_ACTIVE_HARD_TTL_MS2 = 10 * 60000;
103891
104260
  var TURN_ACTIVE_IDLE_SWEEP_MS = 60000;
103892
104261
  function removeTurnActiveMarker2(stateDir) {
103893
104262
  try {
103894
- unlinkSync27(join69(stateDir, TURN_ACTIVE_MARKER_FILE2));
104263
+ unlinkSync28(join69(stateDir, TURN_ACTIVE_MARKER_FILE2));
103895
104264
  } catch {}
103896
104265
  }
103897
104266
  function sweepStaleTurnActiveMarker(stateDir, opts) {
@@ -103910,7 +104279,7 @@ function sweepStaleTurnActiveMarker(stateDir, opts) {
103910
104279
  try {
103911
104280
  payload = readFileSync60(path3, "utf8");
103912
104281
  } catch {}
103913
- unlinkSync27(path3);
104282
+ unlinkSync28(path3);
103914
104283
  if (opts.onRemove) {
103915
104284
  try {
103916
104285
  opts.onRemove({
@@ -103972,9 +104341,9 @@ import {
103972
104341
  fsyncSync as fsyncSync6,
103973
104342
  openSync as openSync17,
103974
104343
  readFileSync as readFileSync61,
103975
- renameSync as renameSync27,
103976
- unlinkSync as unlinkSync28,
103977
- writeFileSync as writeFileSync48
104344
+ renameSync as renameSync28,
104345
+ unlinkSync as unlinkSync29,
104346
+ writeFileSync as writeFileSync49
103978
104347
  } from "node:fs";
103979
104348
  import { join as join71 } from "node:path";
103980
104349
  var BOOT_BEACON_FILE = "gateway-beacon.json";
@@ -104081,12 +104450,12 @@ function writeBootBeaconFile(stateDir, beacon) {
104081
104450
  try {
104082
104451
  mkdirStateSync(stateDir, { recursive: true, mode: 448 });
104083
104452
  fd = openSync17(tmp, "w", 384);
104084
- writeFileSync48(fd, serializeBootBeacon(beacon));
104453
+ writeFileSync49(fd, serializeBootBeacon(beacon));
104085
104454
  adoptStateOwnershipFd(fd, stateDir);
104086
104455
  fsyncSync6(fd);
104087
104456
  closeSync17(fd);
104088
104457
  fd = undefined;
104089
- renameSync27(tmp, path3);
104458
+ renameSync28(tmp, path3);
104090
104459
  return true;
104091
104460
  } catch {
104092
104461
  if (fd !== undefined) {
@@ -104095,7 +104464,7 @@ function writeBootBeaconFile(stateDir, beacon) {
104095
104464
  } catch {}
104096
104465
  }
104097
104466
  try {
104098
- unlinkSync28(tmp);
104467
+ unlinkSync29(tmp);
104099
104468
  } catch {}
104100
104469
  return false;
104101
104470
  }
@@ -104467,10 +104836,10 @@ function startOutboxSweep(deps) {
104467
104836
  }
104468
104837
 
104469
104838
  // ../src/build-info.ts
104470
- var VERSION2 = "0.21.7";
104471
- var COMMIT_SHA = "e4e50f84";
104472
- var COMMIT_DATE = "2026-08-11T23:08:27Z";
104473
- var LATEST_PR = 4613;
104839
+ var VERSION2 = "0.21.8";
104840
+ var COMMIT_SHA = "d778afb6";
104841
+ var COMMIT_DATE = "2026-08-13T03:44:10Z";
104842
+ var LATEST_PR = 4677;
104474
104843
  var COMMITS_AHEAD_OF_TAG = 0;
104475
104844
 
104476
104845
  // gateway/boot-version.ts
@@ -105852,7 +106221,7 @@ function selectResumeBuilder(endedVia, opts) {
105852
106221
  }
105853
106222
 
105854
106223
  // gateway/boot-briefing-wiring.ts
105855
- import { existsSync as existsSync60, readFileSync as readFileSync63, writeFileSync as writeFileSync49 } from "node:fs";
106224
+ import { existsSync as existsSync60, readFileSync as readFileSync63, writeFileSync as writeFileSync50 } from "node:fs";
105856
106225
  import { join as join74 } from "node:path";
105857
106226
  init_history();
105858
106227
 
@@ -106208,7 +106577,7 @@ async function maybeQueueBootBriefing(opts) {
106208
106577
  if (!bootId)
106209
106578
  return;
106210
106579
  try {
106211
- writeFileSync49(genMarkerPath, `${bootId}
106580
+ writeFileSync50(genMarkerPath, `${bootId}
106212
106581
  `);
106213
106582
  } catch {}
106214
106583
  };
@@ -106282,7 +106651,7 @@ async function maybeQueueBootBriefing(opts) {
106282
106651
  }
106283
106652
 
106284
106653
  // gateway/pending-turn-env.ts
106285
- import { existsSync as existsSync61, renameSync as renameSync28, rmSync as rmSync8, writeFileSync as writeFileSync50 } from "node:fs";
106654
+ import { existsSync as existsSync61, renameSync as renameSync29, rmSync as rmSync8, writeFileSync as writeFileSync51 } from "node:fs";
106286
106655
  import { join as join75 } from "node:path";
106287
106656
  function writePendingTurnEnv(agentDir, pending, log = (l) => process.stderr.write(l)) {
106288
106657
  const pendingEnvPath = join75(agentDir, ".pending-turn.env");
@@ -106299,10 +106668,10 @@ function writePendingTurnEnv(agentDir, pending, log = (l) => process.stderr.writ
106299
106668
  pending.interrupt_reason != null ? `SWITCHROOM_PENDING_INTERRUPT_REASON=${pending.interrupt_reason}` : `SWITCHROOM_PENDING_INTERRUPT_REASON=`
106300
106669
  ];
106301
106670
  const pendingEnvTmp = `${pendingEnvPath}.tmp-${process.pid}`;
106302
- writeFileSync50(pendingEnvTmp, lines.join(`
106671
+ writeFileSync51(pendingEnvTmp, lines.join(`
106303
106672
  `) + `
106304
106673
  `, { mode: 384 });
106305
- renameSync28(pendingEnvTmp, pendingEnvPath);
106674
+ renameSync29(pendingEnvTmp, pendingEnvPath);
106306
106675
  log(`telegram gateway: pending-turn env written to ${pendingEnvPath} ` + `turnKey=${pending.turn_key} endedVia=${pending.ended_via ?? "open"}
106307
106676
  `);
106308
106677
  } else if (existsSync61(pendingEnvPath)) {
@@ -106316,8 +106685,184 @@ function writePendingTurnEnv(agentDir, pending, log = (l) => process.stderr.writ
106316
106685
  }
106317
106686
  }
106318
106687
 
106688
+ // gateway/agent-process-liveness.ts
106689
+ import { existsSync as existsSync62, readFileSync as readFileSync64, writeFileSync as writeFileSync52, renameSync as renameSync30, unlinkSync as unlinkSync30, readdirSync as readdirSync16 } from "node:fs";
106690
+ import { join as join76, dirname as dirname28, basename as basename18 } from "node:path";
106691
+ var AGENT_PROCESS_RECORD_FILE = "agent-process.json";
106692
+ var BOOT_RESUME_DONE_FILE = ".boot-resume-done";
106693
+ function parseProcStat(raw) {
106694
+ const close = raw.lastIndexOf(") ");
106695
+ const open = raw.indexOf(" (");
106696
+ if (close < 0 || open < 0 || close < open)
106697
+ return null;
106698
+ const comm = raw.slice(open + 2, close);
106699
+ const rest = raw.slice(close + 2).trim().split(/\s+/);
106700
+ const state7 = rest[0];
106701
+ const starttime = rest[19];
106702
+ if (state7 == null || state7.length === 0)
106703
+ return null;
106704
+ if (starttime == null || !/^\d+$/.test(starttime))
106705
+ return null;
106706
+ return { comm, state: state7, starttime };
106707
+ }
106708
+ function readProcIdentity(pid, procRoot = "/proc") {
106709
+ if (!Number.isInteger(pid) || pid <= 0)
106710
+ return null;
106711
+ let identity;
106712
+ try {
106713
+ identity = parseProcStat(readFileSync64(join76(procRoot, String(pid), "stat"), "utf8"));
106714
+ } catch {
106715
+ return null;
106716
+ }
106717
+ if (identity != null && identity.state === "Z")
106718
+ return null;
106719
+ return identity;
106720
+ }
106721
+ function readAgentProcessRecord(path3) {
106722
+ let parsed;
106723
+ try {
106724
+ parsed = JSON.parse(readFileSync64(path3, "utf8"));
106725
+ } catch {
106726
+ return null;
106727
+ }
106728
+ if (parsed == null || typeof parsed !== "object")
106729
+ return null;
106730
+ const o = parsed;
106731
+ const pid = typeof o.pid === "number" ? o.pid : Number(o.pid);
106732
+ const starttime = typeof o.starttime === "string" ? o.starttime : String(o.starttime ?? "");
106733
+ if (!Number.isInteger(pid) || pid <= 0)
106734
+ return null;
106735
+ if (!/^\d+$/.test(starttime))
106736
+ return null;
106737
+ const rec = { pid, starttime };
106738
+ if (typeof o.comm === "string" && o.comm.length > 0)
106739
+ rec.comm = o.comm;
106740
+ if (typeof o.boot_at === "number" && Number.isFinite(o.boot_at))
106741
+ rec.boot_at = o.boot_at;
106742
+ return rec;
106743
+ }
106744
+ function bootResumeDonePath(stateDir) {
106745
+ return join76(stateDir, BOOT_RESUME_DONE_FILE);
106746
+ }
106747
+ function containerBootIdentity(procRoot = "/proc") {
106748
+ return readProcIdentity(1, procRoot)?.starttime ?? null;
106749
+ }
106750
+ function readBootResumeSentinel(path3, opts = {}) {
106751
+ let raw;
106752
+ try {
106753
+ if (!existsSync62(path3))
106754
+ return { present: false, stale: false };
106755
+ raw = readFileSync64(path3, "utf8");
106756
+ } catch {
106757
+ return { present: false, stale: false };
106758
+ }
106759
+ let stamped = null;
106760
+ try {
106761
+ const parsed = JSON.parse(raw);
106762
+ if (parsed != null && typeof parsed === "object" && typeof parsed.boot === "string" && parsed.boot.length > 0) {
106763
+ stamped = parsed.boot;
106764
+ }
106765
+ } catch {}
106766
+ const live = containerBootIdentity(opts.procRoot);
106767
+ if (stamped != null && live != null && stamped !== live)
106768
+ return { present: true, stale: true };
106769
+ return { present: true, stale: false };
106770
+ }
106771
+ function sweepStrayTmps(path3) {
106772
+ const prefix = `${basename18(path3)}.tmp.`;
106773
+ try {
106774
+ for (const name of readdirSync16(dirname28(path3))) {
106775
+ if (!name.startsWith(prefix))
106776
+ continue;
106777
+ try {
106778
+ unlinkSync30(join76(dirname28(path3), name));
106779
+ } catch {}
106780
+ }
106781
+ } catch {}
106782
+ }
106783
+ function markBootResumeComplete(stateDir, opts = {}) {
106784
+ const path3 = opts.path ?? bootResumeDonePath(stateDir);
106785
+ const tmp = `${path3}.tmp.${process.pid}`;
106786
+ const boot = containerBootIdentity(opts.procRoot);
106787
+ try {
106788
+ const token = { pid: process.pid, at: opts.now ?? Date.now() };
106789
+ if (boot != null)
106790
+ token.boot = boot;
106791
+ writeFileSync52(tmp, JSON.stringify(token) + `
106792
+ `);
106793
+ renameSync30(tmp, path3);
106794
+ sweepStrayTmps(path3);
106795
+ } catch (err) {
106796
+ try {
106797
+ unlinkSync30(tmp);
106798
+ } catch {}
106799
+ const log = opts.log ?? ((s) => process.stderr.write(s));
106800
+ log(`telegram gateway: boot: could not stamp ${BOOT_RESUME_DONE_FILE} ` + `(${err.message}) \u2014 a gateway respawn may repeat the boot resume (#4641)
106801
+ `);
106802
+ }
106803
+ }
106804
+ function decideGatewayOnlyRespawn(input) {
106805
+ const { sentinelPresent, sentinelStale, record: record2, live } = input;
106806
+ if (!sentinelPresent) {
106807
+ return { gatewayOnly: false, reason: "no-boot-resume-sentinel", pid: record2?.pid ?? null };
106808
+ }
106809
+ if (sentinelStale === true) {
106810
+ return { gatewayOnly: false, reason: "stale-boot-token", pid: record2?.pid ?? null };
106811
+ }
106812
+ if (record2 == null) {
106813
+ return { gatewayOnly: true, reason: "gateway-only-respawn-no-record", pid: null };
106814
+ }
106815
+ if (live == null)
106816
+ return { gatewayOnly: false, reason: "agent-process-dead", pid: record2.pid };
106817
+ if (live.starttime !== record2.starttime) {
106818
+ return { gatewayOnly: false, reason: "starttime-mismatch", pid: record2.pid };
106819
+ }
106820
+ if (record2.comm != null && record2.comm !== live.comm) {
106821
+ return { gatewayOnly: false, reason: "comm-mismatch", pid: record2.pid };
106822
+ }
106823
+ return { gatewayOnly: true, reason: "gateway-only-respawn", pid: record2.pid };
106824
+ }
106825
+ function detectGatewayOnlyRespawn(opts) {
106826
+ if (opts.enabled === false)
106827
+ return { gatewayOnly: false, reason: "guard-disabled", pid: null };
106828
+ const procRoot = opts.procRoot ?? "/proc";
106829
+ const sentinel = readBootResumeSentinel(opts.sentinelPath ?? bootResumeDonePath(opts.stateDir), { procRoot });
106830
+ const recordPath2 = opts.recordPath ?? join76(opts.stateDir, AGENT_PROCESS_RECORD_FILE);
106831
+ const record2 = readAgentProcessRecord(recordPath2);
106832
+ const live = record2 == null ? null : readProcIdentity(record2.pid, procRoot);
106833
+ return decideGatewayOnlyRespawn({
106834
+ sentinelPresent: sentinel.present,
106835
+ sentinelStale: sentinel.stale,
106836
+ record: record2,
106837
+ live
106838
+ });
106839
+ }
106840
+ function shouldSkipBootResumeForGatewayOnlyRespawn(stateDir, opts = {}) {
106841
+ const log = opts.log ?? ((s) => process.stderr.write(s));
106842
+ let decision;
106843
+ try {
106844
+ decision = detectGatewayOnlyRespawn({
106845
+ ...opts,
106846
+ stateDir,
106847
+ enabled: opts.enabled ?? process.env.SWITCHROOM_GATEWAY_RESPAWN_GUARD !== "0"
106848
+ });
106849
+ } catch (err) {
106850
+ log(`telegram gateway: boot: agent-liveness probe failed (${err.message}) \u2014 assuming agent restart
106851
+ `);
106852
+ return false;
106853
+ }
106854
+ if (decision.gatewayOnly) {
106855
+ log(`telegram gateway: boot: GATEWAY-ONLY respawn \u2014 this container generation's boot resume is already ` + `done (${BOOT_RESUME_DONE_FILE} present, reason=${decision.reason}, agent pid=${decision.pid ?? "unrecorded"}). ` + `Skipping the orphan-turn reaper, the bridge-dead marker, the resume synthetic, crash-redelivery capture ` + `and .pending-turn.env (#4641): the claude session, its in-flight turn and its sub-agents were never interrupted.
106856
+ `);
106857
+ return true;
106858
+ }
106859
+ log(`telegram gateway: boot: agent-liveness probe \u2192 treating as agent restart (reason=${decision.reason})
106860
+ `);
106861
+ return false;
106862
+ }
106863
+
106319
106864
  // gateway/bridge-dead-watchdog.ts
106320
- import { readFileSync as readFileSync64, writeFileSync as writeFileSync51, renameSync as renameSync29, unlinkSync as unlinkSync29 } from "node:fs";
106865
+ import { readFileSync as readFileSync65, writeFileSync as writeFileSync53, renameSync as renameSync31, unlinkSync as unlinkSync31 } from "node:fs";
106321
106866
 
106322
106867
  // gateway/cron-session.ts
106323
106868
  var CRON_IDENTITY_SUFFIX2 = "-cron";
@@ -106350,7 +106895,7 @@ function readFreshCrashLogTail(path3, opts = {}) {
106350
106895
  const nowMs3 = opts.nowMs ?? Date.now();
106351
106896
  const freshWindowMs = opts.freshWindowMs ?? CRASH_LOG_FRESH_WINDOW_MS;
106352
106897
  const maxLines = opts.maxLines ?? CRASH_LOG_TAIL_LINES;
106353
- const readFile = opts.readFile ?? ((p) => readFileSync64(p, "utf8"));
106898
+ const readFile = opts.readFile ?? ((p) => readFileSync65(p, "utf8"));
106354
106899
  let raw;
106355
106900
  try {
106356
106901
  raw = readFile(path3);
@@ -106371,13 +106916,13 @@ function readFreshCrashLogTail(path3, opts = {}) {
106371
106916
  }
106372
106917
  function writeBridgeDeadEscalationMarker(path3, marker) {
106373
106918
  const tmp = `${path3}.tmp-${process.pid}-${Date.now()}`;
106374
- writeFileSync51(tmp, JSON.stringify(marker), "utf8");
106375
- renameSync29(tmp, path3);
106919
+ writeFileSync53(tmp, JSON.stringify(marker), "utf8");
106920
+ renameSync31(tmp, path3);
106376
106921
  }
106377
106922
  function consumeBridgeDeadEscalationMarker(path3, nowMs3 = Date.now(), maxAgeMs = ESCALATION_MARKER_MAX_AGE_MS) {
106378
106923
  let marker = null;
106379
106924
  try {
106380
- const parsed = JSON.parse(readFileSync64(path3, "utf8"));
106925
+ const parsed = JSON.parse(readFileSync65(path3, "utf8"));
106381
106926
  if (typeof parsed.ts === "number" && Number.isFinite(parsed.ts) && typeof parsed.reason === "string") {
106382
106927
  const age = nowMs3 - parsed.ts;
106383
106928
  if (age >= 0 && age < maxAgeMs) {
@@ -106389,7 +106934,7 @@ function consumeBridgeDeadEscalationMarker(path3, nowMs3 = Date.now(), maxAgeMs
106389
106934
  }
106390
106935
  } catch {}
106391
106936
  try {
106392
- unlinkSync29(path3);
106937
+ unlinkSync31(path3);
106393
106938
  } catch {}
106394
106939
  return marker;
106395
106940
  }
@@ -106530,7 +107075,7 @@ function createBridgeDeadWatchdog(opts) {
106530
107075
  }
106531
107076
 
106532
107077
  // gateway/boot-probes.ts
106533
- import { readFileSync as readFileSync65, readdirSync as readdirSync16, existsSync as existsSync62 } from "fs";
107078
+ import { readFileSync as readFileSync66, readdirSync as readdirSync17, existsSync as existsSync63 } from "fs";
106534
107079
  init_quota_cache();
106535
107080
  init_generation_stamp();
106536
107081
  init_quota_check();
@@ -106538,8 +107083,8 @@ import { execFile as execFileCb2 } from "child_process";
106538
107083
  import { promisify as promisify2 } from "util";
106539
107084
  var execFile2 = promisify2(execFileCb2);
106540
107085
  var realProcFs2 = {
106541
- readdir: (p) => readdirSync16(p),
106542
- readFile: (p) => readFileSync65(p, "utf-8")
107086
+ readdir: (p) => readdirSync17(p),
107087
+ readFile: (p) => readFileSync66(p, "utf-8")
106543
107088
  };
106544
107089
  function findAgentProcessInContainer2(fs4 = realProcFs2) {
106545
107090
  let entries;
@@ -106840,7 +107385,7 @@ if (isGatewayMain) {
106840
107385
  shutdownAnalytics();
106841
107386
  });
106842
107387
  }
106843
- var STATE_DIR = process.env.TELEGRAM_STATE_DIR ?? join76(homedir22(), ".claude", "channels", "telegram");
107388
+ var STATE_DIR = process.env.TELEGRAM_STATE_DIR ?? join77(homedir22(), ".claude", "channels", "telegram");
106844
107389
  var permCardStore = createPermissionCardStore(STATE_DIR);
106845
107390
  var BLOCKED_APPROVALS_DIR = process.env.SWITCHROOM_BLOCKED_APPROVALS_DIR ?? "/state/blocked-approvals";
106846
107391
  var AGENT_NAME = process.env.SWITCHROOM_AGENT_NAME ?? "agent";
@@ -106879,7 +107424,7 @@ function alwaysAllowDrainDeps() {
106879
107424
  return {
106880
107425
  readConfigText: () => {
106881
107426
  const cfgPath = process.env.SWITCHROOM_CONFIG ?? SWITCHROOM_CONFIG ?? findConfigFile2();
106882
- return readFileSync66(cfgPath, "utf8");
107427
+ return readFileSync67(cfgPath, "utf8");
106883
107428
  },
106884
107429
  resolveAllowList: (_configText, agentName3) => {
106885
107430
  const cfg = loadConfig2();
@@ -106940,11 +107485,11 @@ function scheduleAlwaysAllowPersistDrain() {
106940
107485
  }, ALWAYS_ALLOW_DRAIN_INTERVAL_MS);
106941
107486
  timer3.unref?.();
106942
107487
  }
106943
- var ACCESS_FILE = join76(STATE_DIR, "access.json");
106944
- var APPROVED_DIR = join76(STATE_DIR, "approved");
106945
- var ENV_FILE = join76(STATE_DIR, ".env");
106946
- var INBOX_DIR = join76(STATE_DIR, "inbox");
106947
- var PEOPLE_FILE = join76(STATE_DIR, "people.json");
107488
+ var ACCESS_FILE = join77(STATE_DIR, "access.json");
107489
+ var APPROVED_DIR = join77(STATE_DIR, "approved");
107490
+ var ENV_FILE = join77(STATE_DIR, ".env");
107491
+ var INBOX_DIR = join77(STATE_DIR, "inbox");
107492
+ var PEOPLE_FILE = join77(STATE_DIR, "people.json");
106948
107493
  function triggerSelfRestart(targetAgent, reason, delayMs = 300) {
106949
107494
  const isDocker = process.env.SWITCHROOM_RUNTIME === "docker";
106950
107495
  const selfAgent = process.env.SWITCHROOM_AGENT_NAME;
@@ -107009,7 +107554,7 @@ function formatBootVersion() {
107009
107554
  }
107010
107555
  try {
107011
107556
  chmodSync14(ENV_FILE, 384);
107012
- for (const line of readFileSync66(ENV_FILE, "utf8").split(`
107557
+ for (const line of readFileSync67(ENV_FILE, "utf8").split(`
107013
107558
  `)) {
107014
107559
  const m = line.match(/^(\w+)=(.*)$/);
107015
107560
  if (m && process.env[m[1]] === undefined)
@@ -107030,7 +107575,7 @@ var bot;
107030
107575
  var lastGetUpdatesHeartbeatMs = Date.now();
107031
107576
  var GRAMMY_VERSION = (() => {
107032
107577
  try {
107033
- const raw = readFileSync66(new URL("../../node_modules/grammy/package.json", import.meta.url), "utf8");
107578
+ const raw = readFileSync67(new URL("../../node_modules/grammy/package.json", import.meta.url), "utf8");
107034
107579
  return JSON.parse(raw).version ?? "unknown";
107035
107580
  } catch {
107036
107581
  return "unknown";
@@ -107071,7 +107616,7 @@ function assertSendable(f) {
107071
107616
  } catch {
107072
107617
  throw new Error(`refusing to send file \u2014 cannot resolve real path: ${f}`);
107073
107618
  }
107074
- const inbox = join76(stateReal, "inbox");
107619
+ const inbox = join77(stateReal, "inbox");
107075
107620
  if (real.startsWith(stateReal + sep6) && !real.startsWith(inbox + sep6)) {
107076
107621
  throw new Error(`refusing to send channel state: ${f}`);
107077
107622
  }
@@ -107100,7 +107645,7 @@ var HISTORY_ENABLED = HISTORY_ACCESS.historyEnabled !== false;
107100
107645
  if (isGatewayMain && HISTORY_ENABLED) {
107101
107646
  try {
107102
107647
  initHistory(STATE_DIR, HISTORY_ACCESS.historyRetentionDays ?? 30);
107103
- process.stderr.write(`telegram gateway: history capture enabled at ${join76(STATE_DIR, "history.db")}
107648
+ process.stderr.write(`telegram gateway: history capture enabled at ${join77(STATE_DIR, "history.db")}
107104
107649
  `);
107105
107650
  } catch (err) {
107106
107651
  process.stderr.write(`telegram gateway: history init failed (${err.message}) \u2014 capture disabled
@@ -107111,174 +107656,179 @@ var turnsDb = null;
107111
107656
  var bootResumeInbound = null;
107112
107657
  var pendingRedelivery = null;
107113
107658
  var bridgeDeadPriorStreak = 0;
107114
- if (isGatewayMain)
107115
- try {
107116
- const agentDir = STATE_DIR.endsWith("/telegram") ? STATE_DIR.slice(0, -"/telegram".length) : STATE_DIR;
107117
- turnsDb = openTurnsDb(agentDir);
107118
- applySubagentsSchema(turnsDb);
107119
- let markerTurnKey = null;
107120
- let markerAgeMs = null;
107659
+ var bootResumeThrew = false;
107660
+ bootResumeInit:
107661
+ if (isGatewayMain)
107121
107662
  try {
107122
- const markerPath = join76(STATE_DIR, TURN_ACTIVE_MARKER_FILE2);
107123
- if (existsSync64(markerPath)) {
107124
- const st = statSync25(markerPath);
107125
- markerAgeMs = Date.now() - st.mtimeMs;
107126
- try {
107127
- const payload = JSON.parse(readFileSync66(markerPath, "utf8"));
107128
- if (typeof payload.turnKey === "string" && payload.turnKey.length > 0) {
107129
- markerTurnKey = payload.turnKey;
107130
- }
107131
- } catch {}
107132
- }
107133
- } catch {}
107134
- const hangSecs = Number(process.env.TURN_HANG_SECS);
107135
- const hangThresholdMs = (Number.isFinite(hangSecs) && hangSecs > 0 ? hangSecs : 300) * 1000;
107136
- const reasonSnapshot = markerAgeMs != null ? JSON.stringify({ idleMs: Math.round(markerAgeMs) }) : null;
107137
- const { reaped, timeoutTurnKey } = markOrphanedWithTimeoutClassification(turnsDb, {
107138
- markerTurnKey,
107139
- markerAgeMs,
107140
- hangThresholdMs,
107141
- reasonSnapshot
107142
- });
107143
- if (reaped > 0) {
107144
- process.stderr.write(`telegram gateway: turn-registry boot-reaper stamped ${reaped} orphaned turn(s)` + `${timeoutTurnKey ? ` (turnKey=${timeoutTurnKey} as 'timeout', markerAgeMs=${markerAgeMs})` : " as 'restart'"}
107145
- `);
107146
- } else {
107147
- process.stderr.write(`telegram gateway: turn-registry initialized at ${join76(agentDir, "telegram", "registry.db")}
107148
- `);
107149
- }
107150
- const bridgeDeadMarker = consumeBridgeDeadEscalationMarker(join76(STATE_DIR, "bridge-dead-escalation.json"));
107151
- if (bridgeDeadMarker != null) {
107152
- bridgeDeadPriorStreak = bridgeDeadMarker.count ?? 1;
107153
- process.stderr.write(`telegram gateway: boot: prior restart was a bridge-dead escalation (reason=${bridgeDeadMarker.reason}` + `, consecutive=${bridgeDeadPriorStreak}` + `${bridgeDeadMarker.crashTail ? `, crashTail=${bridgeDeadMarker.crashTail}` : ""})
107154
- `);
107155
- }
107156
- const bridgeDeadRestartCause = bridgeDeadMarker != null ? {
107157
- reason: bridgeDeadMarker.reason,
107158
- note: "The framework itself triggered this restart: your Telegram MCP bridge process had died " + "(chat tools were unavailable \u2014 you could not send replies), so the container was bounced " + "to restore the chat surface. This was NOT an operator-initiated restart and NOT a hang-watchdog kill."
107159
- } : undefined;
107160
- const pending2 = findLatestTurnIfInterrupted(turnsDb);
107161
- const selfAgent = process.env.SWITCHROOM_AGENT_NAME ?? "";
107162
- if (pending2 != null && selfAgent) {
107163
- const bootResumeMarkerPath = process.env.SWITCHROOM_GATEWAY_CLEAN_SHUTDOWN_MARKER ?? join76(STATE_DIR, "clean-shutdown.json");
107164
- const bootResumeCleanMarker = readCleanShutdownMarker(bootResumeMarkerPath);
107165
- const bootResumeForceAlways = process.env.SWITCHROOM_BOOT_RESUME_ALWAYS === "1";
107166
- const bootResumeMode = parseBootResumeMode(process.env.SWITCHROOM_BOOT_RESUME);
107167
- const bootResumeSuppressed = shouldSuppressBootResume(bootResumeCleanMarker, Date.now(), {
107168
- forceAlways: bootResumeForceAlways,
107169
- mode: bootResumeMode
107170
- });
107171
- const RESUME_MAX_AGE_MS = (() => {
107172
- const v = Number(process.env.SWITCHROOM_RESUME_MAX_AGE_MS);
107173
- return Number.isFinite(v) && v > 0 ? v : 10800000;
107174
- })();
107175
- const bootResumeKind = decideBootResumeKind({
107176
- pending: pending2,
107177
- suppressed: bootResumeSuppressed,
107178
- ageMs: Math.max(0, Date.now() - pending2.started_at),
107179
- maxAgeMs: RESUME_MAX_AGE_MS
107180
- });
107181
- const redeliverCapture = decideRedeliverCapture({
107182
- willBeResumed: bootResumeKind === "resume",
107183
- hasSessionId: Boolean(pending2.session_id)
107663
+ const agentDir = STATE_DIR.endsWith("/telegram") ? STATE_DIR.slice(0, -"/telegram".length) : STATE_DIR;
107664
+ turnsDb = openTurnsDb(agentDir);
107665
+ applySubagentsSchema(turnsDb);
107666
+ if (shouldSkipBootResumeForGatewayOnlyRespawn(STATE_DIR))
107667
+ break bootResumeInit;
107668
+ let markerTurnKey = null;
107669
+ let markerAgeMs = null;
107670
+ try {
107671
+ const markerPath = join77(STATE_DIR, TURN_ACTIVE_MARKER_FILE2);
107672
+ if (existsSync65(markerPath)) {
107673
+ const st = statSync25(markerPath);
107674
+ markerAgeMs = Date.now() - st.mtimeMs;
107675
+ try {
107676
+ const payload = JSON.parse(readFileSync67(markerPath, "utf8"));
107677
+ if (typeof payload.turnKey === "string" && payload.turnKey.length > 0) {
107678
+ markerTurnKey = payload.turnKey;
107679
+ }
107680
+ } catch {}
107681
+ }
107682
+ } catch {}
107683
+ const hangSecs = Number(process.env.TURN_HANG_SECS);
107684
+ const hangThresholdMs = (Number.isFinite(hangSecs) && hangSecs > 0 ? hangSecs : 300) * 1000;
107685
+ const reasonSnapshot = markerAgeMs != null ? JSON.stringify({ idleMs: Math.round(markerAgeMs) }) : null;
107686
+ const { reaped, timeoutTurnKey } = markOrphanedWithTimeoutClassification(turnsDb, {
107687
+ markerTurnKey,
107688
+ markerAgeMs,
107689
+ hangThresholdMs,
107690
+ reasonSnapshot
107184
107691
  });
107185
- if (redeliverCapture.capture) {
107186
- pendingRedelivery = { turn: pending2, maxAgeMs: RESUME_MAX_AGE_MS };
107187
- } else if (redeliverCapture.skipReason === "will-be-resumed") {
107188
- process.stderr.write(`telegram gateway: crash-redelivery suppressed \u2014 interrupted turnKey=${pending2.turn_key} will be ` + `RESUMED (bootResumeKind=resume); the fresh re-answer supersedes the recovered draft (no double-send)
107692
+ if (reaped > 0) {
107693
+ process.stderr.write(`telegram gateway: turn-registry boot-reaper stamped ${reaped} orphaned turn(s)` + `${timeoutTurnKey ? ` (turnKey=${timeoutTurnKey} as 'timeout', markerAgeMs=${markerAgeMs})` : " as 'restart'"}
107189
107694
  `);
107190
107695
  } else {
107191
- process.stderr.write(`telegram gateway: crash-redelivery skipped \u2014 interrupted turnKey=${pending2.turn_key} has no ` + `pinned session_id (pre-feature turn or no session event seen); cannot resolve exact transcript
107696
+ process.stderr.write(`telegram gateway: turn-registry initialized at ${join77(agentDir, "telegram", "registry.db")}
107192
107697
  `);
107193
107698
  }
107194
- let interruptedSubagents = [];
107195
- try {
107196
- interruptedSubagents = listNonTerminalSubagentsForTurn(turnsDb, pending2.turn_key).map((s) => ({ agentType: s.agent_type, description: s.description, status: s.status }));
107197
- } catch (err) {
107198
- process.stderr.write(`telegram gateway: boot-resume subagent lookup failed (${err.message}) \u2014 continuing without worker list
107699
+ const bridgeDeadMarker = consumeBridgeDeadEscalationMarker(join77(STATE_DIR, "bridge-dead-escalation.json"));
107700
+ if (bridgeDeadMarker != null) {
107701
+ bridgeDeadPriorStreak = bridgeDeadMarker.count ?? 1;
107702
+ process.stderr.write(`telegram gateway: boot: prior restart was a bridge-dead escalation (reason=${bridgeDeadMarker.reason}` + `, consecutive=${bridgeDeadPriorStreak}` + `${bridgeDeadMarker.crashTail ? `, crashTail=${bridgeDeadMarker.crashTail}` : ""})
107199
107703
  `);
107200
107704
  }
107201
- if (bootResumeKind === "resume") {
107202
- bootResumeInbound = {
107203
- agent: selfAgent,
107204
- msg: buildResumeInterruptedInbound({
107205
- turn: pending2,
107206
- subagents: interruptedSubagents,
107207
- restartCause: bridgeDeadRestartCause
107208
- })
107209
- };
107210
- } else if (bootResumeKind === "report") {
107211
- let idleMs = pending2.turn_key === timeoutTurnKey && markerAgeMs != null ? markerAgeMs : null;
107212
- if (idleMs == null && pending2.interrupt_reason) {
107213
- try {
107214
- const parsed = JSON.parse(pending2.interrupt_reason);
107215
- if (typeof parsed.idleMs === "number" && Number.isFinite(parsed.idleMs))
107216
- idleMs = parsed.idleMs;
107217
- } catch {}
107705
+ const bridgeDeadRestartCause = bridgeDeadMarker != null ? {
107706
+ reason: bridgeDeadMarker.reason,
107707
+ note: "The framework itself triggered this restart: your Telegram MCP bridge process had died " + "(chat tools were unavailable \u2014 you could not send replies), so the container was bounced " + "to restore the chat surface. This was NOT an operator-initiated restart and NOT a hang-watchdog kill."
107708
+ } : undefined;
107709
+ const pending2 = findLatestTurnIfInterrupted(turnsDb);
107710
+ const selfAgent = process.env.SWITCHROOM_AGENT_NAME ?? "";
107711
+ if (pending2 != null && selfAgent) {
107712
+ const bootResumeMarkerPath = process.env.SWITCHROOM_GATEWAY_CLEAN_SHUTDOWN_MARKER ?? join77(STATE_DIR, "clean-shutdown.json");
107713
+ const bootResumeCleanMarker = readCleanShutdownMarker(bootResumeMarkerPath);
107714
+ const bootResumeForceAlways = process.env.SWITCHROOM_BOOT_RESUME_ALWAYS === "1";
107715
+ const bootResumeMode = parseBootResumeMode(process.env.SWITCHROOM_BOOT_RESUME);
107716
+ const bootResumeSuppressed = shouldSuppressBootResume(bootResumeCleanMarker, Date.now(), {
107717
+ forceAlways: bootResumeForceAlways,
107718
+ mode: bootResumeMode
107719
+ });
107720
+ const RESUME_MAX_AGE_MS = (() => {
107721
+ const v = Number(process.env.SWITCHROOM_RESUME_MAX_AGE_MS);
107722
+ return Number.isFinite(v) && v > 0 ? v : 10800000;
107723
+ })();
107724
+ const bootResumeKind = decideBootResumeKind({
107725
+ pending: pending2,
107726
+ suppressed: bootResumeSuppressed,
107727
+ ageMs: Math.max(0, Date.now() - pending2.started_at),
107728
+ maxAgeMs: RESUME_MAX_AGE_MS
107729
+ });
107730
+ const redeliverCapture = decideRedeliverCapture({
107731
+ willBeResumed: bootResumeKind === "resume",
107732
+ hasSessionId: Boolean(pending2.session_id)
107733
+ });
107734
+ if (redeliverCapture.capture) {
107735
+ pendingRedelivery = { turn: pending2, maxAgeMs: RESUME_MAX_AGE_MS };
107736
+ } else if (redeliverCapture.skipReason === "will-be-resumed") {
107737
+ process.stderr.write(`telegram gateway: crash-redelivery suppressed \u2014 interrupted turnKey=${pending2.turn_key} will be ` + `RESUMED (bootResumeKind=resume); the fresh re-answer supersedes the recovered draft (no double-send)
107738
+ `);
107739
+ } else {
107740
+ process.stderr.write(`telegram gateway: crash-redelivery skipped \u2014 interrupted turnKey=${pending2.turn_key} has no ` + `pinned session_id (pre-feature turn or no session event seen); cannot resolve exact transcript
107741
+ `);
107218
107742
  }
107219
- if (idleMs == null)
107220
- idleMs = Math.max(0, Date.now() - pending2.started_at);
107221
- bootResumeInbound = {
107222
- agent: selfAgent,
107223
- msg: buildResumeWatchdogReportInbound({
107224
- turn: pending2,
107225
- idleMs,
107226
- subagents: interruptedSubagents,
107227
- restartCause: bridgeDeadRestartCause
107228
- })
107229
- };
107230
- } else if (bootResumeKind === "defer-loop" || bootResumeKind === "defer-suppressed") {
107231
- bootResumeInbound = {
107232
- agent: selfAgent,
107233
- msg: buildResumeDeferredReportInbound({
107234
- turn: pending2,
107235
- reason: bootResumeKind === "defer-loop" ? "loop-guard" : "clean-restart-suppressed",
107236
- subagents: interruptedSubagents,
107237
- restartCause: bridgeDeadRestartCause
107238
- })
107239
- };
107240
- }
107241
- if (bootResumeKind === "defer-suppressed") {
107242
- process.stderr.write(`telegram gateway: boot-resume suppressed (clean shutdown` + `${bootResumeCleanMarker?.reason ? ` reason=${JSON.stringify(bootResumeCleanMarker.reason)}` : ""}` + `, mode=${bootResumeMode}) \u2014 passive report delivered for turnKey=${pending2.turn_key}
107743
+ let interruptedSubagents = [];
107744
+ try {
107745
+ interruptedSubagents = listNonTerminalSubagentsForTurn(turnsDb, pending2.turn_key).map((s) => ({ agentType: s.agent_type, description: s.description, status: s.status }));
107746
+ } catch (err) {
107747
+ process.stderr.write(`telegram gateway: boot-resume subagent lookup failed (${err.message}) \u2014 continuing without worker list
107243
107748
  `);
107244
- } else if (bootResumeKind === "defer-loop") {
107245
- process.stderr.write(`telegram gateway: boot-resume loop-guard tripped (interrupted turn was itself a resume) ` + `\u2014 passive report delivered instead of re-resuming turnKey=${pending2.turn_key}
107749
+ }
107750
+ if (bootResumeKind === "resume") {
107751
+ bootResumeInbound = {
107752
+ agent: selfAgent,
107753
+ msg: buildResumeInterruptedInbound({
107754
+ turn: pending2,
107755
+ subagents: interruptedSubagents,
107756
+ restartCause: bridgeDeadRestartCause
107757
+ })
107758
+ };
107759
+ } else if (bootResumeKind === "report") {
107760
+ let idleMs = pending2.turn_key === timeoutTurnKey && markerAgeMs != null ? markerAgeMs : null;
107761
+ if (idleMs == null && pending2.interrupt_reason) {
107762
+ try {
107763
+ const parsed = JSON.parse(pending2.interrupt_reason);
107764
+ if (typeof parsed.idleMs === "number" && Number.isFinite(parsed.idleMs))
107765
+ idleMs = parsed.idleMs;
107766
+ } catch {}
107767
+ }
107768
+ if (idleMs == null)
107769
+ idleMs = Math.max(0, Date.now() - pending2.started_at);
107770
+ bootResumeInbound = {
107771
+ agent: selfAgent,
107772
+ msg: buildResumeWatchdogReportInbound({
107773
+ turn: pending2,
107774
+ idleMs,
107775
+ subagents: interruptedSubagents,
107776
+ restartCause: bridgeDeadRestartCause
107777
+ })
107778
+ };
107779
+ } else if (bootResumeKind === "defer-loop" || bootResumeKind === "defer-suppressed") {
107780
+ bootResumeInbound = {
107781
+ agent: selfAgent,
107782
+ msg: buildResumeDeferredReportInbound({
107783
+ turn: pending2,
107784
+ reason: bootResumeKind === "defer-loop" ? "loop-guard" : "clean-restart-suppressed",
107785
+ subagents: interruptedSubagents,
107786
+ restartCause: bridgeDeadRestartCause
107787
+ })
107788
+ };
107789
+ }
107790
+ if (bootResumeKind === "defer-suppressed") {
107791
+ process.stderr.write(`telegram gateway: boot-resume suppressed (clean shutdown` + `${bootResumeCleanMarker?.reason ? ` reason=${JSON.stringify(bootResumeCleanMarker.reason)}` : ""}` + `, mode=${bootResumeMode}) \u2014 passive report delivered for turnKey=${pending2.turn_key}
107246
107792
  `);
107247
- }
107248
- if (bootResumeInbound != null) {
107249
- process.stderr.write(`telegram gateway: boot-resume queued kind=${bootResumeKind} mode=${bootResumeMode} ` + `turnKey=${pending2.turn_key} endedVia=${pending2.ended_via ?? "open"} chat=${pending2.chat_id}
107793
+ } else if (bootResumeKind === "defer-loop") {
107794
+ process.stderr.write(`telegram gateway: boot-resume loop-guard tripped (interrupted turn was itself a resume) ` + `\u2014 passive report delivered instead of re-resuming turnKey=${pending2.turn_key}
107250
107795
  `);
107251
- }
107252
- }
107253
- if (bridgeDeadMarker != null && bootResumeInbound == null && selfAgent) {
107254
- const idleNoticeChat = (() => {
107255
- try {
107256
- return loadAccess().allowFrom[0] ?? null;
107257
- } catch {
107258
- return null;
107259
107796
  }
107260
- })();
107261
- if (idleNoticeChat != null) {
107262
- bootResumeInbound = {
107263
- agent: selfAgent,
107264
- msg: buildBridgeDeadIdleNoticeInbound({
107265
- chatId: String(idleNoticeChat),
107266
- marker: bridgeDeadMarker
107267
- })
107268
- };
107269
- process.stderr.write(`telegram gateway: boot: bridge-dead idle notice queued chat=${idleNoticeChat} (no turn was in flight)
107797
+ if (bootResumeInbound != null) {
107798
+ process.stderr.write(`telegram gateway: boot-resume queued kind=${bootResumeKind} mode=${bootResumeMode} ` + `turnKey=${pending2.turn_key} endedVia=${pending2.ended_via ?? "open"} chat=${pending2.chat_id}
107270
107799
  `);
107271
- } else {
107272
- process.stderr.write(`telegram gateway: boot: bridge-dead idle notice skipped \u2014 no allowFrom chat to surface it in
107800
+ }
107801
+ }
107802
+ if (bridgeDeadMarker != null && bootResumeInbound == null && selfAgent) {
107803
+ const idleNoticeChat = (() => {
107804
+ try {
107805
+ return loadAccess().allowFrom[0] ?? null;
107806
+ } catch {
107807
+ return null;
107808
+ }
107809
+ })();
107810
+ if (idleNoticeChat != null) {
107811
+ bootResumeInbound = {
107812
+ agent: selfAgent,
107813
+ msg: buildBridgeDeadIdleNoticeInbound({
107814
+ chatId: String(idleNoticeChat),
107815
+ marker: bridgeDeadMarker
107816
+ })
107817
+ };
107818
+ process.stderr.write(`telegram gateway: boot: bridge-dead idle notice queued chat=${idleNoticeChat} (no turn was in flight)
107273
107819
  `);
107820
+ } else {
107821
+ process.stderr.write(`telegram gateway: boot: bridge-dead idle notice skipped \u2014 no allowFrom chat to surface it in
107822
+ `);
107823
+ }
107274
107824
  }
107275
- }
107276
- writePendingTurnEnv(agentDir, pending2);
107277
- } catch (err) {
107278
- process.stderr.write(`telegram gateway: turn-registry init failed (${err.message}) \u2014 turn tracking disabled
107825
+ writePendingTurnEnv(agentDir, pending2);
107826
+ } catch (err) {
107827
+ process.stderr.write(`telegram gateway: turn-registry init failed (${err.message}) \u2014 turn tracking disabled
107279
107828
  `);
107280
- turnsDb = null;
107281
- }
107829
+ turnsDb = null;
107830
+ bootResumeThrew = true;
107831
+ }
107282
107832
  function resolveSubagentOriginChat(agentId) {
107283
107833
  if (turnsDb == null)
107284
107834
  return null;
@@ -107361,12 +107911,12 @@ if (isGatewayMain && !STATIC)
107361
107911
  function checkApprovals() {
107362
107912
  let files;
107363
107913
  try {
107364
- files = readdirSync18(APPROVED_DIR);
107914
+ files = readdirSync19(APPROVED_DIR);
107365
107915
  } catch {
107366
107916
  return;
107367
107917
  }
107368
107918
  for (const senderId of files) {
107369
- const file = join76(APPROVED_DIR, senderId);
107919
+ const file = join77(APPROVED_DIR, senderId);
107370
107920
  bot.api.sendMessage(senderId, "Paired! Say hi to Claude.").then(() => rmSync9(file, { force: true }), (err) => {
107371
107921
  process.stderr.write(`telegram gateway: failed to send approval confirm: ${err}
107372
107922
  `);
@@ -107544,15 +108094,15 @@ function noteAgentOutputAt(key, ts) {
107544
108094
  lastAgentOutputAt.delete(oldest);
107545
108095
  }
107546
108096
  }
107547
- var OBLIGATION_STORE_PATH = join76(STATE_DIR, "obligations.json");
108097
+ var OBLIGATION_STORE_PATH = join77(STATE_DIR, "obligations.json");
107548
108098
  var obligationStoreFs = {
107549
- readFileSync: (p) => readFileSync66(p, "utf8"),
108099
+ readFileSync: (p) => readFileSync67(p, "utf8"),
107550
108100
  writeFileSync: (p, d) => writeStateFileSync2(p, d),
107551
- renameSync: (a, b) => renameSync30(a, b),
107552
- existsSync: (p) => existsSync64(p),
108101
+ renameSync: (a, b) => renameSync32(a, b),
108102
+ existsSync: (p) => existsSync65(p),
107553
108103
  fsyncFileSync: fsyncPathSync,
107554
108104
  fsyncDirSync: fsyncPathSync,
107555
- unlinkSync: unlinkSync31
108105
+ unlinkSync: unlinkSync33
107556
108106
  };
107557
108107
  var obligationLedger = new ObligationLedger(OBLIGATION_REPRESENT_MAX, {
107558
108108
  onChange: STATIC || !OBLIGATION_LEDGER_ENABLED ? undefined : (snapshot) => persistObligations(OBLIGATION_STORE_PATH, obligationStoreFs, snapshot)
@@ -108311,7 +108861,7 @@ function emitTurnRecord(turn, endedAt) {
108311
108861
  return;
108312
108862
  }
108313
108863
  },
108314
- rename: (from, to) => renameSync30(from, to)
108864
+ rename: (from, to) => renameSync32(from, to)
108315
108865
  });
108316
108866
  appendFileSync11(turnsPath, rec);
108317
108867
  } catch {}
@@ -109882,28 +110432,28 @@ var PIN_STATUS_WHILE_WORKING = (() => {
109882
110432
  })();
109883
110433
  var statusPinClaims = new Map;
109884
110434
  var statusPinRightsCache = new PinRightsCache2;
109885
- var STATUS_PIN_STORE_PATH = join76(STATE_DIR, "status-pins.json");
110435
+ var STATUS_PIN_STORE_PATH = join77(STATE_DIR, "status-pins.json");
109886
110436
  var statusPinStoreFs = {
109887
- readFileSync: (p) => readFileSync66(p, "utf8"),
110437
+ readFileSync: (p) => readFileSync67(p, "utf8"),
109888
110438
  writeFileSync: (p, d) => writeStateFileSync2(p, d),
109889
- renameSync: (a, b) => renameSync30(a, b),
109890
- existsSync: (p) => existsSync64(p)
110439
+ renameSync: (a, b) => renameSync32(a, b),
110440
+ existsSync: (p) => existsSync65(p)
109891
110441
  };
109892
110442
  var statusPinPersistEnabled = !STATIC && PIN_STATUS_WHILE_WORKING;
109893
- var ACTIVITY_CARD_STORE_PATH = join76(STATE_DIR, "activity-cards-pending.json");
110443
+ var ACTIVITY_CARD_STORE_PATH = join77(STATE_DIR, "activity-cards-pending.json");
109894
110444
  var activityCardStoreFs = {
109895
- readFileSync: (p) => readFileSync66(p, "utf8"),
110445
+ readFileSync: (p) => readFileSync67(p, "utf8"),
109896
110446
  writeFileSync: (p, d) => writeStateFileSync2(p, d),
109897
- renameSync: (a, b) => renameSync30(a, b),
109898
- existsSync: (p) => existsSync64(p)
110447
+ renameSync: (a, b) => renameSync32(a, b),
110448
+ existsSync: (p) => existsSync65(p)
109899
110449
  };
109900
110450
  var activityCardPersistEnabled = !STATIC;
109901
- var QUEUED_CARD_STORE_PATH = join76(STATE_DIR, "queued-cards-pending.json");
110451
+ var QUEUED_CARD_STORE_PATH = join77(STATE_DIR, "queued-cards-pending.json");
109902
110452
  var queuedCardStoreFs = {
109903
- readFileSync: (p) => readFileSync66(p, "utf8"),
110453
+ readFileSync: (p) => readFileSync67(p, "utf8"),
109904
110454
  writeFileSync: (p, d) => writeStateFileSync2(p, d),
109905
- renameSync: (a, b) => renameSync30(a, b),
109906
- existsSync: (p) => existsSync64(p)
110455
+ renameSync: (a, b) => renameSync32(a, b),
110456
+ existsSync: (p) => existsSync65(p)
109907
110457
  };
109908
110458
  var queuedCardPersistEnabled = !STATIC;
109909
110459
  function persistQueuedCard(key, chatId, threadId, messageId) {
@@ -110186,11 +110736,11 @@ async function unpinAllStatusPins() {
110186
110736
  }
110187
110737
  }
110188
110738
  var stalePinSweepEligible = false;
110189
- var STALE_PIN_SWEEP_STORE_PATH = join76(STATE_DIR, "stale-pin-sweep.json");
110739
+ var STALE_PIN_SWEEP_STORE_PATH = join77(STATE_DIR, "stale-pin-sweep.json");
110190
110740
  var sweepStoreFs = {
110191
- readFileSync: (p) => readFileSync66(p, "utf-8"),
110741
+ readFileSync: (p) => readFileSync67(p, "utf-8"),
110192
110742
  writeFileSync: (p, data) => atomicWriteStateFileSync2(p, data, 384),
110193
- existsSync: (p) => existsSync64(p)
110743
+ existsSync: (p) => existsSync65(p)
110194
110744
  };
110195
110745
  var stalePinSweeper = createGatewayStalePinSweeper({
110196
110746
  telegram: { handle: () => lockedBot, call: robustApiCall },
@@ -110227,12 +110777,12 @@ var getPinnedProgressCardMessageId = null;
110227
110777
  var completeProgressCardTurn = null;
110228
110778
  var subagentWatcher = null;
110229
110779
  var workerActivityFeed = null;
110230
- var SOCKET_PATH = process.env.SWITCHROOM_GATEWAY_SOCKET ?? join76(STATE_DIR, "gateway.sock");
110780
+ var SOCKET_PATH = process.env.SWITCHROOM_GATEWAY_SOCKET ?? join77(STATE_DIR, "gateway.sock");
110231
110781
  if (isGatewayMain)
110232
110782
  mkdirStateSync2(STATE_DIR, { recursive: true, mode: 448 });
110233
- var GATEWAY_PID_PATH = process.env.SWITCHROOM_GATEWAY_PID_FILE ?? join76(STATE_DIR, "gateway.pid.json");
110234
- var GATEWAY_SESSION_MARKER_PATH = process.env.SWITCHROOM_GATEWAY_SESSION_MARKER ?? join76(STATE_DIR, "gateway-session.json");
110235
- var GATEWAY_CLEAN_SHUTDOWN_MARKER_PATH = process.env.SWITCHROOM_GATEWAY_CLEAN_SHUTDOWN_MARKER ?? join76(STATE_DIR, "clean-shutdown.json");
110783
+ var GATEWAY_PID_PATH = process.env.SWITCHROOM_GATEWAY_PID_FILE ?? join77(STATE_DIR, "gateway.pid.json");
110784
+ var GATEWAY_SESSION_MARKER_PATH = process.env.SWITCHROOM_GATEWAY_SESSION_MARKER ?? join77(STATE_DIR, "gateway-session.json");
110785
+ var GATEWAY_CLEAN_SHUTDOWN_MARKER_PATH = process.env.SWITCHROOM_GATEWAY_CLEAN_SHUTDOWN_MARKER ?? join77(STATE_DIR, "clean-shutdown.json");
110236
110786
  var GATEWAY_STARTED_AT_MS = Date.now();
110237
110787
  var BOOT_CARD_ENABLED = process.env.SWITCHROOM_BOOT_CARD !== "false";
110238
110788
  var activeBootCard = null;
@@ -110262,7 +110812,7 @@ function ensureIssuesCard(chatId, threadId) {
110262
110812
  bot: botApi,
110263
110813
  log: (msg) => process.stderr.write(`telegram gateway: ${msg}
110264
110814
  `),
110265
- persistPath: join76(stateDir, "issues-card.json")
110815
+ persistPath: join77(stateDir, "issues-card.json")
110266
110816
  });
110267
110817
  activeIssuesWatcher = startIssuesWatcher({
110268
110818
  stateDir,
@@ -110467,13 +111017,13 @@ if (isGatewayMain)
110467
111017
  var inboundSpool;
110468
111018
  if (isGatewayMain)
110469
111019
  inboundSpool = STATIC ? undefined : createInboundSpool({
110470
- path: join76(STATE_DIR, "inbound-spool.jsonl"),
111020
+ path: join77(STATE_DIR, "inbound-spool.jsonl"),
110471
111021
  fs: {
110472
111022
  appendFileSync: (p, d) => appendStateFileSync(p, d),
110473
- readFileSync: (p) => readFileSync66(p, "utf8"),
111023
+ readFileSync: (p) => readFileSync67(p, "utf8"),
110474
111024
  writeFileSync: (p, d) => writeStateFileSync2(p, d),
110475
- renameSync: (a, b) => renameSync30(a, b),
110476
- existsSync: (p) => existsSync64(p),
111025
+ renameSync: (a, b) => renameSync32(a, b),
111026
+ existsSync: (p) => existsSync65(p),
110477
111027
  statSizeSync: (p) => statSync25(p).size,
110478
111028
  fsyncFileSync: fsyncPathSync,
110479
111029
  fsyncDirSync: fsyncPathSync
@@ -110538,13 +111088,13 @@ async function maybeRedeliverUndeliveredAnswer() {
110538
111088
  let transcriptText;
110539
111089
  try {
110540
111090
  const projectsDir = getProjectsDirForCwd();
110541
- const path3 = join76(projectsDir, `${sessionId}.jsonl`);
110542
- if (!existsSync64(path3)) {
111091
+ const path3 = join77(projectsDir, `${sessionId}.jsonl`);
111092
+ if (!existsSync65(path3)) {
110543
111093
  process.stderr.write(`telegram gateway: crash-redelivery \u2014 transcript not found for turnKey=${turn.turn_key} session=${sessionId} (${path3}); skipping
110544
111094
  `);
110545
111095
  return;
110546
111096
  }
110547
- transcriptText = readFileSync66(path3, "utf8");
111097
+ transcriptText = readFileSync67(path3, "utf8");
110548
111098
  } catch (err) {
110549
111099
  process.stderr.write(`telegram gateway: crash-redelivery \u2014 transcript read failed turnKey=${turn.turn_key}: ${err.message}
110550
111100
  `);
@@ -110638,6 +111188,8 @@ if (isGatewayMain && bootResumeInbound != null) {
110638
111188
  }
110639
111189
  }
110640
111190
  }
111191
+ if (isGatewayMain && !bootResumeThrew)
111192
+ markBootResumeComplete(STATE_DIR);
110641
111193
  if (isGatewayMain && inboundSpool != null) {
110642
111194
  const replay = inboundSpool.liveEntries();
110643
111195
  for (const e of replay)
@@ -110711,8 +111263,8 @@ var bridgeDeadWatchdog = createBridgeDeadWatchdog({
110711
111263
  isSessionAlive: () => findAgentProcessInContainer2()?.comm === "claude",
110712
111264
  isShuttingDown: () => shuttingDown,
110713
111265
  escalate: (reason) => triggerSelfRestart(process.env.SWITCHROOM_AGENT_NAME ?? "", reason, 1500),
110714
- crashLogPath: join76(STATE_DIR, "bridge-crash.log"),
110715
- markerPath: join76(STATE_DIR, "bridge-dead-escalation.json"),
111266
+ crashLogPath: join77(STATE_DIR, "bridge-crash.log"),
111267
+ markerPath: join77(STATE_DIR, "bridge-dead-escalation.json"),
110716
111268
  log: (line) => process.stderr.write(`${line}
110717
111269
  `),
110718
111270
  priorStreak: bridgeDeadPriorStreak,
@@ -110819,8 +111371,8 @@ if (isGatewayMain)
110819
111371
  probeQuotaViaBroker: (t) => probeQuotaForBootCard(agentSlug, t),
110820
111372
  tmuxSupervisor: process.env.SWITCHROOM_TMUX_SUPERVISOR === "1",
110821
111373
  dockerMode: process.env.SWITCHROOM_RUNTIME === "docker",
110822
- configSnapshotPath: join76(resolvedAgentDirForCard, ".config-snapshot.json"),
110823
- bootCardStatePath: join76(resolvedAgentDirForCard, ".boot-card-msgid.json"),
111374
+ configSnapshotPath: join77(resolvedAgentDirForCard, ".config-snapshot.json"),
111375
+ bootCardStatePath: join77(resolvedAgentDirForCard, ".boot-card-msgid.json"),
110824
111376
  floodStatePath: FLOOD_STATE_PATH,
110825
111377
  ...updateOutcomeLine ? { updateOutcomeLine } : {}
110826
111378
  }, ackMsgId).then((handle) => {
@@ -110920,7 +111472,7 @@ if (isGatewayMain)
110920
111472
  if (turnsDb != null && msg.activeFile != null) {
110921
111473
  const stampKey = currentTurn?.registryKey ?? null;
110922
111474
  if (stampKey != null && stampKey !== lastSessionStampedTurnKey) {
110923
- const sessionId = basename18(msg.activeFile).replace(/\.jsonl$/, "");
111475
+ const sessionId = basename19(msg.activeFile).replace(/\.jsonl$/, "");
110924
111476
  if (sessionId) {
110925
111477
  try {
110926
111478
  stampTurnSessionId(turnsDb, stampKey, sessionId);
@@ -111418,10 +111970,10 @@ if (isGatewayMain)
111418
111970
  }
111419
111971
  },
111420
111972
  onPostSkillProposal(_client, msg) {
111421
- handlePostSkillProposal(msg, { bot, assertAllowedChat, swallowingApiCall });
111973
+ handlePostSkillProposal(msg, { bot, assertAllowedChat, swallowingApiCall, deliverResumeSyntheticOrBuffer });
111422
111974
  },
111423
111975
  onPostEvalCaseProposal(_client, msg) {
111424
- handlePostEvalCaseProposal(msg, { bot, assertAllowedChat, swallowingApiCall });
111976
+ handlePostEvalCaseProposal(msg, { bot, assertAllowedChat, swallowingApiCall, deliverResumeSyntheticOrBuffer });
111425
111977
  },
111426
111978
  onBuzzPublishResult: (_c, m) => getBuzzMirror()?.onPublishResult(m),
111427
111979
  log: (msg) => process.stderr.write(`telegram gateway: ipc \u2014 ${msg}
@@ -111454,7 +112006,7 @@ if (isGatewayMain)
111454
112006
  const receiverUid = receiverUidRaw ? Number(receiverUidRaw) : NaN;
111455
112007
  if (Number.isInteger(receiverUid))
111456
112008
  allowedUids.push(receiverUid);
111457
- const socketPath = join76(STATE_DIR, "webhook.sock");
112009
+ const socketPath = join77(STATE_DIR, "webhook.sock");
111458
112010
  const webhookInject = (agentName3, inbound) => {
111459
112011
  const msg = inbound;
111460
112012
  const delivered = ipcServer.sendToAgent(agentName3, msg);
@@ -111706,9 +112258,9 @@ function redactOutboundText(text5, site) {
111706
112258
  var VOICE_OUT_DEFAULT_CHUNK_CHARS = 600;
111707
112259
  var VOICE_OUT_HARD_CHUNK_CAP = 4096;
111708
112260
  var voiceOnDemandCache = new VoiceOnDemandCache({
111709
- persistPath: join76(STATE_DIR, "voice-ondemand.json")
112261
+ persistPath: join77(STATE_DIR, "voice-ondemand.json")
111710
112262
  });
111711
- var VOICE_CACHE_DIR = join76(STATE_DIR, "voice-cache");
112263
+ var VOICE_CACHE_DIR = join77(STATE_DIR, "voice-cache");
111712
112264
  var voicePreSynthQueue = new PreSynthQueue({
111713
112265
  runJob: async (job) => {
111714
112266
  const sidecarToken = await materializeSidecarToken2();
@@ -112116,11 +112668,11 @@ async function executeSendGif(rawArgs) {
112116
112668
  };
112117
112669
  }
112118
112670
  async function publishToTelegraph(text5, shortName, authorName) {
112119
- const accountPath = join76(STATE_DIR, "telegraph-account.json");
112671
+ const accountPath = join77(STATE_DIR, "telegraph-account.json");
112120
112672
  let account = null;
112121
112673
  try {
112122
- if (existsSync64(accountPath)) {
112123
- const raw = readFileSync66(accountPath, "utf-8");
112674
+ if (existsSync65(accountPath)) {
112675
+ const raw = readFileSync67(accountPath, "utf-8");
112124
112676
  const parsed = JSON.parse(raw);
112125
112677
  if (parsed.shortName && parsed.accessToken) {
112126
112678
  account = parsed;
@@ -112140,7 +112692,7 @@ async function publishToTelegraph(text5, shortName, authorName) {
112140
112692
  account = created.value;
112141
112693
  try {
112142
112694
  mkdirStateSync2(STATE_DIR, { recursive: true, mode: 448 });
112143
- writeFileSync53(accountPath, JSON.stringify(account, null, 2), { mode: 384 });
112695
+ writeFileSync55(accountPath, JSON.stringify(account, null, 2), { mode: 384 });
112144
112696
  } catch (err) {
112145
112697
  process.stderr.write(`telegram gateway: telegraph cache write failed: ${err.message}
112146
112698
  `);
@@ -112256,7 +112808,7 @@ _The secret was NOT saved. The agent can re-request with \`request_secret\`._`,
112256
112808
  }
112257
112809
  function readLiveSwitchroomConfigText() {
112258
112810
  const cfgPath = process.env.SWITCHROOM_CONFIG ?? findConfigFile2();
112259
- return readFileSync66(cfgPath, "utf8");
112811
+ return readFileSync67(cfgPath, "utf8");
112260
112812
  }
112261
112813
  async function executeReact(args) {
112262
112814
  if (!args.chat_id)
@@ -112297,7 +112849,7 @@ async function executeDownloadAttachment(args) {
112297
112849
  });
112298
112850
  mkdirSync50(INBOX_DIR, { recursive: true, mode: 448 });
112299
112851
  assertInsideInbox2(INBOX_DIR, dlPath);
112300
- writeFileSync53(dlPath, buf, { mode: 384 });
112852
+ writeFileSync55(dlPath, buf, { mode: 384 });
112301
112853
  return { content: [{ type: "text", text: dlPath }] };
112302
112854
  }
112303
112855
  async function executeEditMessage(args) {
@@ -113653,7 +114205,7 @@ function getMyAgentName() {
113653
114205
  const fromEnv = process.env.SWITCHROOM_AGENT_NAME;
113654
114206
  if (fromEnv && fromEnv.trim().length > 0)
113655
114207
  return fromEnv.trim();
113656
- return basename18(process.cwd());
114208
+ return basename19(process.cwd());
113657
114209
  }
113658
114210
  function isSelfTargetingCommand(name) {
113659
114211
  if (name === "all")
@@ -113666,14 +114218,14 @@ function restartMarkerPath() {
113666
114218
  const agentDir = resolveAgentDirFromEnv();
113667
114219
  if (!agentDir)
113668
114220
  return null;
113669
- return join76(agentDir, "restart-pending.json");
114221
+ return join77(agentDir, "restart-pending.json");
113670
114222
  }
113671
114223
  function writeRestartMarker(marker) {
113672
114224
  const p = restartMarkerPath();
113673
114225
  if (!p)
113674
114226
  return;
113675
114227
  try {
113676
- writeFileSync53(p, JSON.stringify(marker));
114228
+ writeFileSync55(p, JSON.stringify(marker));
113677
114229
  lastPlannedRestartAt = Date.now();
113678
114230
  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}
113679
114231
  `);
@@ -113692,7 +114244,7 @@ function readRestartMarker() {
113692
114244
  if (!p)
113693
114245
  return null;
113694
114246
  try {
113695
- return JSON.parse(readFileSync66(p, "utf8"));
114247
+ return JSON.parse(readFileSync67(p, "utf8"));
113696
114248
  } catch {
113697
114249
  return null;
113698
114250
  }
@@ -113841,7 +114393,7 @@ var _dockerReachable;
113841
114393
  function isDockerReachable() {
113842
114394
  if (_dockerReachable !== undefined)
113843
114395
  return _dockerReachable;
113844
- if (!existsSync64("/var/run/docker.sock")) {
114396
+ if (!existsSync65("/var/run/docker.sock")) {
113845
114397
  _dockerReachable = false;
113846
114398
  return _dockerReachable;
113847
114399
  }
@@ -113858,12 +114410,12 @@ function _resetDockerReachableCache() {
113858
114410
  }
113859
114411
  function spawnSwitchroomDetached(args, onFailure) {
113860
114412
  const fullArgs = SWITCHROOM_CONFIG ? ["--config", SWITCHROOM_CONFIG, ...args] : args;
113861
- const logPath = join76(STATE_DIR, "detached-spawn.log");
114413
+ const logPath = join77(STATE_DIR, "detached-spawn.log");
113862
114414
  let outFd = null;
113863
114415
  try {
113864
114416
  mkdirStateSync2(STATE_DIR, { recursive: true });
113865
114417
  outFd = openSync18(logPath, "a");
113866
- writeFileSync53(logPath, `
114418
+ writeFileSync55(logPath, `
113867
114419
  [${new Date().toISOString()}] spawn ${SWITCHROOM_CLI} ${fullArgs.join(" ")}
113868
114420
  `, { flag: "a" });
113869
114421
  } catch {}
@@ -113889,7 +114441,7 @@ function spawnSwitchroomDetached(args, onFailure) {
113889
114441
  return;
113890
114442
  let tail = "";
113891
114443
  try {
113892
- const full = readFileSync66(logPath, "utf8");
114444
+ const full = readFileSync67(logPath, "utf8");
113893
114445
  tail = full.split(`
113894
114446
  `).slice(-30).join(`
113895
114447
  `).trim();
@@ -114151,10 +114703,10 @@ ${preBlock(formatSwitchroomOutput(detail))}`, { html: true });
114151
114703
  }
114152
114704
  function readRecentDenialsForAgent(agentName3, windowMs, limit) {
114153
114705
  try {
114154
- const auditPath = join76(homedir22(), ".switchroom", "vault-audit.log");
114155
- if (!existsSync64(auditPath))
114706
+ const auditPath = join77(homedir22(), ".switchroom", "vault-audit.log");
114707
+ if (!existsSync65(auditPath))
114156
114708
  return [];
114157
- const raw = readFileSync66(auditPath, "utf8");
114709
+ const raw = readFileSync67(auditPath, "utf8");
114158
114710
  return recentDenialsFromAuditLog(raw, { agentName: agentName3, windowMs, limit });
114159
114711
  } catch {
114160
114712
  return [];
@@ -114205,7 +114757,7 @@ async function buildAgentMetadata(agentName3) {
114205
114757
  try {
114206
114758
  const agentDir = resolveAgentDirFromEnv();
114207
114759
  if (agentDir) {
114208
- const raw = readFileSync66(join76(agentDir, ".claude", ".claude.json"), "utf8");
114760
+ const raw = readFileSync67(join77(agentDir, ".claude", ".claude.json"), "utf8");
114209
114761
  claudeJson = JSON.parse(raw);
114210
114762
  }
114211
114763
  } catch {}
@@ -114334,7 +114886,7 @@ function buildModelDeps(restartCtx) {
114334
114886
  try {
114335
114887
  const agentDir = resolveAgentDirFromEnv();
114336
114888
  if (agentDir) {
114337
- const local = await fetchQuota2({ claudeConfigDir: join76(agentDir, ".claude") });
114889
+ const local = await fetchQuota2({ claudeConfigDir: join77(agentDir, ".claude") });
114338
114890
  if (local.ok)
114339
114891
  return formatQuotaLine2(local.data);
114340
114892
  }
@@ -114581,10 +115133,10 @@ function effortMenuReplyMarkup(reply) {
114581
115133
  function flushAgentHandoff(agentDir) {
114582
115134
  let removed = 0;
114583
115135
  for (const fname of [".handoff.md", ".handoff-topic"]) {
114584
- const p = join76(agentDir, fname);
115136
+ const p = join77(agentDir, fname);
114585
115137
  try {
114586
- if (existsSync64(p)) {
114587
- unlinkSync31(p);
115138
+ if (existsSync65(p)) {
115139
+ unlinkSync33(p);
114588
115140
  removed++;
114589
115141
  }
114590
115142
  } catch (err) {
@@ -114639,7 +115191,7 @@ async function handleNewCommand(ctx) {
114639
115191
  writeRestartMarker({ chat_id: chatId, thread_id: threadId ?? null, ack_message_id: ackId, ts: Date.now() });
114640
115192
  if (agentDir != null) {
114641
115193
  try {
114642
- writeFileSync53(join76(agentDir, ".force-fresh-session"), `${kind} at ${new Date().toISOString()}
115194
+ writeFileSync55(join77(agentDir, ".force-fresh-session"), `${kind} at ${new Date().toISOString()}
114643
115195
  `, "utf8");
114644
115196
  } catch (err) {
114645
115197
  process.stderr.write(`telegram gateway: failed to write force-fresh marker: ${err}
@@ -114752,16 +115304,16 @@ function buildFolderPickerDeps() {
114752
115304
  };
114753
115305
  }
114754
115306
  var lockoutOps = {
114755
- readFileSync: (p, enc) => readFileSync66(p, enc),
115307
+ readFileSync: (p, enc) => readFileSync67(p, enc),
114756
115308
  writeFileSync: (p, data, opts) => writeStateFileSync2(p, data, opts),
114757
- existsSync: (p) => existsSync64(p),
115309
+ existsSync: (p) => existsSync65(p),
114758
115310
  mkdirSync: (p, opts) => mkdirStateSync2(p, opts),
114759
- joinPath: (...parts) => join76(...parts)
115311
+ joinPath: (...parts) => join77(...parts)
114760
115312
  };
114761
115313
  var FLEET_FALLBACK_DEDUP_MS = 30000;
114762
115314
  function isAuthBrokerSocketReachable() {
114763
115315
  try {
114764
- return existsSync64(resolveAuthBrokerSocketPath2());
115316
+ return existsSync65(resolveAuthBrokerSocketPath2());
114765
115317
  } catch {
114766
115318
  return false;
114767
115319
  }
@@ -115017,7 +115569,7 @@ async function runCreditWatch() {
115017
115569
  if (!agentDir)
115018
115570
  return;
115019
115571
  const agentName3 = getMyAgentName();
115020
- const claudeConfigDir = join76(agentDir, ".claude");
115572
+ const claudeConfigDir = join77(agentDir, ".claude");
115021
115573
  const stateDir = STATE_DIR;
115022
115574
  const reason = readClaudeJsonOverage(claudeConfigDir);
115023
115575
  const prev = loadCreditState(stateDir);
@@ -116787,7 +117339,7 @@ Send \`/auth ${parsed.provider} cancel\` to abort.`, { html: true });
116787
117339
  await switchroomReply(ctx, "**/usage:** cannot resolve agent dir.", { html: true });
116788
117340
  return;
116789
117341
  }
116790
- const result = await fetchQuota2({ claudeConfigDir: join76(agentDir, ".claude") });
117342
+ const result = await fetchQuota2({ claudeConfigDir: join77(agentDir, ".claude") });
116791
117343
  if (!result.ok) {
116792
117344
  await switchroomReply(ctx, `**/usage:** ${escapeHtmlForTg2(result.reason)}`, { html: true });
116793
117345
  return;
@@ -117178,7 +117730,7 @@ ${interimLabel}` : interimLabel
117178
117730
  const unifiedDiff = (() => {
117179
117731
  try {
117180
117732
  const cfgPath = process.env.SWITCHROOM_CONFIG ?? SWITCHROOM_CONFIG ?? findConfigFile2();
117181
- const raw = readFileSync66(cfgPath, "utf8");
117733
+ const raw = readFileSync67(cfgPath, "utf8");
117182
117734
  return synthesizeAllowRuleDiff({ agentName: agentName3, rule: chosen.rule, configText: raw });
117183
117735
  } catch (err) {
117184
117736
  process.stderr.write(`telegram gateway: always-allow diff synth failed: ${err.message}
@@ -118053,7 +118605,7 @@ async function startGateway() {
118053
118605
  return;
118054
118606
  }
118055
118607
  })();
118056
- const resolvedAgentDirForBootCard = agentDir ?? join76(homedir22(), ".switchroom", "agents", agentSlug);
118608
+ const resolvedAgentDirForBootCard = agentDir ?? join77(homedir22(), ".switchroom", "agents", agentSlug);
118057
118609
  const handle = await startBootCard(chatId, threadId, botApiForCard, {
118058
118610
  agentName: agentDisplayName,
118059
118611
  agentSlug,
@@ -118067,8 +118619,8 @@ async function startGateway() {
118067
118619
  probeQuotaViaBroker: (t) => probeQuotaForBootCard(agentSlug, t),
118068
118620
  tmuxSupervisor: process.env.SWITCHROOM_TMUX_SUPERVISOR === "1",
118069
118621
  dockerMode: process.env.SWITCHROOM_RUNTIME === "docker",
118070
- configSnapshotPath: join76(resolvedAgentDirForBootCard, ".config-snapshot.json"),
118071
- bootCardStatePath: join76(resolvedAgentDirForBootCard, ".boot-card-msgid.json"),
118622
+ configSnapshotPath: join77(resolvedAgentDirForBootCard, ".config-snapshot.json"),
118623
+ bootCardStatePath: join77(resolvedAgentDirForBootCard, ".boot-card-msgid.json"),
118072
118624
  floodStatePath: FLOOD_STATE_PATH,
118073
118625
  ...updateOutcomeLine ? { updateOutcomeLine } : {}
118074
118626
  }, ackMsgId);
@@ -118101,7 +118653,7 @@ async function startGateway() {
118101
118653
  if (smAgentDir) {
118102
118654
  const resolutionTimeoutMs = resolveSessionModelResolutionTimeoutMs(process.env.SWITCHROOM_SESSION_MODEL_RESOLUTION_TIMEOUT_MS);
118103
118655
  const resolved = await waitForSessionModelResolution({
118104
- barrierExists: () => existsSync64(join76(smAgentDir, ".session-model-resolved")),
118656
+ barrierExists: () => existsSync65(join77(smAgentDir, ".session-model-resolved")),
118105
118657
  timeoutMs: resolutionTimeoutMs
118106
118658
  });
118107
118659
  if (!resolved) {
@@ -118109,10 +118661,10 @@ async function startGateway() {
118109
118661
  process.stderr.write(`telegram gateway: gw /model relaunch UNRESOLVED agent=${getMyAgentName()} target=${target} (barrier timeout after ${resolutionTimeoutMs}ms)
118110
118662
  `);
118111
118663
  } else {
118112
- const activePath = join76(smAgentDir, ".active-session-model");
118113
- if (existsSync64(activePath)) {
118664
+ const activePath = join77(smAgentDir, ".active-session-model");
118665
+ if (existsSync65(activePath)) {
118114
118666
  try {
118115
- const launched = readFileSync66(activePath, "utf8").trim();
118667
+ const launched = readFileSync67(activePath, "utf8").trim();
118116
118668
  const configured = (() => {
118117
118669
  const d = switchroomExecJson(["agent", "list"]);
118118
118670
  const raw = d?.agents?.find((a) => a.name === getMyAgentName())?.model ?? null;
@@ -118145,29 +118697,29 @@ async function startGateway() {
118145
118697
  deliverModelSwitchBootNotice({
118146
118698
  ...modelBootCardDeps,
118147
118699
  confirmation,
118148
- hasSessionModelAlert: existsSync64(join76(smAgentDir, ".session-model-alert"))
118700
+ hasSessionModelAlert: existsSync65(join77(smAgentDir, ".session-model-alert"))
118149
118701
  });
118150
118702
  }
118151
118703
  } catch {}
118152
118704
  }
118153
- const activeEffortPath = join76(smAgentDir, ".active-session-effort");
118154
- if (existsSync64(activeEffortPath)) {
118705
+ const activeEffortPath = join77(smAgentDir, ".active-session-effort");
118706
+ if (existsSync65(activeEffortPath)) {
118155
118707
  try {
118156
- const launchedEffort = readFileSync66(activeEffortPath, "utf8").trim();
118708
+ const launchedEffort = readFileSync67(activeEffortPath, "utf8").trim();
118157
118709
  const configuredEffort = getConfiguredEffortForPersist();
118158
118710
  sessionEffortOverride = launchedEffort.length > 0 && launchedEffort !== configuredEffort ? launchedEffort : null;
118159
118711
  } catch {}
118160
118712
  }
118161
- const alertPath = join76(smAgentDir, ".session-model-alert");
118162
- if (existsSync64(alertPath)) {
118713
+ const alertPath = join77(smAgentDir, ".session-model-alert");
118714
+ if (existsSync65(alertPath)) {
118163
118715
  let alertText = null;
118164
118716
  try {
118165
- alertText = readFileSync66(alertPath, "utf8").trim();
118717
+ alertText = readFileSync67(alertPath, "utf8").trim();
118166
118718
  } catch {
118167
118719
  alertText = null;
118168
118720
  }
118169
118721
  try {
118170
- unlinkSync31(alertPath);
118722
+ unlinkSync33(alertPath);
118171
118723
  } catch {}
118172
118724
  if (alertText && alertText.length > 0) {
118173
118725
  const operators = loadAccess().allowFrom;