switchroom 0.19.17 → 0.19.18

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 (66) hide show
  1. package/bin/run-hook.sh +148 -0
  2. package/bin/workspace-dynamic-hook.sh +147 -38
  3. package/dist/agent-scheduler/index.js +11 -3
  4. package/dist/auth-broker/index.js +29 -4
  5. package/dist/cli/notion-write-pretool.mjs +11 -3
  6. package/dist/cli/switchroom.js +8306 -7619
  7. package/dist/host-control/main.js +626 -36
  8. package/dist/vault/approvals/kernel-server.js +30 -5
  9. package/dist/vault/broker/server.js +71 -18
  10. package/package.json +3 -2
  11. package/profiles/_base/start.sh.hbs +8 -4
  12. package/profiles/coding/CLAUDE.md.hbs +1 -1
  13. package/profiles/default/CLAUDE.md.hbs +3 -3
  14. package/profiles/executive-assistant/CLAUDE.md.hbs +1 -1
  15. package/profiles/health-coach/CLAUDE.md.hbs +1 -1
  16. package/skills/mental-model-curator/SKILL.md +8 -6
  17. package/telegram-plugin/bridge/bridge.ts +11 -19
  18. package/telegram-plugin/bridge/mcp-instructions.ts +87 -0
  19. package/telegram-plugin/dist/bridge/bridge.js +15 -20
  20. package/telegram-plugin/dist/gateway/gateway.js +763 -373
  21. package/telegram-plugin/dist/server.js +19 -20
  22. package/telegram-plugin/gateway/boot-card.ts +5 -1
  23. package/telegram-plugin/gateway/boot-probes.ts +113 -0
  24. package/telegram-plugin/gateway/config-approval-handler.test.ts +54 -0
  25. package/telegram-plugin/gateway/config-approval-handler.ts +16 -1
  26. package/telegram-plugin/gateway/disconnect-flush.ts +17 -0
  27. package/telegram-plugin/gateway/gateway.ts +43 -1
  28. package/telegram-plugin/gateway/handback-preturn-signal.ts +61 -7
  29. package/telegram-plugin/gateway/ipc-protocol.ts +5 -0
  30. package/telegram-plugin/gateway/ipc-server.ts +13 -0
  31. package/telegram-plugin/gateway/liveness-wiring.ts +125 -5
  32. package/telegram-plugin/gateway/obligation-ledger.ts +84 -4
  33. package/telegram-plugin/gateway/resume-inbound-builder.ts +13 -4
  34. package/telegram-plugin/gateway/stream-render.ts +24 -5
  35. package/telegram-plugin/hooks/secret-guard-pretool.mjs +249 -76
  36. package/telegram-plugin/registry/turns-schema.test.ts +8 -3
  37. package/telegram-plugin/registry/turns-schema.ts +40 -12
  38. package/telegram-plugin/runtime-metrics.ts +14 -0
  39. package/telegram-plugin/silence-poke.ts +138 -0
  40. package/telegram-plugin/tests/boot-probe-drift.test.ts +152 -0
  41. package/telegram-plugin/tests/gateway-disconnect-flush.test.ts +32 -0
  42. package/telegram-plugin/tests/handback-preturn-signal.test.ts +62 -0
  43. package/telegram-plugin/tests/helpers/liveness-wiring-fixture.ts +178 -0
  44. package/telegram-plugin/tests/ipc-server-validate-config-approval.test.ts +95 -0
  45. package/telegram-plugin/tests/mcp-instructions-budget.test.ts +184 -0
  46. package/telegram-plugin/tests/multitopic-routing-wiring.test.ts +22 -2
  47. package/telegram-plugin/tests/obligation-determinism.test.ts +114 -3
  48. package/telegram-plugin/tests/obligation-ledger.test.ts +310 -0
  49. package/telegram-plugin/tests/registry-turns.test.ts +13 -0
  50. package/telegram-plugin/tests/resume-inbound-builder.test.ts +15 -0
  51. package/telegram-plugin/tests/secret-guard-pretool.test.ts +347 -16
  52. package/telegram-plugin/tests/silence-poke-orphan-reap.test.ts +392 -0
  53. package/telegram-plugin/tests/silence-poke-teardown-notice.test.ts +301 -0
  54. package/telegram-plugin/tests/stream-render-golden.test.ts +103 -1
  55. package/telegram-plugin/tests/tts-normalize.test.ts +43 -0
  56. package/telegram-plugin/tests/voice-normalize-text.test.ts +212 -3
  57. package/telegram-plugin/tts-normalize.ts +6 -4
  58. package/telegram-plugin/voice-normalize-text.ts +168 -11
  59. package/vendor/hindsight-memory/CHANGELOG.md +73 -0
  60. package/vendor/hindsight-memory/scripts/lib/config.py +8 -3
  61. package/vendor/hindsight-memory/scripts/lib/directives.py +62 -4
  62. package/vendor/hindsight-memory/scripts/recall.py +257 -12
  63. package/vendor/hindsight-memory/scripts/retain.py +12 -6
  64. package/vendor/hindsight-memory/scripts/tests/test_directives.py +80 -9
  65. package/vendor/hindsight-memory/scripts/tests/test_recall_integration.py +362 -18
  66. package/vendor/hindsight-memory/settings.json +1 -1
@@ -20663,7 +20663,7 @@ var require_dist = __commonJS((exports) => {
20663
20663
  });
20664
20664
 
20665
20665
  // ../src/config/schema.ts
20666
- var CodeRepoEntrySchema, AgentBindMountSchema, HttpDiffPollSchema, PollSpecSchema, TelegramMessageActionSchema, WebhookActionSchema, ActionSpecSchema, ScheduleEntrySchema, AgentSoulSchema, AgentToolsSchema, AgentMemorySchema, HookEntrySchema, AgentHooksSchema, SubagentSchema, SessionSchema, SessionContinuitySchema, webhookDispatchRule, TelegramChannelSchema, ChannelsSchema, TIMEZONE_REGEX, ApproverIdSchema, GoogleWorkspaceTierSchema, GoogleWorkspaceConfigSchema, LiteLLMConfigSchema, HindsightPerOpLlmSchema, HindsightConfigSchema, MicrosoftWorkspaceConfigSchema, NotionWorkspaceConfigSchema, AgentGoogleWorkspaceConfigSchema, MicrosoftAccountEmailSchema, MicrosoftToolTokenSchema, MicrosoftAccountBindingSchema, AgentMicrosoftWorkspaceConfigSchema, AgentNotionWorkspaceConfigSchema, ReactionsSchema, ReactionDispatchSchema, ReleaseBlock, NetworkIsolationSchema, servesField, knowsField, profileFields, ProfileSchema, _omitExtends, defaultsFields, AgentDefaultsSchema, AgentSchema, TelegramConfigSchema, MemoryBackendConfigSchema, VaultConfigSchema, QuotaConfigSchema, AutoReleaseCheckSchema, HostControlConfigSchema, WebServiceConfigSchema, FleetHealthConfigSchema, HostdConfigSchema, CronEgressSchema, CronConfigSchema, UserSchema, SwitchroomConfigSchema;
20666
+ var CodeRepoEntrySchema, AgentBindMountSchema, HttpDiffPollSchema, PollSpecSchema, TelegramMessageActionSchema, WebhookActionSchema, ActionSpecSchema, ScheduleEntrySchema, AgentSoulSchema, AgentToolsSchema, AgentMemorySchema, HookEntrySchema, AgentHooksSchema, SubagentSchema, SessionSchema, SessionContinuitySchema, webhookDispatchRule, TelegramChannelSchema, ChannelsSchema, TIMEZONE_REGEX, ApproverIdSchema, GoogleWorkspaceTierSchema, GoogleWorkspaceConfigSchema, LiteLLMConfigSchema, HindsightPerOpLlmSchema, HindsightConfigSchema, MicrosoftWorkspaceConfigSchema, NotionWorkspaceConfigSchema, AgentGoogleWorkspaceConfigSchema, MicrosoftAccountEmailSchema, MicrosoftToolTokenSchema, MicrosoftAccountBindingSchema, AgentMicrosoftWorkspaceConfigSchema, AgentNotionWorkspaceConfigSchema, ReactionsSchema, ReactionDispatchSchema, releaseBlockFields, ReleaseBlock, RootReleaseBlock, NetworkIsolationSchema, servesField, knowsField, profileFields, ProfileSchema, _omitExtends, defaultsFields, AgentDefaultsSchema, AgentSchema, TelegramConfigSchema, MemoryBackendConfigSchema, VaultConfigSchema, QuotaConfigSchema, AutoReleaseCheckSchema, HostControlConfigSchema, WebServiceConfigSchema, FleetHealthConfigSchema, HostdConfigSchema, CronEgressSchema, CronConfigSchema, UserSchema, SwitchroomConfigSchema;
20667
20667
  var init_schema = __esm(() => {
20668
20668
  init_zod();
20669
20669
  CodeRepoEntrySchema = exports_external.object({
@@ -20814,7 +20814,7 @@ var init_schema = __esm(() => {
20814
20814
  recall: exports_external.object({
20815
20815
  max_memories: exports_external.number().int().min(0).optional().describe("Cap on the number of memories injected into the prompt by " + "auto-recall, regardless of token budget. Plugin default is 12. " + "0 disables the cap (all memories Hindsight returns are injected)."),
20816
20816
  cache_ttl_secs: exports_external.number().int().min(0).optional().describe("Per-session recall cache TTL in seconds. When > 0, identical " + "(prompt, bank) within the same session reuse the cached recall " + "result instead of round-tripping to Hindsight. 0 disables. " + "Default is 600 (10 min) for switchroom-managed agents."),
20817
- min_overlap: exports_external.number().min(0).max(1).optional().describe("Minimum Jaccard token overlap [0.0\u20131.0] between the user " + "prompt and a memory's text for the memory to be injected. " + "Drops low-relevance matches before the count cap so weak hits " + "don't fill the slot on real queries. 0.0 disables (default \u2014 " + "current behaviour). Try 0.10\u20130.20 to start; observe the " + "`overlap_dropped` field via `switchroom memory recall-log`."),
20817
+ min_overlap: exports_external.number().min(0).max(1).optional().describe("Minimum containment token overlap [0.0\u20131.0] between the user " + "prompt and a memory's text for the memory to be injected. " + "A cheap FLOOR that removes candidates with (near-)zero lexical " + "relationship to the prompt \u2014 NOT a precision control: at the " + "recommended 0.10 the gate is near-passthrough, and the " + "effective precision control is the engine rerank plus the " + "max_memories head-slice. 0.0 disables (default \u2014 current " + "behaviour). Use 0.10; values at or above 0.20 measurably " + "starve recall \u2014 on production replay 0.20 leaves ~41.9% of " + "turns with NO memories at all, re-creating the bug #3541 " + "fixed. Observe the `overlap_dropped` field via " + "`switchroom memory recall-log`."),
20818
20818
  types: exports_external.array(exports_external.string()).optional().describe("Hindsight fact types to recall. Switchroom default is " + '["world", "experience", "observation"] \u2014 the synthesized ' + "`observation` tier is on by default. Set to " + '["world", "experience"] to opt out of observation-backed ' + "recall for this agent (or fleet-wide under defaults)."),
20819
20819
  additional_banks: exports_external.array(exports_external.string()).optional().describe("Extra Hindsight banks to recall from on every turn, merged into " + "the agent's own bank results \u2014 e.g. a shared operator/household " + "profile bank authored via `switchroom memory profile`. Each is " + "recalled with an 8s timeout and is non-fatal on failure. Stays " + "within the single tenant: all banks are the operator's data, in " + "the operator's Hindsight instance (see the `single-tenant` " + "invariant). Defaults to [] (no extra banks)."),
20820
20820
  sender_banks: exports_external.record(exports_external.string(), exports_external.string()).optional().describe("Per-speaker recall routing: a map of Telegram sender \u2192 extra " + "recall bank. When a message arrives, the agent also recalls the " + "speaker's bank (matched by Telegram username \u2014 a leading @ is " + "optional \u2014 or numeric user_id), merged " + "into its own results \u2014 so each trusted user gets their own " + "profile context. Additive recall scoping within the single " + "tenant: never an access boundary (who may drive an agent stays " + "the per-agent user assignment in `access.allowFrom`). Author the " + "banks via `switchroom memory profile`."),
@@ -21126,9 +21126,16 @@ var init_schema = __esm(() => {
21126
21126
  enabled: exports_external.boolean().optional().describe("Master switch for the reaction-dispatch path. Default false \u2014 " + "with no reaction_dispatch block, reactions are persisted (and may " + "feed the `reactions` feedback path) but are NEVER dispatched as " + "event-driven inbound turns."),
21127
21127
  emojis: exports_external.array(exports_external.string()).optional().describe('Emoji allowlist that triggers a `<channel event="reaction">` ' + "inbound turn when reacted to any message. Default [] (nothing " + "fires). Cascade mode: REPLACE (not union) \u2014 a layer's list " + "replaces lower layers entirely so an operator can narrow per-agent.")
21128
21128
  }).optional();
21129
- ReleaseBlock = exports_external.object({
21129
+ releaseBlockFields = {
21130
21130
  channel: exports_external.enum(["dev", "rc", "latest"]).optional(),
21131
21131
  pin: exports_external.string().regex(/^(sha-[0-9a-f]{7,40}|v\d+\.\d+\.\d+)$/).optional()
21132
+ };
21133
+ ReleaseBlock = exports_external.object(releaseBlockFields).strict().refine((r) => !(r.channel && r.pin), {
21134
+ message: "release.channel and release.pin are mutually exclusive"
21135
+ });
21136
+ RootReleaseBlock = exports_external.object({
21137
+ ...releaseBlockFields,
21138
+ auto_update: exports_external.boolean().optional().describe("Opt-in unattended fleet auto-update (KEN-131). When true, hostd's " + "release watcher polls the published release version and, on a new " + "release, drives the EXISTING staggered canary rollout " + "(`switchroom rollout --pin vX.Y.Z`) unattended: canary-first, " + "per-agent version assert, durable pin persisted only after the " + "canary is green, abort + operator alert card on canary failure, " + "and compose rollback on a failed apply. Default false \u2014 with it " + "unset/false, behaviour is unchanged.")
21132
21139
  }).strict().refine((r) => !(r.channel && r.pin), {
21133
21140
  message: "release.channel and release.pin are mutually exclusive"
21134
21141
  });
@@ -21372,6 +21379,7 @@ var init_schema = __esm(() => {
21372
21379
  enabled: exports_external.boolean().default(false).describe("When true, hostd polls the remote release tag every " + "`interval_minutes` and applies + restarts the fleet when a new " + "release is detected. Default false \u2014 opt-in."),
21373
21380
  interval_minutes: exports_external.number().int().min(5).max(1440).default(5).describe("Poll interval in minutes. Floor of 5m matches the agent-config " + "cron rate limit; ceiling of 1440m (24h) is a sanity cap."),
21374
21381
  apply_on_detect: exports_external.boolean().default(true).describe("When false, hostd logs `release_detected` but does NOT call " + "update_apply / restart all. Useful for dogfooding the detector " + "without rolling the fleet."),
21382
+ notify_on_detect: exports_external.boolean().default(false).describe("KEN-129 \u2014 operator-in-the-loop update prompt. Only consulted " + "when apply_on_detect is false (auto-apply supersedes notify): " + "a newly detected release posts ONE operator approval card " + "('fleet is behind \u2014 tap to apply') via an admin agent's " + "gateway; Approve runs hostd's update_apply path (fleet-" + "mutation-locked, durable status rows, get_status-pollable). " + "Dedup on release id: the last-notified id persists in " + "~/.switchroom/release-notify-state.json, so a card that " + "reached the operator is never re-posted for the same release."),
21375
21383
  image_ref: exports_external.string().default("ghcr.io/switchroom/switchroom-agent:latest").describe("Image reference whose remote digest is compared to the local " + "image digest. Defaults to the agent image's :latest tag, which " + "is the canonical signal that a release has been promoted.")
21376
21384
  });
21377
21385
  HostControlConfigSchema = exports_external.object({
@@ -21414,7 +21422,7 @@ var init_schema = __esm(() => {
21414
21422
  timezone: exports_external.string().regex(TIMEZONE_REGEX, "timezone must be an IANA zone name like 'Australia/Melbourne' or 'UTC'").optional().describe("Global default IANA timezone applied to every agent unless the " + "agent (or its profile) declares its own. See the per-agent " + "timezone field for the full cascade and auto-detection fallback.")
21415
21423
  }),
21416
21424
  telegram: TelegramConfigSchema,
21417
- release: ReleaseBlock.optional().describe("Fleet-wide default release-channel pin / pointer for the update " + "flow. Either `channel` (dev|rc|latest) or `pin` (sha-<hex>|v<semver>) " + "\u2014 mutually exclusive. Per-agent `release` REPLACES this entirely."),
21425
+ release: RootReleaseBlock.optional().describe("Fleet-wide default release-channel pin / pointer for the update " + "flow. Either `channel` (dev|rc|latest) or `pin` (sha-<hex>|v<semver>) " + "\u2014 mutually exclusive. Per-agent `release` REPLACES this entirely " + "(except `auto_update`, which is root-only \u2014 a fleet property read by " + "hostd's release watcher, never per-agent)."),
21418
21426
  memory: MemoryBackendConfigSchema.optional(),
21419
21427
  hindsight: HindsightConfigSchema.optional().describe("Fleet-singleton Hindsight (memory backend) configuration. Currently " + "just the LLM knob (provider + model) used for retain/reflect/" + "consolidation. Both fields optional; when unset the container falls " + "back to provider=claude-code + the hard-coded HINDSIGHT_DEFAULT_MODEL " + "so nothing changes for operators who don't set it. Read at container " + "launch by startHindsight() (src/setup/hindsight.ts) \u2014 takes effect on " + "the next `switchroom apply` / `memory setup --recreate`."),
21420
21428
  vault: VaultConfigSchema.optional(),
@@ -29495,6 +29503,79 @@ var init_atomic = __esm(() => {
29495
29503
  TMP_OPEN_FLAGS = constants2.O_WRONLY | constants2.O_CREAT | constants2.O_EXCL | (constants2.O_NOFOLLOW ?? 0);
29496
29504
  });
29497
29505
 
29506
+ // ../src/agents/generation-stamp.ts
29507
+ import { createHash as createHash5 } from "node:crypto";
29508
+ import { existsSync as existsSync33, readFileSync as readFileSync32, writeFileSync as writeFileSync29 } from "node:fs";
29509
+ import { join as join38 } from "node:path";
29510
+ function sha256Text(s) {
29511
+ return createHash5("sha256").update(s, "utf-8").digest("hex");
29512
+ }
29513
+ function hashManagedClaudeMd(content3) {
29514
+ const idx = content3.indexOf(CLAUDE_MD_YOURS_MARKER);
29515
+ const managed = idx === -1 ? content3 : content3.slice(0, idx + CLAUDE_MD_YOURS_MARKER.length);
29516
+ return sha256Text(managed);
29517
+ }
29518
+ function hashStampedFileOnDisk(agentDir, rel) {
29519
+ const p = join38(agentDir, rel);
29520
+ if (!existsSync33(p))
29521
+ return null;
29522
+ let content3;
29523
+ try {
29524
+ content3 = readFileSync32(p, "utf-8");
29525
+ } catch {
29526
+ return null;
29527
+ }
29528
+ return rel === "CLAUDE.md" ? hashManagedClaudeMd(content3) : sha256Text(content3);
29529
+ }
29530
+ function readGenerationStamp(agentDir) {
29531
+ const p = join38(agentDir, GENERATION_STAMP_FILE);
29532
+ if (!existsSync33(p))
29533
+ return null;
29534
+ try {
29535
+ const parsed = JSON.parse(readFileSync32(p, "utf-8"));
29536
+ if (parsed?.version !== 1 || typeof parsed.files !== "object" || parsed.files === null || Array.isArray(parsed.files)) {
29537
+ return null;
29538
+ }
29539
+ return parsed;
29540
+ } catch {
29541
+ return null;
29542
+ }
29543
+ }
29544
+ function detectStampDrift(agentDir, opts = {}) {
29545
+ const stamp = readGenerationStamp(agentDir);
29546
+ if (!stamp)
29547
+ return { hasStamp: false, findings: [] };
29548
+ const findings = [];
29549
+ for (const rel of STAMPED_FILES) {
29550
+ const stamped = stamp.files[rel];
29551
+ if (!stamped)
29552
+ continue;
29553
+ const current = hashStampedFileOnDisk(agentDir, rel);
29554
+ if (current === null) {
29555
+ findings.push({ surface: rel, detail: "missing (was present at last apply)" });
29556
+ } else if (current !== stamped) {
29557
+ findings.push({ surface: rel, detail: "modified since last apply" });
29558
+ }
29559
+ }
29560
+ if (opts.currentConfigHash && opts.currentConfigHash !== stamp.configHash) {
29561
+ findings.push({
29562
+ surface: "config",
29563
+ detail: "switchroom.yaml changed since last apply (generated files are stale)"
29564
+ });
29565
+ }
29566
+ if (opts.currentVersion && opts.currentVersion !== stamp.switchroomVersion) {
29567
+ findings.push({
29568
+ surface: "version",
29569
+ detail: `generated by switchroom ${stamp.switchroomVersion}, installed ${opts.currentVersion} \u2014 re-apply to refresh`
29570
+ });
29571
+ }
29572
+ return { hasStamp: true, findings };
29573
+ }
29574
+ var GENERATION_STAMP_FILE = ".switchroom-generated.json", CLAUDE_MD_YOURS_MARKER = "# --- Yours (preserved across apply) ---", STAMPED_FILES;
29575
+ var init_generation_stamp = __esm(() => {
29576
+ STAMPED_FILES = ["start.sh", "CLAUDE.md", ".mcp.json"];
29577
+ });
29578
+
29498
29579
  // ../node_modules/.bun/handlebars@4.7.9/node_modules/handlebars/dist/cjs/handlebars/utils.js
29499
29580
  var require_utils = __commonJS((exports2) => {
29500
29581
  exports2.__esModule = true;
@@ -32451,7 +32532,7 @@ var require_util = __commonJS((exports2) => {
32451
32532
  return path2;
32452
32533
  }
32453
32534
  exports2.normalize = normalize;
32454
- function join39(aRoot, aPath) {
32535
+ function join40(aRoot, aPath) {
32455
32536
  if (aRoot === "") {
32456
32537
  aRoot = ".";
32457
32538
  }
@@ -32483,7 +32564,7 @@ var require_util = __commonJS((exports2) => {
32483
32564
  }
32484
32565
  return joined;
32485
32566
  }
32486
- exports2.join = join39;
32567
+ exports2.join = join40;
32487
32568
  exports2.isAbsolute = function(aPath) {
32488
32569
  return aPath.charAt(0) === "/" || urlRegexp.test(aPath);
32489
32570
  };
@@ -32656,7 +32737,7 @@ var require_util = __commonJS((exports2) => {
32656
32737
  parsed.path = parsed.path.substring(0, index2 + 1);
32657
32738
  }
32658
32739
  }
32659
- sourceURL = join39(urlGenerate(parsed), sourceURL);
32740
+ sourceURL = join40(urlGenerate(parsed), sourceURL);
32660
32741
  }
32661
32742
  return normalize(sourceURL);
32662
32743
  }
@@ -35261,19 +35342,19 @@ function renderAuthLine(state7, agentName3, now = Date.now()) {
35261
35342
  }
35262
35343
 
35263
35344
  // gateway/quota-cache.ts
35264
- import { existsSync as existsSync43, readFileSync as readFileSync44, writeFileSync as writeFileSync37, mkdirSync as mkdirSync38 } from "fs";
35265
- import { join as join48, dirname as dirname15 } from "path";
35345
+ import { existsSync as existsSync44, readFileSync as readFileSync45, writeFileSync as writeFileSync38, mkdirSync as mkdirSync38 } from "fs";
35346
+ import { join as join49, dirname as dirname15 } from "path";
35266
35347
  function defaultCachePath() {
35267
- return process.env.SWITCHROOM_QUOTA_CACHE_PATH ?? join48(process.env.HOME ?? "/tmp", ".switchroom", "quota-cache.json");
35348
+ return process.env.SWITCHROOM_QUOTA_CACHE_PATH ?? join49(process.env.HOME ?? "/tmp", ".switchroom", "quota-cache.json");
35268
35349
  }
35269
35350
  function readQuotaCache(opts = {}) {
35270
35351
  const path2 = opts.path ?? defaultCachePath();
35271
35352
  const now = opts.now ?? Date.now();
35272
- if (!existsSync43(path2))
35353
+ if (!existsSync44(path2))
35273
35354
  return null;
35274
35355
  let entry;
35275
35356
  try {
35276
- entry = JSON.parse(readFileSync44(path2, "utf8"));
35357
+ entry = JSON.parse(readFileSync45(path2, "utf8"));
35277
35358
  } catch {
35278
35359
  return null;
35279
35360
  }
@@ -35300,7 +35381,7 @@ function writeQuotaCache(result, opts = {}) {
35300
35381
  };
35301
35382
  try {
35302
35383
  mkdirSync38(dirname15(path2), { recursive: true });
35303
- writeFileSync37(path2, JSON.stringify(entry, null, 2), { mode: 384 });
35384
+ writeFileSync38(path2, JSON.stringify(entry, null, 2), { mode: 384 });
35304
35385
  } catch {}
35305
35386
  }
35306
35387
  var DEFAULT_TTL_MS4, RATE_LIMIT_TTL_MS;
@@ -35310,8 +35391,8 @@ var init_quota_cache = __esm(() => {
35310
35391
  });
35311
35392
 
35312
35393
  // gateway/boot-probes.ts
35313
- import { readFileSync as readFileSync45, readdirSync as readdirSync10, existsSync as existsSync44 } from "fs";
35314
- import { join as join49 } from "path";
35394
+ import { readFileSync as readFileSync46, readdirSync as readdirSync10, existsSync as existsSync45 } from "fs";
35395
+ import { join as join50 } from "path";
35315
35396
  import { execFile as execFileCb } from "child_process";
35316
35397
  import { promisify } from "util";
35317
35398
  async function withTimeout(label, p, timeoutMs = PROBE_TIMEOUT_MS) {
@@ -35353,11 +35434,11 @@ function mapPlan(billingType, hasExtra) {
35353
35434
  }
35354
35435
  async function probeAccount(agentDir) {
35355
35436
  return withTimeout("Account", (async () => {
35356
- const claudeDir = join49(agentDir, ".claude");
35357
- const claudeJsonPath = join49(claudeDir, ".claude.json");
35437
+ const claudeDir = join50(agentDir, ".claude");
35438
+ const claudeJsonPath = join50(claudeDir, ".claude.json");
35358
35439
  let cfg = {};
35359
35440
  try {
35360
- const raw = readFileSync45(claudeJsonPath, "utf8");
35441
+ const raw = readFileSync46(claudeJsonPath, "utf8");
35361
35442
  cfg = JSON.parse(raw);
35362
35443
  } catch {
35363
35444
  return { status: "fail", label: "Account", detail: "no .claude.json" };
@@ -35375,12 +35456,12 @@ async function probeAccount(agentDir) {
35375
35456
  let tokenStr = "";
35376
35457
  let status = "ok";
35377
35458
  for (const candidate of [
35378
- join49(claudeDir, ".oauth-token.meta.json"),
35379
- join49(claudeDir, "accounts", "default", ".oauth-token.meta.json")
35459
+ join50(claudeDir, ".oauth-token.meta.json"),
35460
+ join50(claudeDir, "accounts", "default", ".oauth-token.meta.json")
35380
35461
  ]) {
35381
- if (existsSync44(candidate)) {
35462
+ if (existsSync45(candidate)) {
35382
35463
  try {
35383
- const meta = JSON.parse(readFileSync45(candidate, "utf8"));
35464
+ const meta = JSON.parse(readFileSync46(candidate, "utf8"));
35384
35465
  if (meta.expiresAt) {
35385
35466
  tokenStr = " \u00b7 " + formatDaysFromNow(meta.expiresAt);
35386
35467
  const daysLeft = Math.round((meta.expiresAt - Date.now()) / 86400000);
@@ -35555,9 +35636,9 @@ async function resolveTmuxSupervisorPid(agentName3, execFileImpl) {
35555
35636
  if (!cgroup)
35556
35637
  return null;
35557
35638
  const procsPath = `/sys/fs/cgroup${cgroup}/cgroup.procs`;
35558
- if (!existsSync44(procsPath))
35639
+ if (!existsSync45(procsPath))
35559
35640
  return null;
35560
- const pidsRaw = readFileSync45(procsPath, "utf-8");
35641
+ const pidsRaw = readFileSync46(procsPath, "utf-8");
35561
35642
  const pids = pidsRaw.split(`
35562
35643
  `).map((s) => s.trim()).filter(Boolean);
35563
35644
  if (pids.length === 0)
@@ -35570,7 +35651,7 @@ async function resolveTmuxSupervisorPid(agentName3, execFileImpl) {
35570
35651
  let rss = 0;
35571
35652
  let comm = "";
35572
35653
  try {
35573
- const status = readFileSync45(`/proc/${pid}/status`, "utf-8");
35654
+ const status = readFileSync46(`/proc/${pid}/status`, "utf-8");
35574
35655
  const rssLine = status.split(`
35575
35656
  `).find((l) => l.startsWith("VmRSS:"));
35576
35657
  if (rssLine) {
@@ -35582,7 +35663,7 @@ async function resolveTmuxSupervisorPid(agentName3, execFileImpl) {
35582
35663
  continue;
35583
35664
  }
35584
35665
  try {
35585
- comm = readFileSync45(`/proc/${pid}/comm`, "utf-8").trim();
35666
+ comm = readFileSync46(`/proc/${pid}/comm`, "utf-8").trim();
35586
35667
  } catch {}
35587
35668
  candidates.push({ pid, rss, comm });
35588
35669
  }
@@ -35762,9 +35843,9 @@ async function probeQuota(claudeConfigDir, _agentDir, fetchImpl = fetch, opts =
35762
35843
  let claudeDirForProbe = null;
35763
35844
  for (const candidate of [
35764
35845
  claudeConfigDir,
35765
- join49(claudeConfigDir, "accounts", "default")
35846
+ join50(claudeConfigDir, "accounts", "default")
35766
35847
  ]) {
35767
- if (existsSync44(join49(candidate, ".oauth-token"))) {
35848
+ if (existsSync45(join50(candidate, ".oauth-token"))) {
35768
35849
  claudeDirForProbe = candidate;
35769
35850
  break;
35770
35851
  }
@@ -35829,7 +35910,7 @@ async function probeHindsight(bankName, fetchImpl = fetch) {
35829
35910
  }
35830
35911
  function readContainerBootTimeMsForProbe() {
35831
35912
  try {
35832
- const stat1 = readFileSync45("/proc/1/stat", "utf8");
35913
+ const stat1 = readFileSync46("/proc/1/stat", "utf8");
35833
35914
  const lastParen = stat1.lastIndexOf(")");
35834
35915
  if (lastParen < 0)
35835
35916
  return null;
@@ -35837,7 +35918,7 @@ function readContainerBootTimeMsForProbe() {
35837
35918
  const starttimeTicks = Number(after[19]);
35838
35919
  if (!Number.isFinite(starttimeTicks))
35839
35920
  return null;
35840
- const procStat = readFileSync45("/proc/stat", "utf8");
35921
+ const procStat = readFileSync46("/proc/stat", "utf8");
35841
35922
  const btimeLine = procStat.split(`
35842
35923
  `).find((l) => l.startsWith("btime "));
35843
35924
  if (!btimeLine)
@@ -35935,7 +36016,7 @@ async function probeUds(label, socketPath, opts = {}) {
35935
36016
  }
35936
36017
  return withTimeout(label, (async () => {
35937
36018
  if (!opts.connectImpl) {
35938
- if (!existsSync44(socketPath)) {
36019
+ if (!existsSync45(socketPath)) {
35939
36020
  return {
35940
36021
  status: "fail",
35941
36022
  label,
@@ -35999,7 +36080,7 @@ async function probeSkills(agentDir, opts = {}) {
35999
36080
  return withTimeout("Skills", (async () => {
36000
36081
  const fs2 = opts.fs ?? realSkillsFs;
36001
36082
  const max = opts.maxNamesShown ?? 3;
36002
- const skillsDir = join49(agentDir, ".claude", "skills");
36083
+ const skillsDir = join50(agentDir, ".claude", "skills");
36003
36084
  if (!fs2.exists(skillsDir)) {
36004
36085
  return { status: "ok", label: "Skills", detail: "no skills dir" };
36005
36086
  }
@@ -36014,17 +36095,17 @@ async function probeSkills(agentDir, opts = {}) {
36014
36095
  }
36015
36096
  const dangling = [];
36016
36097
  for (const name of entries) {
36017
- const skillPath = join49(skillsDir, name);
36098
+ const skillPath = join50(skillsDir, name);
36018
36099
  if (!fs2.exists(skillPath)) {
36019
36100
  dangling.push(name);
36020
36101
  continue;
36021
36102
  }
36022
- const skillMd = join49(skillPath, "SKILL.md");
36103
+ const skillMd = join50(skillPath, "SKILL.md");
36023
36104
  if (!fs2.exists(skillMd) && !fs2.exists(skillPath + ".md")) {
36024
36105
  continue;
36025
36106
  }
36026
36107
  }
36027
- const overlayDir = opts.overlaySkillsDir ?? join49(agentDir, "skills.d");
36108
+ const overlayDir = opts.overlaySkillsDir ?? join50(agentDir, "skills.d");
36028
36109
  const overlaySlugs = new Set;
36029
36110
  if (fs2.exists(overlayDir)) {
36030
36111
  let overlayEntries = [];
@@ -36066,8 +36147,8 @@ function renderBucketedSkills(switchroom, agent) {
36066
36147
  }
36067
36148
  async function probeConnections(agentDir, opts = {}) {
36068
36149
  return withTimeout("Connections", (async () => {
36069
- const path2 = join49(agentDir, ".claude", "connection-health.json");
36070
- const read = opts.readFileImpl ?? ((p) => readFileSync45(p, "utf8"));
36150
+ const path2 = join50(agentDir, ".claude", "connection-health.json");
36151
+ const read = opts.readFileImpl ?? ((p) => readFileSync46(p, "utf8"));
36071
36152
  let issues = [];
36072
36153
  try {
36073
36154
  const parsed = JSON.parse(read(path2));
@@ -36091,31 +36172,87 @@ async function probeConnections(agentDir, opts = {}) {
36091
36172
  };
36092
36173
  })());
36093
36174
  }
36175
+ async function probeDrift(agentDir, opts = {}) {
36176
+ return withTimeout("Drift", (async () => {
36177
+ const surfaces = [];
36178
+ let stampGeneratedAtMs = NaN;
36179
+ try {
36180
+ const stampResult = detectStampDrift(agentDir);
36181
+ for (const f of stampResult.findings) {
36182
+ surfaces.push(`${f.surface} (${f.detail})`);
36183
+ }
36184
+ if (stampResult.hasStamp) {
36185
+ const raw = readGenerationStamp(agentDir);
36186
+ if (raw?.generatedAt)
36187
+ stampGeneratedAtMs = Date.parse(raw.generatedAt);
36188
+ }
36189
+ } catch {}
36190
+ try {
36191
+ const raw = readFileSync46(join50(agentDir, ".switchroom-drift.json"), "utf8");
36192
+ const report = JSON.parse(raw);
36193
+ if (report?.version === 1 && Array.isArray(report.findings)) {
36194
+ const gen = report.generatedAt ? Date.parse(report.generatedAt) : NaN;
36195
+ const supersededByApply = Number.isFinite(stampGeneratedAtMs) && Number.isFinite(gen) && gen < stampGeneratedAtMs;
36196
+ if (!supersededByApply) {
36197
+ const seen = new Set(surfaces.map((s) => s.split(" ")[0]));
36198
+ let ageNote = "";
36199
+ if (Number.isFinite(gen)) {
36200
+ const ageMs = (opts.nowMs?.() ?? Date.now()) - gen;
36201
+ if (ageMs > 3600000) {
36202
+ const hours = Math.round(ageMs / 3600000);
36203
+ const human = hours >= 48 ? `${Math.round(hours / 24)}d` : `${hours}h`;
36204
+ ageNote = ` \u2014 as of ${human} ago`;
36205
+ }
36206
+ }
36207
+ for (const f of report.findings) {
36208
+ if (!f?.surface || seen.has(f.surface))
36209
+ continue;
36210
+ seen.add(f.surface);
36211
+ surfaces.push(`${f.surface}${ageNote}`);
36212
+ }
36213
+ }
36214
+ }
36215
+ } catch {}
36216
+ if (surfaces.length === 0) {
36217
+ return { status: "ok", label: "Drift", detail: "generated surfaces in sync" };
36218
+ }
36219
+ const shown = surfaces.slice(0, 4).join(", ");
36220
+ const more = surfaces.length > 4 ? ` +${surfaces.length - 4} more` : "";
36221
+ const target = opts.agentName ? ` ${opts.agentName}` : "";
36222
+ return {
36223
+ status: "degraded",
36224
+ label: "Drift",
36225
+ detail: `${surfaces.length} drifted surface(s): ${shown}${more}`,
36226
+ nextStep: `Run \`switchroom apply\` (or \`switchroom agent reconcile${target} --restart\`); see \`switchroom doctor\` for detail`
36227
+ };
36228
+ })());
36229
+ }
36094
36230
  var execFile, PROBE_TIMEOUT_MS = 2000, QUOTA_BROKER_TIMEOUT_MS = 7000, QUOTA_DIRECT_FALLBACK_TIMEOUT_MS = 5000, QUOTA_PROBE_OUTER_TIMEOUT_MS = 9000, TOKEN_EXPIRING_SOON_DAYS = 7, AGENT_RETRY_INTERVAL_MS = 1500, AGENT_RETRY_MAX_MS = 12000, AGENT_LIVE_WINDOW_MS = 45000, AGENT_LIVE_POLL_INTERVAL_MS = 2000, AGENT_LIVE_FOLLOWUP_REPOLL_MS = 30000, realProcFs, SCHEDULER_LOCK_PATH_DEFAULT = "/state/agent/scheduler.lock", SCHEDULER_JSONL_PATH_DEFAULT = "/state/agent/scheduler.jsonl", SCHEDULER_FRESH_BOOT_MS = 30000, realSchedulerFs, realSkillsFs;
36095
36231
  var init_boot_probes = __esm(() => {
36096
36232
  init_quota_cache();
36233
+ init_generation_stamp();
36097
36234
  init_quota_check();
36098
36235
  execFile = promisify(execFileCb);
36099
36236
  realProcFs = {
36100
36237
  readdir: (p) => readdirSync10(p),
36101
- readFile: (p) => readFileSync45(p, "utf-8")
36238
+ readFile: (p) => readFileSync46(p, "utf-8")
36102
36239
  };
36103
36240
  realSchedulerFs = {
36104
- readFile: (p) => readFileSync45(p, "utf-8"),
36241
+ readFile: (p) => readFileSync46(p, "utf-8"),
36105
36242
  mtimeMs: (p) => {
36106
36243
  const { statSync: statSync14 } = __require("fs");
36107
36244
  return statSync14(p).mtimeMs;
36108
36245
  },
36109
- exists: (p) => existsSync44(p)
36246
+ exists: (p) => existsSync45(p)
36110
36247
  };
36111
36248
  realSkillsFs = {
36112
36249
  readdir: (p) => readdirSync10(p),
36113
- exists: (p) => existsSync44(p)
36250
+ exists: (p) => existsSync45(p)
36114
36251
  };
36115
36252
  });
36116
36253
 
36117
36254
  // gateway/boot-issue-cache.ts
36118
- import { existsSync as existsSync45, readFileSync as readFileSync46, writeFileSync as writeFileSync38, mkdirSync as mkdirSync39, renameSync as renameSync19 } from "fs";
36255
+ import { existsSync as existsSync46, readFileSync as readFileSync47, writeFileSync as writeFileSync39, mkdirSync as mkdirSync39, renameSync as renameSync19 } from "fs";
36119
36256
  import { dirname as dirname16 } from "path";
36120
36257
  function fingerprintProbe(key, r) {
36121
36258
  if (r.status === "ok")
@@ -36195,11 +36332,11 @@ function diffProbes(probes, cache, opts = {}) {
36195
36332
  return out;
36196
36333
  }
36197
36334
  function loadCache(path2, now = Date.now) {
36198
- if (!existsSync45(path2))
36335
+ if (!existsSync46(path2))
36199
36336
  return { ...EMPTY_CACHE, probes: {} };
36200
36337
  let raw;
36201
36338
  try {
36202
- raw = readFileSync46(path2, "utf-8");
36339
+ raw = readFileSync47(path2, "utf-8");
36203
36340
  } catch {
36204
36341
  return { ...EMPTY_CACHE, probes: {} };
36205
36342
  }
@@ -36244,7 +36381,7 @@ function applyAndSave(path2, cache, diff) {
36244
36381
  try {
36245
36382
  mkdirSync39(dirname16(path2), { recursive: true });
36246
36383
  const tmp = `${path2}.tmp`;
36247
- writeFileSync38(tmp, JSON.stringify(next), { mode: 384 });
36384
+ writeFileSync39(tmp, JSON.stringify(next), { mode: 384 });
36248
36385
  renameSync19(tmp, path2);
36249
36386
  } catch {}
36250
36387
  return next;
@@ -36257,14 +36394,14 @@ var init_boot_issue_cache = __esm(() => {
36257
36394
  });
36258
36395
 
36259
36396
  // gateway/config-snapshot.ts
36260
- import { createHash as createHash5 } from "crypto";
36261
- import { existsSync as existsSync46, readFileSync as readFileSync47, writeFileSync as writeFileSync39, mkdirSync as mkdirSync40, renameSync as renameSync20 } from "fs";
36397
+ import { createHash as createHash6 } from "crypto";
36398
+ import { existsSync as existsSync47, readFileSync as readFileSync48, writeFileSync as writeFileSync40, mkdirSync as mkdirSync40, renameSync as renameSync20 } from "fs";
36262
36399
  import { dirname as dirname17 } from "path";
36263
36400
  function hashStringArray(items) {
36264
36401
  if (!items || items.length === 0)
36265
36402
  return null;
36266
36403
  const sorted = [...items].sort();
36267
- const raw = createHash5("sha256").update(sorted.join("\x00")).digest("hex");
36404
+ const raw = createHash6("sha256").update(sorted.join("\x00")).digest("hex");
36268
36405
  return raw.slice(0, 12);
36269
36406
  }
36270
36407
  function normalizeModel(model) {
@@ -36323,11 +36460,11 @@ function renderConfigChangeDim(dim) {
36323
36460
  }
36324
36461
  }
36325
36462
  function loadSnapshot(path2, now = Date.now) {
36326
- if (!existsSync46(path2))
36463
+ if (!existsSync47(path2))
36327
36464
  return null;
36328
36465
  let raw;
36329
36466
  try {
36330
- raw = readFileSync47(path2, "utf-8");
36467
+ raw = readFileSync48(path2, "utf-8");
36331
36468
  } catch {
36332
36469
  return null;
36333
36470
  }
@@ -36359,7 +36496,7 @@ function persistSnapshot(path2, snapshot) {
36359
36496
  try {
36360
36497
  mkdirSync40(dirname17(path2), { recursive: true });
36361
36498
  const tmp = `${path2}.tmp`;
36362
- writeFileSync39(tmp, JSON.stringify(snapshot), { mode: 384 });
36499
+ writeFileSync40(tmp, JSON.stringify(snapshot), { mode: 384 });
36363
36500
  renameSync20(tmp, path2);
36364
36501
  } catch {}
36365
36502
  }
@@ -36368,13 +36505,13 @@ var init_config_snapshot = __esm(() => {
36368
36505
  });
36369
36506
 
36370
36507
  // gateway/boot-card-msgid.ts
36371
- import { readFileSync as readFileSync48, writeFileSync as writeFileSync40 } from "node:fs";
36508
+ import { readFileSync as readFileSync49, writeFileSync as writeFileSync41 } from "node:fs";
36372
36509
  function bootCardChatKey(chatId, threadId) {
36373
36510
  return `${chatId}:${threadId ?? ""}`;
36374
36511
  }
36375
36512
  function readStore(path2) {
36376
36513
  try {
36377
- const parsed = JSON.parse(readFileSync48(path2, "utf8"));
36514
+ const parsed = JSON.parse(readFileSync49(path2, "utf8"));
36378
36515
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
36379
36516
  return parsed;
36380
36517
  }
@@ -36393,7 +36530,7 @@ function saveBootCardMsgId(path2, chatKey3, messageId) {
36393
36530
  if (store2[chatKey3] === messageId)
36394
36531
  return;
36395
36532
  store2[chatKey3] = messageId;
36396
- writeFileSync40(path2, JSON.stringify(store2), "utf8");
36533
+ writeFileSync41(path2, JSON.stringify(store2), "utf8");
36397
36534
  } catch {}
36398
36535
  }
36399
36536
  var init_boot_card_msgid = () => {};
@@ -36411,8 +36548,8 @@ __export(exports_boot_card, {
36411
36548
  readRoutingMode: () => readRoutingMode,
36412
36549
  containerBootStartMs: () => containerBootStartMs
36413
36550
  });
36414
- import { join as join50 } from "path";
36415
- import { readFileSync as readFileSync49, statSync as statSync14 } from "fs";
36551
+ import { join as join51 } from "path";
36552
+ import { readFileSync as readFileSync50, statSync as statSync14 } from "fs";
36416
36553
  function resolvePersonaName(slug, loadConfig3) {
36417
36554
  try {
36418
36555
  const config = loadConfig3 ? loadConfig3() : loadConfig();
@@ -36434,7 +36571,7 @@ function shouldSkipDuplicateBootCard(gate, site) {
36434
36571
  }
36435
36572
  return { skip: false };
36436
36573
  }
36437
- function containerBootStartMs(fsImpl = { readFileSync: readFileSync49 }) {
36574
+ function containerBootStartMs(fsImpl = { readFileSync: readFileSync50 }) {
36438
36575
  try {
36439
36576
  const btimeLine = fsImpl.readFileSync("/proc/stat", "utf-8").split(`
36440
36577
  `).find((l) => l.startsWith("btime "));
@@ -36455,8 +36592,8 @@ function containerBootStartMs(fsImpl = { readFileSync: readFileSync49 }) {
36455
36592
  return null;
36456
36593
  }
36457
36594
  }
36458
- function readRoutingMode(agentDir, bootStartMs, fsImpl = { readFileSync: readFileSync49, statSync: statSync14 }) {
36459
- const path2 = join50(agentDir, ".routing-mode");
36595
+ function readRoutingMode(agentDir, bootStartMs, fsImpl = { readFileSync: readFileSync50, statSync: statSync14 }) {
36596
+ const path2 = join51(agentDir, ".routing-mode");
36460
36597
  try {
36461
36598
  const raw = fsImpl.readFileSync(path2, "utf-8");
36462
36599
  const line = raw.split(`
@@ -36563,7 +36700,7 @@ function renderBootCard(opts) {
36563
36700
  return stackCardLines(flatLines);
36564
36701
  }
36565
36702
  async function runAllProbes(opts) {
36566
- const claudeDir = join50(opts.agentDir, ".claude");
36703
+ const claudeDir = join51(opts.agentDir, ".claude");
36567
36704
  const probes = {};
36568
36705
  const slug = opts.agentSlug ?? opts.agentName;
36569
36706
  await Promise.allSettled([
@@ -36596,6 +36733,9 @@ async function runAllProbes(opts) {
36596
36733
  }),
36597
36734
  probeConnections(opts.agentDir).then((r) => {
36598
36735
  probes.connections = r;
36736
+ }),
36737
+ probeDrift(opts.agentDir, { agentName: opts.agentSlug ?? opts.agentName }).then((r) => {
36738
+ probes.drift = r;
36599
36739
  })
36600
36740
  ]);
36601
36741
  return probes;
@@ -36854,7 +36994,8 @@ var init_boot_card = __esm(() => {
36854
36994
  broker: "Broker",
36855
36995
  kernel: "Kernel",
36856
36996
  skills: "Skills",
36857
- connections: "Connections"
36997
+ connections: "Connections",
36998
+ drift: "Drift"
36858
36999
  };
36859
37000
  PROBE_KEYS = [
36860
37001
  "account",
@@ -36866,7 +37007,8 @@ var init_boot_card = __esm(() => {
36866
37007
  "broker",
36867
37008
  "kernel",
36868
37009
  "skills",
36869
- "connections"
37010
+ "connections",
37011
+ "drift"
36870
37012
  ];
36871
37013
  REASON_EMOJI = {
36872
37014
  planned: "\u2705",
@@ -37417,7 +37559,7 @@ __export(exports_tmux2, {
37417
37559
  captureAgentPane: () => captureAgentPane2
37418
37560
  });
37419
37561
  import { execFileSync as execFileSync8 } from "node:child_process";
37420
- import { chmodSync as chmodSync13, mkdirSync as mkdirSync48, readdirSync as readdirSync13, statSync as statSync19, unlinkSync as unlinkSync26, writeFileSync as writeFileSync49 } from "node:fs";
37562
+ import { chmodSync as chmodSync13, mkdirSync as mkdirSync48, readdirSync as readdirSync13, statSync as statSync19, unlinkSync as unlinkSync26, writeFileSync as writeFileSync50 } from "node:fs";
37421
37563
  import { resolve as resolve12 } from "node:path";
37422
37564
  function captureAgentPane2(opts) {
37423
37565
  const { agentName: agentName3, agentDir, reason } = opts;
@@ -37468,7 +37610,7 @@ function captureAgentPane2(opts) {
37468
37610
  ` + `
37469
37611
  `;
37470
37612
  try {
37471
- writeFileSync49(outPath, Buffer.concat([Buffer.from(header, "utf8"), body]), {
37613
+ writeFileSync50(outPath, Buffer.concat([Buffer.from(header, "utf8"), body]), {
37472
37614
  mode: 384
37473
37615
  });
37474
37616
  } catch (err) {
@@ -37606,7 +37748,8 @@ function clipReason(reason) {
37606
37748
  }
37607
37749
  function buildConfigApprovalCardBody(args) {
37608
37750
  const safeReason = clipReason(args.reason);
37609
- const render = (diff) => `\uD83D\uDEE0 **Config edit proposed**
37751
+ const safeTitle = args.title ? args.title.replace(/[\u0000-\u001f\u007f]+/g, " ").slice(0, 200) : undefined;
37752
+ const render = (diff) => `${safeTitle ?? "\uD83D\uDEE0 **Config edit proposed**"}
37610
37753
  ` + `Agent: \`${args.agentName}\`
37611
37754
  ` + `Reason: ${escapeMarkdown(safeReason)}
37612
37755
 
@@ -37651,7 +37794,8 @@ async function handleRequestConfigApproval(client3, msg, deps) {
37651
37794
  const built = buildConfigApprovalCardBody({
37652
37795
  agentName: msg.agentName,
37653
37796
  reason: msg.reason,
37654
- unifiedDiff: prelim
37797
+ unifiedDiff: prelim,
37798
+ ...msg.title !== undefined ? { title: msg.title } : {}
37655
37799
  });
37656
37800
  const body = built.body;
37657
37801
  const oversize = prelim !== msg.unifiedDiff || built.truncated;
@@ -38348,7 +38492,7 @@ __export(exports_materialize_bot_token, {
38348
38492
  materializeBotToken: () => materializeBotToken,
38349
38493
  BotTokenMaterializeError: () => BotTokenMaterializeError
38350
38494
  });
38351
- import { existsSync as existsSync55 } from "node:fs";
38495
+ import { existsSync as existsSync56 } from "node:fs";
38352
38496
  function pickConfiguredToken(config, agentName3) {
38353
38497
  if (agentName3) {
38354
38498
  const agent = config.agents?.[agentName3];
@@ -38362,7 +38506,7 @@ function tryDirectVaultRead4(ref, config, passphrase) {
38362
38506
  if (!passphrase)
38363
38507
  return null;
38364
38508
  const vaultPath = resolvePath(config.vault?.path ?? "~/.switchroom/vault.enc");
38365
- if (!existsSync55(vaultPath))
38509
+ if (!existsSync56(vaultPath))
38366
38510
  return null;
38367
38511
  try {
38368
38512
  const secrets = openVault(passphrase, vaultPath);
@@ -38514,11 +38658,11 @@ var init_approvals_commands = __esm(() => {
38514
38658
  // gateway/gateway.ts
38515
38659
  var import_grammy16 = __toESM(require_mod(), 1);
38516
38660
  var import_runner3 = __toESM(require_mod3(), 1);
38517
- import { randomBytes as randomBytes10, createHash as createHash6 } from "crypto";
38661
+ import { randomBytes as randomBytes10, createHash as createHash7 } from "crypto";
38518
38662
  import { execFileSync as execFileSync9, execSync as execSync2, spawn as spawn2 } from "child_process";
38519
38663
  import {
38520
- readFileSync as readFileSync60,
38521
- writeFileSync as writeFileSync50,
38664
+ readFileSync as readFileSync61,
38665
+ writeFileSync as writeFileSync51,
38522
38666
  mkdirSync as mkdirSync49,
38523
38667
  readdirSync as readdirSync14,
38524
38668
  rmSync as rmSync6,
@@ -38528,12 +38672,12 @@ import {
38528
38672
  chmodSync as chmodSync14,
38529
38673
  openSync as openSync12,
38530
38674
  closeSync as closeSync12,
38531
- existsSync as existsSync56,
38675
+ existsSync as existsSync57,
38532
38676
  unlinkSync as unlinkSync27,
38533
38677
  appendFileSync as appendFileSync9
38534
38678
  } from "fs";
38535
38679
  import { homedir as homedir20 } from "os";
38536
- import { join as join62, sep as sep4, basename as basename15 } from "path";
38680
+ import { join as join63, sep as sep4, basename as basename15 } from "path";
38537
38681
 
38538
38682
  // plugin-logger.ts
38539
38683
  import { appendFileSync, mkdirSync, renameSync, statSync, existsSync } from "fs";
@@ -39942,8 +40086,89 @@ var ACRONYMS = new Set([
39942
40086
  "HTTP",
39943
40087
  "JSON",
39944
40088
  "SQL",
39945
- "UI"
40089
+ "UI",
40090
+ "HTTPS",
40091
+ "SSH",
40092
+ "DNS",
40093
+ "CLI",
40094
+ "AWS",
40095
+ "UTC",
40096
+ "MCP",
40097
+ "PDF",
40098
+ "ID",
40099
+ "OK",
40100
+ "VM",
40101
+ "LLM",
40102
+ "YAML",
40103
+ "RAM",
40104
+ "USB"
39946
40105
  ]);
40106
+ var ACRONYM_MAX_LEN = Math.max(...[...ACRONYMS].map((a) => a.length));
40107
+ var PATH_TOKEN_RE = /(?<![\w~./-])(?:~|\.{1,2}|[A-Za-z0-9_.@-]+)?(?:\/[A-Za-z0-9_.@+-]+){2,}\/?(?![\w/-])/g;
40108
+ function looksLikePath(m, segs) {
40109
+ if (/^(?:\/|\.{1,2}\/|~\/)/.test(m))
40110
+ return true;
40111
+ const last = segs[segs.length - 1];
40112
+ if (/\.[A-Za-z][A-Za-z0-9]{0,7}$/.test(last))
40113
+ return true;
40114
+ return !segs.every((sg) => /^[a-z][a-z0-9]*$/.test(sg));
40115
+ }
40116
+ function isSpeakableSegment(seg) {
40117
+ if (!/[A-Za-z]/.test(seg))
40118
+ return false;
40119
+ if (seg.length > 32)
40120
+ return false;
40121
+ if (/^[0-9a-f]{8,}$/i.test(seg))
40122
+ return false;
40123
+ return true;
40124
+ }
40125
+ function speakPaths(input) {
40126
+ return input.replace(PATH_TOKEN_RE, (m) => {
40127
+ const segs = m.split("/").filter((sg) => sg.length > 0);
40128
+ if (segs.length < 2)
40129
+ return m;
40130
+ if (segs.every((sg) => /^\d+$/.test(sg)))
40131
+ return m;
40132
+ if (!looksLikePath(m, segs))
40133
+ return m;
40134
+ const last = segs[segs.length - 1];
40135
+ return isSpeakableSegment(last) ? last : "a path";
40136
+ });
40137
+ }
40138
+ var BLOCK_MARKER_RE = /^[ \t]{0,3}(?:#{1,6}[ \t]+|[-*+][ \t]+|\d+[.)][ \t]+)/;
40139
+ var HEADING_MARKER_RE = /^[ \t]{0,3}#{1,6}[ \t]+/;
40140
+ function ensureTerminated(line) {
40141
+ const t = line.replace(/[ \t]+$/, "");
40142
+ if (!t.trim())
40143
+ return t;
40144
+ if (/[.!?:;]$/.test(t))
40145
+ return t;
40146
+ if (/,$/.test(t))
40147
+ return `${t.slice(0, -1)}.`;
40148
+ return `${t}.`;
40149
+ }
40150
+ function applyBlockPauses(input) {
40151
+ const lines = input.split(`
40152
+ `);
40153
+ const isBlock = lines.map((l) => BLOCK_MARKER_RE.test(l));
40154
+ const isHeading = lines.map((l) => HEADING_MARKER_RE.test(l));
40155
+ const stripped = lines.map((l, i) => isBlock[i] ? l.slice(l.match(BLOCK_MARKER_RE)[0].length) : l);
40156
+ const inUnit = [];
40157
+ for (let i = 0;i < stripped.length; i++) {
40158
+ inUnit[i] = isBlock[i] === true || i > 0 && inUnit[i - 1] === true && isHeading[i - 1] !== true && stripped[i].trim() !== "";
40159
+ }
40160
+ return stripped.map((line, i) => {
40161
+ const next = stripped[i + 1];
40162
+ const nextIsBlock = isBlock[i + 1] === true;
40163
+ const unitEndsHere = isHeading[i] === true || next === undefined || next.trim() === "" || nextIsBlock;
40164
+ if (inUnit[i] && unitEndsHere)
40165
+ return ensureTerminated(line);
40166
+ if (nextIsBlock && line.trim() !== "")
40167
+ return ensureTerminated(line);
40168
+ return line;
40169
+ }).join(`
40170
+ `);
40171
+ }
39947
40172
  function normalizeForSpeech(input) {
39948
40173
  if (!input)
39949
40174
  return "";
@@ -39952,7 +40177,7 @@ function normalizeForSpeech(input) {
39952
40177
  s = decodeHtmlEntities(s);
39953
40178
  s = stripBackslashEscapes(s);
39954
40179
  s = s.replace(/[\u{1F000}-\u{1FAFF}\u{1F1E6}-\u{1F1FF}\u{2600}-\u{27BF}\u{2B00}-\u{2BFF}\u{FE00}-\u{FE0F}\u{200D}\u{2B50}\u{3030}\u{303D}\u{3297}\u{3299}\u{24C2}]/gu, "");
39955
- s = s.replace(/:([a-z0-9][a-z0-9_+-]*):/gi, " ");
40180
+ s = s.replace(/(?<![\w:]):([a-z0-9][a-z0-9_+-]*):(?!\d)/gi, " ");
39956
40181
  s = s.replace(/(^|\n)[ \t]*(`{3,}|~{3,})[^\n]*\n[\s\S]*?\n[ \t]*\2[ \t]*(?=\n|$)/g, `$1${CODE_BLOCK_PLACEHOLDER}.`);
39957
40182
  s = s.replace(/(^|\n)[ \t]*(`{3,}|~{3,})[^\n]*\n[\s\S]*$/g, `$1${CODE_BLOCK_PLACEHOLDER}.`);
39958
40183
  s = s.replace(/!\[([^\]]*)\]\([^)]*\)/g, "$1");
@@ -39961,6 +40186,7 @@ function normalizeForSpeech(input) {
39961
40186
  s = s.replace(/\bhttps?:\/\/[^\s)]+/gi, "a link");
39962
40187
  s = s.replace(/`([^`\n]+)`/g, "$1");
39963
40188
  s = s.replace(/`/g, "");
40189
+ s = speakPaths(s);
39964
40190
  s = s.replace(/\*\*\*(.+?)\*\*\*/g, "$1");
39965
40191
  s = s.replace(/___(.+?)___/g, "$1");
39966
40192
  s = s.replace(/\*\*(.+?)\*\*/g, "$1");
@@ -39968,10 +40194,8 @@ function normalizeForSpeech(input) {
39968
40194
  s = s.replace(/\*(.+?)\*/g, "$1");
39969
40195
  s = s.replace(/(?<![A-Za-z0-9])_(.+?)_(?![A-Za-z0-9])/g, "$1");
39970
40196
  s = s.replace(/~~(.+?)~~/g, "$1");
39971
- s = s.replace(/^[ \t]{0,3}#{1,6}[ \t]+/gm, "");
39972
40197
  s = s.replace(/^[ \t]{0,3}>[ \t]?/gm, "");
39973
- s = s.replace(/^[ \t]{0,3}[-*+][ \t]+/gm, "");
39974
- s = s.replace(/^[ \t]{0,3}\d+[.)][ \t]+/gm, "");
40198
+ s = applyBlockPauses(s);
39975
40199
  s = s.replace(/^[ \t]{0,3}([-_*])\1{2,}[ \t]*$/gm, "");
39976
40200
  s = s.replace(/^[ \t]*\|?[ \t]*:?-{2,}:?[ \t]*(\|[ \t]*:?-{2,}:?[ \t]*)+\|?[ \t]*$/gm, "");
39977
40201
  s = s.replace(/\|/g, " ");
@@ -39996,7 +40220,7 @@ function normalizeForSpeech(input) {
39996
40220
  return m;
39997
40221
  return `${MONTHS[month]} ${ord} ${yw}`;
39998
40222
  });
39999
- s = s.replace(/\b([01]?\d|2[0-3]):([0-5]\d)\b/g, (m, hh, mm) => {
40223
+ s = s.replace(/(?<![\d:])\b([01]?\d|2[0-3]):([0-5]\d)(?!:?\d)/g, (m, hh, mm) => {
40000
40224
  const h = Number(hh);
40001
40225
  const min = Number(mm);
40002
40226
  const hw = belowThousand(h);
@@ -40004,11 +40228,12 @@ function normalizeForSpeech(input) {
40004
40228
  return `${hw} o'clock`;
40005
40229
  return `${hw} ${minutesToWords(min)}`;
40006
40230
  });
40007
- s = s.replace(/\$(\d{1,9})(?:\.(\d{2}))?\b/g, (m, dollars, cents) => {
40008
- const dw = numberToWords(Number(dollars));
40231
+ s = s.replace(/\$(\d{1,3}(?:,\d{3})+|\d{1,9})(?:\.(\d{2}))?(?!\d|[.,]\d)/g, (m, dollarsRaw, cents) => {
40232
+ const dollars = Number(String(dollarsRaw).replace(/,/g, ""));
40233
+ const dw = numberToWords(dollars);
40009
40234
  if (!dw)
40010
40235
  return m;
40011
- const noun = Number(dollars) === 1 && !cents ? "dollar" : "dollars";
40236
+ const noun = dollars === 1 && !cents ? "dollar" : "dollars";
40012
40237
  if (cents && cents !== "00") {
40013
40238
  const cw = numberToWords(Number(cents));
40014
40239
  return `${dw} ${noun} ${cw}`;
@@ -40040,7 +40265,7 @@ function normalizeForSpeech(input) {
40040
40265
  s = s.replace(/\s=\s/g, " equals ");
40041
40266
  s = s.replace(/(\s)&(\s)/g, "$1and$2");
40042
40267
  s = s.replace(/(\w)&(\w)/g, "$1 and $2");
40043
- s = s.replace(/\b[A-Z]{2,5}\b/g, (tok) => ACRONYMS.has(tok) ? tok.split("").join(" ") : tok);
40268
+ s = s.replace(new RegExp(`\\b[A-Z]{2,${ACRONYM_MAX_LEN}}\\b`, "g"), (tok) => ACRONYMS.has(tok) ? tok.split("").join(" ") : tok);
40044
40269
  s = s.replace(/([!?.]){2,}/g, "$1");
40045
40270
  s = s.replace(/[ \t]*\n[ \t]*\n[ \t]*/g, ". ");
40046
40271
  s = s.replace(/\s*\n\s*/g, " ");
@@ -40106,8 +40331,24 @@ var ACRONYMS2 = new Set([
40106
40331
  "HTTP",
40107
40332
  "JSON",
40108
40333
  "SQL",
40109
- "UI"
40334
+ "UI",
40335
+ "HTTPS",
40336
+ "SSH",
40337
+ "DNS",
40338
+ "CLI",
40339
+ "AWS",
40340
+ "UTC",
40341
+ "MCP",
40342
+ "PDF",
40343
+ "ID",
40344
+ "OK",
40345
+ "VM",
40346
+ "LLM",
40347
+ "YAML",
40348
+ "RAM",
40349
+ "USB"
40110
40350
  ]);
40351
+ var ACRONYM_MAX_LEN2 = Math.max(...[...ACRONYMS2].map((a) => a.length));
40111
40352
 
40112
40353
  // tts-normalize.ts
40113
40354
  var NULL = "\x00";
@@ -40344,7 +40585,7 @@ table omitted.`);
40344
40585
  words.push(ONES2[Number(ch)]);
40345
40586
  return words.join(" ");
40346
40587
  });
40347
- s = s.replace(/\b([01]?\d|2[0-3]):([0-5]\d)(?!:?\d)/g, (_m, hh, mm) => {
40588
+ s = s.replace(/(?<![\d:])\b([01]?\d|2[0-3]):([0-5]\d)(?!:?\d)/g, (_m, hh, mm) => {
40348
40589
  const h = Number(hh);
40349
40590
  const min = Number(mm);
40350
40591
  const hw = belowThousand2(h);
@@ -45438,7 +45679,7 @@ table omitted.`);
45438
45679
  words.push(ONES3[Number(ch)]);
45439
45680
  return words.join(" ");
45440
45681
  });
45441
- s = s.replace(/\b([01]?\d|2[0-3]):([0-5]\d)(?!:?\d)/g, (_m, hh, mm) => {
45682
+ s = s.replace(/(?<![\d:])\b([01]?\d|2[0-3]):([0-5]\d)(?!:?\d)/g, (_m, hh, mm) => {
45442
45683
  const h = Number(hh);
45443
45684
  const min = Number(mm);
45444
45685
  const hw = belowThousand3(h);
@@ -51919,12 +52160,20 @@ function createHandbackPreturnSignal(deps) {
51919
52160
  `);
51920
52161
  });
51921
52162
  }
52163
+ function stopTypingUnlessTurnLive(entry, reason) {
52164
+ if (deps.hasLiveTurn?.(entry.statusKey) === true) {
52165
+ log(`handback-preturn-signal: ${reason} key=${entry.statusKey} ` + `typing=kept (live turn owns the stop)
52166
+ `);
52167
+ return;
52168
+ }
52169
+ deps.stopTypingLoop(entry.chatId, entry.threadId);
52170
+ }
51922
52171
  function reap(entry) {
51923
52172
  entry.reapTimer = null;
51924
52173
  if (entry.consumed)
51925
52174
  return;
51926
52175
  entry.consumed = true;
51927
- deps.stopTypingLoop(entry.chatId, entry.threadId);
52176
+ stopTypingUnlessTurnLive(entry, "orphan reap");
51928
52177
  if (entry.activityMessageId != null) {
51929
52178
  const record = {
51930
52179
  turnKey: entry.syntheticTurnKey,
@@ -51951,11 +52200,20 @@ function createHandbackPreturnSignal(deps) {
51951
52200
  return;
51952
52201
  const threadId = inbound.threadId ?? null;
51953
52202
  const adoptTurnId = deps.deriveTurnId(chatId, threadId, inbound.messageId);
51954
- if (adoptTurnId == null)
52203
+ const releaseKey = deps.chatKey(chatId, threadId);
52204
+ if (adoptTurnId == null) {
52205
+ log(`handback-preturn-signal: release key=${releaseKey} armed=no (no derivable turnId)
52206
+ `);
51955
52207
  return;
51956
- const statusKey = deps.chatKey(chatId, threadId);
51957
- if (byKey.has(statusKey))
52208
+ }
52209
+ const statusKey = releaseKey;
52210
+ if (byKey.has(statusKey)) {
52211
+ log(`handback-preturn-signal: release key=${statusKey} turnId=${adoptTurnId} ` + `armed=no (deduped: topic already has a live pre-turn entry)
52212
+ `);
51958
52213
  return;
52214
+ }
52215
+ log(`handback-preturn-signal: release key=${statusKey} turnId=${adoptTurnId} armed=yes
52216
+ `);
51959
52217
  const startedAt = now();
51960
52218
  const syntheticTurnKey = `${PRETURN_TURNKEY_PREFIX}${statusKey}:${startedAt}`;
51961
52219
  const entry = {
@@ -52021,7 +52279,7 @@ function createHandbackPreturnSignal(deps) {
52021
52279
  if (entry == null)
52022
52280
  return;
52023
52281
  entry.consumed = true;
52024
- deps.stopTypingLoop(entry.chatId, entry.threadId);
52282
+ stopTypingUnlessTurnLive(entry, "reaped record");
52025
52283
  dropEntry(entry);
52026
52284
  },
52027
52285
  pendingCount() {
@@ -68058,6 +68316,7 @@ __export(exports_silence_poke, {
68058
68316
  longestInFlightTool: () => longestInFlightTool,
68059
68317
  formatFrameworkFallbackText: () => formatFrameworkFallbackText,
68060
68318
  endTurn: () => endTurn,
68319
+ decideFallbackTeardownNotice: () => decideFallbackTeardownNotice,
68061
68320
  __tickForTests: () => __tickForTests,
68062
68321
  __setDepsForTests: () => __setDepsForTests,
68063
68322
  __resetAllForTests: () => __resetAllForTests2,
@@ -68224,6 +68483,22 @@ function formatFrameworkFallbackText(fallbackKind, silenceMs, inFlightTools = []
68224
68483
  }
68225
68484
  return null;
68226
68485
  }
68486
+ function decideFallbackTeardownNotice(input) {
68487
+ if (input.userAddressedTextDelivered)
68488
+ return { send: false, reason: "user-addressed-text-delivered" };
68489
+ if (!input.tearsDownLiveTurn)
68490
+ return { send: false, reason: "no-live-turn-torn-down" };
68491
+ if (input.role !== "user")
68492
+ return { send: false, reason: "non-user-turn" };
68493
+ if (input.finalAnswerDelivered)
68494
+ return { send: false, reason: "answer-already-delivered" };
68495
+ const minutes = Math.max(1, Math.round(input.silenceMs / 60000));
68496
+ const tail = input.representWillFollow ? "Your message is still tracked and is being re-asked now." : "Your message was not answered \u2014 please re-send it.";
68497
+ return {
68498
+ send: true,
68499
+ text: `\u26a0\ufe0f no output for ${minutes} min \u2014 the framework ended that stalled turn. ${tail}`
68500
+ };
68501
+ }
68227
68502
  function longestInFlightTool(key, now) {
68228
68503
  const s = state2.get(key);
68229
68504
  if (s == null)
@@ -68297,6 +68572,11 @@ function tick(now) {
68297
68572
  const silence = now - zeroAt;
68298
68573
  if (silence < 0)
68299
68574
  continue;
68575
+ if (activeDeps.isTurnLive != null && !activeDeps.isTurnLive(key)) {
68576
+ state2.delete(key);
68577
+ activeDeps.emitMetric({ kind: "silence_poke_orphan_reaped", key, silence_ms: silence });
68578
+ continue;
68579
+ }
68300
68580
  tryMidTurnFloor(key, s, now, false);
68301
68581
  if (!s.fallbackFired && silence >= thresholds.fallback) {
68302
68582
  const ceiling = thresholds.fallbackHardCeiling ?? Number.POSITIVE_INFINITY;
@@ -76716,6 +76996,7 @@ function recordTurnEnd(db3, args) {
76716
76996
  }
76717
76997
  var INTERRUPTED_VIA = new Set([
76718
76998
  "restart",
76999
+ "reaped_stale",
76719
77000
  "sigterm",
76720
77001
  "timeout",
76721
77002
  "unknown"
@@ -77425,6 +77706,22 @@ function formatFrameworkFallbackText2(fallbackKind, silenceMs, inFlightTools = [
77425
77706
  }
77426
77707
  return null;
77427
77708
  }
77709
+ function decideFallbackTeardownNotice2(input) {
77710
+ if (input.userAddressedTextDelivered)
77711
+ return { send: false, reason: "user-addressed-text-delivered" };
77712
+ if (!input.tearsDownLiveTurn)
77713
+ return { send: false, reason: "no-live-turn-torn-down" };
77714
+ if (input.role !== "user")
77715
+ return { send: false, reason: "non-user-turn" };
77716
+ if (input.finalAnswerDelivered)
77717
+ return { send: false, reason: "answer-already-delivered" };
77718
+ const minutes = Math.max(1, Math.round(input.silenceMs / 60000));
77719
+ const tail = input.representWillFollow ? "Your message is still tracked and is being re-asked now." : "Your message was not answered \u2014 please re-send it.";
77720
+ return {
77721
+ send: true,
77722
+ text: `\u26a0\ufe0f no output for ${minutes} min \u2014 the framework ended that stalled turn. ${tail}`
77723
+ };
77724
+ }
77428
77725
 
77429
77726
  // gateway/stream-render.ts
77430
77727
  function handleSessionEvent(deps, ev) {
@@ -77602,9 +77899,11 @@ function handleSessionEvent(deps, ev) {
77602
77899
  next.activityMessageId = handbackAdoption.activityMessageId;
77603
77900
  next.activityEverOpened = true;
77604
77901
  }
77605
- startTurnTypingLoop(ev.chatId, enqThreadIdNum ?? null);
77902
+ process.stderr.write(`telegram gateway: handback pre-turn adopted turnId=${turnId} ` + `key=${handbackAdoption.statusKey} card=${handbackAdoption.activityMessageId ?? "none"}
77903
+ `);
77606
77904
  }
77607
77905
  }
77906
+ startTurnTypingLoop(ev.chatId, enqThreadIdNum ?? null);
77608
77907
  setCurrentTurn(next, statusKey(ev.chatId, enqThreadIdNum));
77609
77908
  scheduleEarlyLivenessOpen(next);
77610
77909
  process.stderr.write(`telegram gateway: ${formatTurnLifecycle("set", "enqueue", next, startedAt)}
@@ -79338,6 +79637,7 @@ function flushOnAgentDisconnect(deps) {
79338
79637
  disposeProgressDriver,
79339
79638
  stopTurnTypingLoops,
79340
79639
  onDanglingTurnsSwept,
79640
+ onTurnKeyEnded,
79341
79641
  log
79342
79642
  } = deps;
79343
79643
  if (agentName3 == null) {
@@ -79349,6 +79649,7 @@ function flushOnAgentDisconnect(deps) {
79349
79649
  activeStatusReactions.delete(key);
79350
79650
  activeReactionMsgIds.delete(key);
79351
79651
  activeTurnStartedAt.delete(key);
79652
+ onTurnKeyEnded?.(key);
79352
79653
  }
79353
79654
  clearActiveReactions3();
79354
79655
  const danglingKeys = [...activeTurnStartedAt.keys()];
@@ -79356,6 +79657,7 @@ function flushOnAgentDisconnect(deps) {
79356
79657
  for (const k of danglingKeys) {
79357
79658
  activeTurnStartedAt.delete(k);
79358
79659
  activeReactionMsgIds.delete(k);
79660
+ onTurnKeyEnded?.(k);
79359
79661
  }
79360
79662
  log(`telegram gateway: disconnect-flush swept ${danglingKeys.length} dangling turn key(s) ` + `post-bridge-death (controller loop missed \u2014 finalize raced disconnect)`);
79361
79663
  onDanglingTurnsSwept?.(danglingKeys);
@@ -81894,13 +82196,14 @@ async function discoverModels(agentName3, opts = {}) {
81894
82196
  }
81895
82197
 
81896
82198
  // ../src/agents/scaffold.ts
81897
- import { dirname as dirname14, isAbsolute, join as join40, relative, resolve as resolve8 } from "node:path";
82199
+ import { dirname as dirname14, isAbsolute, join as join41, relative, resolve as resolve8 } from "node:path";
81898
82200
  init_atomic();
81899
82201
 
81900
82202
  // ../src/agents/agent-uid.ts
81901
82203
  init_peercred();
81902
82204
 
81903
82205
  // ../src/agents/scaffold.ts
82206
+ init_generation_stamp();
81904
82207
  init_schema();
81905
82208
 
81906
82209
  // ../src/config/users.ts
@@ -81911,7 +82214,7 @@ init_merge();
81911
82214
  init_timezone();
81912
82215
 
81913
82216
  // ../src/cli/agent-config.ts
81914
- import { join as join38 } from "node:path";
82217
+ import { join as join39 } from "node:path";
81915
82218
  import { homedir as homedir12 } from "node:os";
81916
82219
 
81917
82220
  // ../src/cli/helpers.ts
@@ -81932,12 +82235,12 @@ var WEBKITE_VAULT_KEYS = new Set([
81932
82235
  init_overlay_loader();
81933
82236
 
81934
82237
  // ../src/cli/agent-config.ts
81935
- var AUDIT_ROOT = join38(homedir12(), ".switchroom", "audit");
82238
+ var AUDIT_ROOT = join39(homedir12(), ".switchroom", "audit");
81936
82239
 
81937
82240
  // ../src/agents/profiles.ts
81938
82241
  var import_handlebars = __toESM(require_lib(), 1);
81939
- import { readFileSync as readFileSync32, writeFileSync as writeFileSync29, existsSync as existsSync33, readdirSync as readdirSync7, statSync as statSync12, copyFileSync, mkdirSync as mkdirSync31, realpathSync as realpathSync2 } from "node:fs";
81940
- import { resolve as resolve7, join as join39, sep as pathSep } from "node:path";
82242
+ import { readFileSync as readFileSync33, writeFileSync as writeFileSync30, existsSync as existsSync34, readdirSync as readdirSync7, statSync as statSync12, copyFileSync, mkdirSync as mkdirSync31, realpathSync as realpathSync2 } from "node:fs";
82243
+ import { resolve as resolve7, join as join40, sep as pathSep } from "node:path";
81941
82244
  function resolveProfilesRoot() {
81942
82245
  const envOverride = process.env.SWITCHROOM_PROFILES_ROOT?.trim();
81943
82246
  if (envOverride) {
@@ -81948,7 +82251,7 @@ function resolveProfilesRoot() {
81948
82251
  resolve7(import.meta.dirname, "profiles")
81949
82252
  ];
81950
82253
  for (const candidate of candidates) {
81951
- if (existsSync33(candidate)) {
82254
+ if (existsSync34(candidate)) {
81952
82255
  return candidate;
81953
82256
  }
81954
82257
  }
@@ -81964,9 +82267,9 @@ import_handlebars.default.registerHelper("isNumber", (value) => {
81964
82267
  var SHARED_FRAGMENTS_DIR = resolve7(PROFILES_ROOT, "_shared");
81965
82268
  var SHARED_FRAGMENTS = ["vault-protocol", "agent-self-service", "execution-discipline", "reply-discipline", "dev-protocol"];
81966
82269
  for (const name of SHARED_FRAGMENTS) {
81967
- const fragPath = join39(SHARED_FRAGMENTS_DIR, `${name}.md.hbs`);
81968
- if (existsSync33(fragPath)) {
81969
- import_handlebars.default.registerPartial(name, readFileSync32(fragPath, "utf-8"));
82270
+ const fragPath = join40(SHARED_FRAGMENTS_DIR, `${name}.md.hbs`);
82271
+ if (existsSync34(fragPath)) {
82272
+ import_handlebars.default.registerPartial(name, readFileSync33(fragPath, "utf-8"));
81970
82273
  }
81971
82274
  }
81972
82275
 
@@ -81983,7 +82286,23 @@ var HINDSIGHT_HEALTHCHECK_PY = 'import urllib.request,sys; sys.exit(0 if urllib.
81983
82286
  var HINDSIGHT_HEALTHCHECK_CMD = `python3 -c '${HINDSIGHT_HEALTHCHECK_PY}'`;
81984
82287
 
81985
82288
  // ../src/memory/hindsight.ts
81986
- var DEFAULT_RETAIN_MISSION = "Extract user preferences, ongoing projects, recurring commitments, " + "important context, and durable facts that should help across future " + "conversations. Skip one-off chatter and temporary task noise, " + "including in-flight workflow/process narration (a sub-task started, " + "paused, or is still running) \u2014 only retain the outcome once a task " + "actually completes or a decision is made.";
82289
+ var DEFAULT_RETAIN_MISSION = "Extract durable facts that will still be true and useful weeks from now: " + "user preferences and standing rules, ongoing projects and recurring " + "commitments, technical and architectural decisions with their rationale, " + "and people/tool relationships. A preference revealed by a request is " + "durable \u2014 record the preference (what the user likes, wants, or always " + `does), not the request itself.
82290
+
82291
+ ` + `NEVER extract:
82292
+ ` + "- Agent tool-use traces or narration of what the assistant did (e.g. " + `"the assistant used X to query Y", "ran a search", "sent the message").
82293
+ ` + "- In-flight workflow/process narration (a sub-task started, paused, or is " + "still running) \u2014 retain the outcome only once the task completes or a " + `decision is made.
82294
+ ` + `- Operation, request, batch or session IDs, UUIDs, hashes, or error codes.
82295
+ ` + "- Hindsight's own errors, retries, backlogs, or internal state \u2014 the " + `memory system's self-reports are not memories.
82296
+ ` + `- Restatements of the user's current request or the task in progress.
82297
+ ` + "- Transient state (unread counts, build status, what is running right now) " + "unless the fact is explicitly dated, in which case record it as a dated " + `observation.
82298
+ ` + `- Greetings, acknowledgements, and routine operational chatter.
82299
+
82300
+ ` + "If a candidate fact matches an exclusion, drop it rather than rewording " + "it. If nothing durable remains, return an empty facts list.";
82301
+ var SUPERSEDED_RETAIN_MISSIONS = [
82302
+ "Extract technical decisions, architectural choices, user preferences, project context, and people/tool relationships. Ignore routine greetings and transient operational details.",
82303
+ "Extract user preferences, ongoing projects, recurring commitments, " + "important context, and durable facts that should help across future " + "conversations. Skip one-off chatter and temporary task noise.",
82304
+ "Extract user preferences, ongoing projects, recurring commitments, " + "important context, and durable facts that should help across future " + "conversations. Skip one-off chatter and temporary task noise, " + "including in-flight workflow/process narration (a sub-task started, " + "paused, or is still running) \u2014 only retain the outcome once a task " + "actually completes or a decision is made."
82305
+ ];
81987
82306
  var PROFILE_MEMORY_DEFAULTS = {
81988
82307
  "health-coach": {
81989
82308
  disposition: { skepticism: 2, literalism: 2, empathy: 5 },
@@ -82028,6 +82347,7 @@ function normalizeModelAlias(model) {
82028
82347
  return model === "claude-fable-5" ? "fable" : model;
82029
82348
  }
82030
82349
  var CLAUDE_MD_YOURS_PLACEHOLDER = "This space is yours. Add per-agent rules, exceptions, or context the " + "Switchroom template doesn't capture. Everything above the marker line is " + "regenerated on every apply; this section is preserved.";
82350
+ var pendingBankOps = new Set;
82031
82351
  var SWITCHROOM_OWNED_SETTINGS_KEYS = new Set([
82032
82352
  "permissions",
82033
82353
  "mcpServers",
@@ -82573,7 +82893,7 @@ init_overlay_loader();
82573
82893
  init_merge();
82574
82894
  init_timezone();
82575
82895
  var import_yaml4 = __toESM(require_dist(), 1);
82576
- import { readFileSync as readFileSync33, existsSync as existsSync34 } from "node:fs";
82896
+ import { readFileSync as readFileSync34, existsSync as existsSync35 } from "node:fs";
82577
82897
  import { homedir as homedir13 } from "node:os";
82578
82898
  import { resolve as resolve9 } from "node:path";
82579
82899
 
@@ -82649,7 +82969,7 @@ function findConfigFile2(startDir) {
82649
82969
  resolve9(userDir, "clerk.yml")
82650
82970
  ].filter(Boolean);
82651
82971
  for (const path2 of searchPaths) {
82652
- if (existsSync34(path2)) {
82972
+ if (existsSync35(path2)) {
82653
82973
  return path2;
82654
82974
  }
82655
82975
  }
@@ -82657,12 +82977,12 @@ function findConfigFile2(startDir) {
82657
82977
  }
82658
82978
  function loadConfig2(configPath) {
82659
82979
  const filePath = configPath ?? findConfigFile2();
82660
- if (!existsSync34(filePath)) {
82980
+ if (!existsSync35(filePath)) {
82661
82981
  throw new ConfigError2(`Config file not found: ${filePath}`);
82662
82982
  }
82663
82983
  let raw;
82664
82984
  try {
82665
- raw = readFileSync33(filePath, "utf-8");
82985
+ raw = readFileSync34(filePath, "utf-8");
82666
82986
  } catch (err) {
82667
82987
  throw new ConfigError2(`Failed to read config file: ${filePath}`, [
82668
82988
  ` ${err.message}`
@@ -83192,15 +83512,15 @@ function topicForRecipient(args) {
83192
83512
  }
83193
83513
 
83194
83514
  // ../src/agents/perf.ts
83195
- import { existsSync as existsSync35, readFileSync as readFileSync34 } from "node:fs";
83515
+ import { existsSync as existsSync36, readFileSync as readFileSync35 } from "node:fs";
83196
83516
  function readTurnUsages(jsonlPath, lastN) {
83197
- if (!existsSync35(jsonlPath))
83517
+ if (!existsSync36(jsonlPath))
83198
83518
  return [];
83199
83519
  if (lastN <= 0)
83200
83520
  return [];
83201
83521
  let raw;
83202
83522
  try {
83203
- raw = readFileSync34(jsonlPath, "utf-8");
83523
+ raw = readFileSync35(jsonlPath, "utf-8");
83204
83524
  } catch {
83205
83525
  return [];
83206
83526
  }
@@ -83291,8 +83611,8 @@ function numField(obj, key) {
83291
83611
  }
83292
83612
 
83293
83613
  // gateway/context-occupancy.ts
83294
- import { mkdirSync as mkdirSync32, writeFileSync as writeFileSync30 } from "node:fs";
83295
- import { join as join41 } from "node:path";
83614
+ import { mkdirSync as mkdirSync32, writeFileSync as writeFileSync31 } from "node:fs";
83615
+ import { join as join42 } from "node:path";
83296
83616
  var CONTEXT_OCCUPANCY_FILENAME = "context-occupancy.json";
83297
83617
  var TIGHT_FRACTION = 0.8;
83298
83618
  function buildContextOccupancy(occupancy, cap, now) {
@@ -83315,9 +83635,9 @@ function buildContextOccupancy(occupancy, cap, now) {
83315
83635
  }
83316
83636
  function writeContextOccupancySnapshot(stateDir, snapshot, deps) {
83317
83637
  try {
83318
- const path2 = join41(stateDir, CONTEXT_OCCUPANCY_FILENAME);
83638
+ const path2 = join42(stateDir, CONTEXT_OCCUPANCY_FILENAME);
83319
83639
  (deps?.mkdir ?? ((p, o) => mkdirSync32(p, o)))(stateDir, { recursive: true });
83320
- (deps?.writeFile ?? ((p, d) => writeFileSync30(p, d)))(path2, JSON.stringify(snapshot, null, 2) + `
83640
+ (deps?.writeFile ?? ((p, d) => writeFileSync31(p, d)))(path2, JSON.stringify(snapshot, null, 2) + `
83321
83641
  `);
83322
83642
  } catch {}
83323
83643
  }
@@ -83471,7 +83791,7 @@ function nextCompactNotify(state6, ev) {
83471
83791
  }
83472
83792
 
83473
83793
  // gateway/hostd-dispatch.ts
83474
- import { existsSync as existsSync36 } from "node:fs";
83794
+ import { existsSync as existsSync37 } from "node:fs";
83475
83795
  import { randomBytes as randomBytes7 } from "node:crypto";
83476
83796
  init_loader();
83477
83797
  var _hostdEnabled2;
@@ -83495,13 +83815,13 @@ function hostdSocketPath2(agentName3) {
83495
83815
  function hostdWillBeUsed2(agentName3) {
83496
83816
  if (!isHostdEnabled2())
83497
83817
  return false;
83498
- return existsSync36(hostdSocketPath2(agentName3));
83818
+ return existsSync37(hostdSocketPath2(agentName3));
83499
83819
  }
83500
83820
  async function tryHostdDispatch2(agentName3, req, timeoutMs = 5000) {
83501
83821
  if (!isHostdEnabled2())
83502
83822
  return "not-configured";
83503
83823
  const sockPath = hostdSocketPath2(agentName3);
83504
- if (!existsSync36(sockPath))
83824
+ if (!existsSync37(sockPath))
83505
83825
  return "not-configured";
83506
83826
  try {
83507
83827
  return await hostdRequest({ socketPath: sockPath, timeoutMs }, req);
@@ -83530,7 +83850,7 @@ async function pollHostdStatus(agentName3, targetRequestId, opts) {
83530
83850
  if (!isHostdEnabled2())
83531
83851
  return "not-configured";
83532
83852
  const sockPath = hostdSocketPath2(agentName3);
83533
- if (!existsSync36(sockPath))
83853
+ if (!existsSync37(sockPath))
83534
83854
  return "not-configured";
83535
83855
  const now = opts.now ?? Date.now;
83536
83856
  const sleep2 = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
@@ -83649,7 +83969,7 @@ function createDmPinSweeper(deps) {
83649
83969
 
83650
83970
  // gateway/webhook-ingest-server.ts
83651
83971
  import net4 from "node:net";
83652
- import { chmodSync as chmodSync10, existsSync as existsSync37, unlinkSync as unlinkSync17 } from "node:fs";
83972
+ import { chmodSync as chmodSync10, existsSync as existsSync38, unlinkSync as unlinkSync17 } from "node:fs";
83653
83973
  var MAX_REQUEST_BYTES = 1024 * 1024;
83654
83974
  function fdOf(conn) {
83655
83975
  const handle = conn._handle;
@@ -83661,7 +83981,7 @@ function startWebhookIngestServer(opts) {
83661
83981
  const log = opts.log ?? ((s) => process.stderr.write(s));
83662
83982
  const allowed = new Set(opts.allowedUids);
83663
83983
  try {
83664
- if (existsSync37(opts.socketPath))
83984
+ if (existsSync38(opts.socketPath))
83665
83985
  unlinkSync17(opts.socketPath);
83666
83986
  } catch (err) {
83667
83987
  log(`webhook-ingest-server: could not unlink stale socket: ${err.message}
@@ -83754,7 +84074,7 @@ function startWebhookIngestServer(opts) {
83754
84074
  server.close();
83755
84075
  } catch {}
83756
84076
  try {
83757
- if (existsSync37(opts.socketPath))
84077
+ if (existsSync38(opts.socketPath))
83758
84078
  unlinkSync17(opts.socketPath);
83759
84079
  } catch {}
83760
84080
  }
@@ -83763,19 +84083,19 @@ function startWebhookIngestServer(opts) {
83763
84083
 
83764
84084
  // ../src/web/webhook-gateway-record.ts
83765
84085
  import { appendFileSync as appendFileSync8, mkdirSync as mkdirSync35 } from "fs";
83766
- import { join as join44 } from "path";
84086
+ import { join as join45 } from "path";
83767
84087
  import { homedir as homedir15 } from "os";
83768
84088
 
83769
84089
  // ../src/web/webhook-handler.ts
83770
- import { appendFileSync as appendFileSync7, existsSync as existsSync38, mkdirSync as mkdirSync33, readFileSync as readFileSync35, writeFileSync as writeFileSync31 } from "fs";
83771
- import { join as join42 } from "path";
84090
+ import { appendFileSync as appendFileSync7, existsSync as existsSync39, mkdirSync as mkdirSync33, readFileSync as readFileSync36, writeFileSync as writeFileSync32 } from "fs";
84091
+ import { join as join43 } from "path";
83772
84092
  var DEDUP_MAX = 1000;
83773
84093
  var DEDUP_TTL_MS = 24 * 60 * 60 * 1000;
83774
84094
  function loadDedupFile(path2) {
83775
84095
  try {
83776
- if (!existsSync38(path2))
84096
+ if (!existsSync39(path2))
83777
84097
  return {};
83778
- const raw = JSON.parse(readFileSync35(path2, "utf-8"));
84098
+ const raw = JSON.parse(readFileSync36(path2, "utf-8"));
83779
84099
  return typeof raw.deliveries === "object" && raw.deliveries !== null ? raw.deliveries : {};
83780
84100
  } catch {
83781
84101
  return {};
@@ -83789,7 +84109,7 @@ function saveDedupFile(path2, deliveries, now) {
83789
84109
  }
83790
84110
  const sorted = Object.entries(pruned).sort((a, b) => b[1] - a[1]).slice(0, DEDUP_MAX);
83791
84111
  const final = Object.fromEntries(sorted);
83792
- writeFileSync31(path2, JSON.stringify({ deliveries: final }), {
84112
+ writeFileSync32(path2, JSON.stringify({ deliveries: final }), {
83793
84113
  mode: 384
83794
84114
  });
83795
84115
  }
@@ -83797,8 +84117,8 @@ var agentDedupCache = new Map;
83797
84117
  function createFileDedupStore(resolveAgentDir) {
83798
84118
  return {
83799
84119
  check(agent, deliveryId, now) {
83800
- const telegramDir = join42(resolveAgentDir(agent), "telegram");
83801
- const filePath = join42(telegramDir, "webhook-dedup.json");
84120
+ const telegramDir = join43(resolveAgentDir(agent), "telegram");
84121
+ const filePath = join43(telegramDir, "webhook-dedup.json");
83802
84122
  if (!agentDedupCache.has(agent)) {
83803
84123
  agentDedupCache.set(agent, loadDedupFile(filePath));
83804
84124
  }
@@ -83819,8 +84139,8 @@ var tokenBuckets = new Map;
83819
84139
  var throttleIssueWindow = new Map;
83820
84140
 
83821
84141
  // ../src/web/webhook-dispatch.ts
83822
- import { existsSync as existsSync39, mkdirSync as mkdirSync34, readFileSync as readFileSync36, writeFileSync as writeFileSync32 } from "fs";
83823
- import { join as join43 } from "path";
84142
+ import { existsSync as existsSync40, mkdirSync as mkdirSync34, readFileSync as readFileSync37, writeFileSync as writeFileSync33 } from "fs";
84143
+ import { join as join44 } from "path";
83824
84144
  import { homedir as homedir14 } from "os";
83825
84145
 
83826
84146
  // ../src/agent-scheduler/ipc-client.ts
@@ -84119,9 +84439,9 @@ function cooldownKey(source, eventType, repo, number, ruleIndex) {
84119
84439
  }
84120
84440
  function loadCooldownFile(path2) {
84121
84441
  try {
84122
- if (!existsSync39(path2))
84442
+ if (!existsSync40(path2))
84123
84443
  return {};
84124
- const raw = JSON.parse(readFileSync36(path2, "utf-8"));
84444
+ const raw = JSON.parse(readFileSync37(path2, "utf-8"));
84125
84445
  return typeof raw.dispatches === "object" && raw.dispatches !== null ? raw.dispatches : {};
84126
84446
  } catch {
84127
84447
  return {};
@@ -84129,7 +84449,7 @@ function loadCooldownFile(path2) {
84129
84449
  }
84130
84450
  function saveCooldownFile(path2, dispatches) {
84131
84451
  try {
84132
- writeFileSync32(path2, JSON.stringify({ dispatches }), {
84452
+ writeFileSync33(path2, JSON.stringify({ dispatches }), {
84133
84453
  mode: 384
84134
84454
  });
84135
84455
  } catch {}
@@ -84140,8 +84460,8 @@ function createFileCooldownStore(resolveAgentDir) {
84140
84460
  isCoolingDown(agent, key, cooldownMs, now) {
84141
84461
  if (cooldownMs <= 0)
84142
84462
  return false;
84143
- const telegramDir = join43(resolveAgentDir(agent), "telegram");
84144
- const filePath = join43(telegramDir, "webhook-cooldown.json");
84463
+ const telegramDir = join44(resolveAgentDir(agent), "telegram");
84464
+ const filePath = join44(telegramDir, "webhook-cooldown.json");
84145
84465
  if (!cache.has(agent)) {
84146
84466
  cache.set(agent, loadCooldownFile(filePath));
84147
84467
  }
@@ -84198,9 +84518,9 @@ async function defaultInject(socketPath, agentName3, inbound) {
84198
84518
  }
84199
84519
  function injectWebhookInbound(agent, prompt, ctx, deps = {}) {
84200
84520
  const log = deps.log ?? ((s) => process.stderr.write(s));
84201
- const resolveAgentDir = deps.resolveAgentDir ?? ((a) => join43(homedir14(), ".switchroom", "agents", a));
84521
+ const resolveAgentDir = deps.resolveAgentDir ?? ((a) => join44(homedir14(), ".switchroom", "agents", a));
84202
84522
  const now = (deps.now ?? Date.now)();
84203
- const socketPath = join43(resolveAgentDir(agent), "telegram", "gateway.sock");
84523
+ const socketPath = join44(resolveAgentDir(agent), "telegram", "gateway.sock");
84204
84524
  const inbound = {
84205
84525
  type: "inbound",
84206
84526
  chatId: ctx.chatId,
@@ -84272,7 +84592,7 @@ function evaluateDispatch(args, deps = {}) {
84272
84592
  const log = deps.log ?? ((s) => process.stderr.write(s));
84273
84593
  const now = (deps.now ?? Date.now)();
84274
84594
  const nowDate = deps.nowDate ?? (() => new Date(now));
84275
- const resolveAgentDir = deps.resolveAgentDir ?? ((a) => join43(homedir14(), ".switchroom", "agents", a));
84595
+ const resolveAgentDir = deps.resolveAgentDir ?? ((a) => join44(homedir14(), ".switchroom", "agents", a));
84276
84596
  const cooldownStore = deps.cooldownStore ?? createFileCooldownStore(resolveAgentDir);
84277
84597
  if (!DISPATCH_SOURCES.includes(args.source))
84278
84598
  return 0;
@@ -84350,10 +84670,10 @@ var CONSOLIDATION_COMPLETED_EVENT = "consolidation.completed";
84350
84670
  function recordWebhookEvent(rec, deps = {}) {
84351
84671
  const log = deps.log ?? ((s) => process.stderr.write(s));
84352
84672
  const now = rec.ts || (deps.now ?? Date.now)();
84353
- const resolveAgentDir = deps.resolveAgentDir ?? ((a) => join44(homedir15(), ".switchroom", "agents", a));
84673
+ const resolveAgentDir = deps.resolveAgentDir ?? ((a) => join45(homedir15(), ".switchroom", "agents", a));
84354
84674
  const dedupStore = deps.dedupStore ?? createFileDedupStore(resolveAgentDir);
84355
84675
  const agent = rec.agent;
84356
- const telegramDir = join44(resolveAgentDir(agent), "telegram");
84676
+ const telegramDir = join45(resolveAgentDir(agent), "telegram");
84357
84677
  if (rec.source === "github" && rec.delivery_id) {
84358
84678
  const originalTs = dedupStore.check(agent, rec.delivery_id, now);
84359
84679
  if (originalTs !== undefined) {
@@ -84362,7 +84682,7 @@ function recordWebhookEvent(rec, deps = {}) {
84362
84682
  return { status: "deduped", ts: originalTs };
84363
84683
  }
84364
84684
  }
84365
- const logPath = join44(telegramDir, "webhook-events.jsonl");
84685
+ const logPath = join45(telegramDir, "webhook-events.jsonl");
84366
84686
  try {
84367
84687
  mkdirSync35(telegramDir, { recursive: true });
84368
84688
  const record = {
@@ -84588,6 +84908,8 @@ function validateClientMessage(msg) {
84588
84908
  return false;
84589
84909
  if (typeof m.timeoutMs !== "number" || !Number.isFinite(m.timeoutMs) || m.timeoutMs <= 0)
84590
84910
  return false;
84911
+ if (m.title !== undefined && (typeof m.title !== "string" || m.title.length === 0 || m.title.length > 200 || /[\u0000-\u001f\u007f]/.test(m.title)))
84912
+ return false;
84591
84913
  return true;
84592
84914
  }
84593
84915
  case "request_config_finalize": {
@@ -85899,6 +86221,22 @@ class ObligationLedger {
85899
86221
  return { action: "escalate", obligation: o };
85900
86222
  return { action: "represent", obligation: o };
85901
86223
  }
86224
+ static representGraceStillProtecting(o, now, representGraceMs, trailingGraceMs) {
86225
+ if (representGraceMs <= 0)
86226
+ return false;
86227
+ if (o.lastRepresentedAt == null)
86228
+ return false;
86229
+ const sinceRepresent = now - o.lastRepresentedAt;
86230
+ if (sinceRepresent >= representGraceMs)
86231
+ return false;
86232
+ const representTurnEnded = o.lastTurnEndedAt != null && o.lastTurnEndedAt > o.lastRepresentedAt;
86233
+ if (representTurnEnded && trailingGraceMs > 0) {
86234
+ const floorMs = Math.min(representGraceMs, Math.max(trailingGraceMs, ObligationLedger.MIN_REPRESENT_INTERVAL_MS));
86235
+ return sinceRepresent < floorMs;
86236
+ }
86237
+ return true;
86238
+ }
86239
+ static MIN_REPRESENT_INTERVAL_MS = 30000;
85902
86240
  oldestEligible(now, graceMs, backgroundWorkActive, backgroundGraceMs, representGraceMs) {
85903
86241
  let best;
85904
86242
  for (const o of this.open.values()) {
@@ -85906,7 +86244,7 @@ class ObligationLedger {
85906
86244
  continue;
85907
86245
  if (backgroundWorkActive && backgroundGraceMs > 0 && now - o.openedAt < backgroundGraceMs)
85908
86246
  continue;
85909
- if (representGraceMs > 0 && o.lastRepresentedAt != null && now - o.lastRepresentedAt < representGraceMs)
86247
+ if (ObligationLedger.representGraceStillProtecting(o, now, representGraceMs, graceMs))
85910
86248
  continue;
85911
86249
  if (best === undefined || o.openedAt < best.openedAt)
85912
86250
  best = o;
@@ -88158,6 +88496,22 @@ class ObligationLedger2 {
88158
88496
  return { action: "escalate", obligation: o };
88159
88497
  return { action: "represent", obligation: o };
88160
88498
  }
88499
+ static representGraceStillProtecting(o, now, representGraceMs, trailingGraceMs) {
88500
+ if (representGraceMs <= 0)
88501
+ return false;
88502
+ if (o.lastRepresentedAt == null)
88503
+ return false;
88504
+ const sinceRepresent = now - o.lastRepresentedAt;
88505
+ if (sinceRepresent >= representGraceMs)
88506
+ return false;
88507
+ const representTurnEnded = o.lastTurnEndedAt != null && o.lastTurnEndedAt > o.lastRepresentedAt;
88508
+ if (representTurnEnded && trailingGraceMs > 0) {
88509
+ const floorMs = Math.min(representGraceMs, Math.max(trailingGraceMs, ObligationLedger2.MIN_REPRESENT_INTERVAL_MS));
88510
+ return sinceRepresent < floorMs;
88511
+ }
88512
+ return true;
88513
+ }
88514
+ static MIN_REPRESENT_INTERVAL_MS = 30000;
88161
88515
  oldestEligible(now, graceMs, backgroundWorkActive, backgroundGraceMs, representGraceMs) {
88162
88516
  let best;
88163
88517
  for (const o of this.open.values()) {
@@ -88165,7 +88519,7 @@ class ObligationLedger2 {
88165
88519
  continue;
88166
88520
  if (backgroundWorkActive && backgroundGraceMs > 0 && now - o.openedAt < backgroundGraceMs)
88167
88521
  continue;
88168
- if (representGraceMs > 0 && o.lastRepresentedAt != null && now - o.lastRepresentedAt < representGraceMs)
88522
+ if (ObligationLedger2.representGraceStillProtecting(o, now, representGraceMs, graceMs))
88169
88523
  continue;
88170
88524
  if (best === undefined || o.openedAt < best.openedAt)
88171
88525
  best = o;
@@ -88540,10 +88894,12 @@ function buildSilencePokeOptions(deps) {
88540
88894
  SILENCE_FALLBACK_HARD_MS,
88541
88895
  SILENCE_FLOOR_MS,
88542
88896
  SILENCE_DEFER_INFLIGHT_TOOLS,
88897
+ isObligationOpenForTurn,
88543
88898
  TURN_PREVIEW_MAX,
88544
88899
  STATE_DIR,
88545
88900
  isLegitimatelyWorking,
88546
88901
  getCurrentTurn,
88902
+ getCurrentTurnForKey,
88547
88903
  getInFlightUpdate,
88548
88904
  getTurnsDb,
88549
88905
  getInboundSpool,
@@ -88569,6 +88925,7 @@ function buildSilencePokeOptions(deps) {
88569
88925
  thresholdsMs: { fallback: SILENCE_FALLBACK_MS, fallbackHardCeiling: SILENCE_FALLBACK_HARD_MS, floor: SILENCE_FLOOR_MS },
88570
88926
  deferFallbackWhileToolInFlight: SILENCE_DEFER_INFLIGHT_TOOLS,
88571
88927
  isLegitimatelyWorking: (key) => isLegitimatelyWorking(key),
88928
+ isTurnLive: (key) => !(activeTurnStartedAt.get(key) == null && getCurrentTurnForKey(key) == null),
88572
88929
  emitMetric: (event) => {
88573
88930
  emitRuntimeMetric2(event);
88574
88931
  },
@@ -88581,7 +88938,7 @@ function buildSilencePokeOptions(deps) {
88581
88938
  return { role: turn.role, finalAnswerDelivered: turn.finalAnswerDelivered };
88582
88939
  },
88583
88940
  onMidTurnFloor: async (ctx) => {
88584
- if (activeTurnStartedAt.get(ctx.key) == null && getCurrentTurn() == null)
88941
+ if (activeTurnStartedAt.get(ctx.key) == null && getCurrentTurnForKey(ctx.key) == null)
88585
88942
  return;
88586
88943
  const blockedOnApproval = activeStatusReactions.get(statusKey(ctx.chatId, ctx.threadId))?.isAwaiting() ?? false;
88587
88944
  if (!blockedOnApproval)
@@ -88598,7 +88955,7 @@ function buildSilencePokeOptions(deps) {
88598
88955
  }
88599
88956
  },
88600
88957
  onFrameworkFallback: async (ctx) => {
88601
- if (activeTurnStartedAt.get(ctx.key) == null && getCurrentTurn() == null) {
88958
+ if (activeTurnStartedAt.get(ctx.key) == null && getCurrentTurnForKey(ctx.key) == null) {
88602
88959
  process.stderr.write(`telegram gateway: silence-poke framework-fallback late-fire skipped \u2014 ` + `turn ended cleanly during silence window ` + `chat=${ctx.chatId} thread=${ctx.threadId ?? "-"} silence_ms=${ctx.silenceMs}
88603
88960
  `);
88604
88961
  logStreamingEvent2({
@@ -88647,9 +89004,11 @@ function buildSilencePokeOptions(deps) {
88647
89004
  silenceMs: ctx.silenceMs,
88648
89005
  fallbackKind: ctx.fallbackKind
88649
89006
  });
89007
+ let userAddressedTextDelivered = false;
88650
89008
  if (text4 != null) {
88651
89009
  try {
88652
89010
  await sendSilenceText(ctx.chatId, ctx.threadId ?? null, text4, blockedOnApproval ? false : true);
89011
+ userAddressedTextDelivered = blockedOnApproval;
88653
89012
  } catch (err) {
88654
89013
  process.stderr.write(`silence-poke fallback sendMessage failed chat=${ctx.chatId} thread=${ctx.threadId}: ${err}
88655
89014
  `);
@@ -88660,6 +89019,7 @@ function buildSilencePokeOptions(deps) {
88660
89019
  const fbThreadId = ctx.threadId ?? undefined;
88661
89020
  const wedgedTurn = getCurrentTurn();
88662
89021
  const turnMatchesFallback = wedgedTurn != null && wedgedTurn.sessionChatId === fbChatId && wedgedTurn.sessionThreadId === fbThreadId;
89022
+ const tearsDownLiveTurn = turnMatchesFallback && wedgedTurn != null && turnLiveForItsTopic(wedgedTurn);
88663
89023
  const turnStartedAt = activeTurnStartedAt.get(fbKey);
88664
89024
  if (turnStartedAt != null) {
88665
89025
  const turnDurationMs = Date.now() - turnStartedAt;
@@ -88717,17 +89077,36 @@ function buildSilencePokeOptions(deps) {
88717
89077
  const sib = silenceMsForKey2(siblingKey, fbNow);
88718
89078
  return sib == null || sib >= DEFAULT_THRESHOLDS2.fallback;
88719
89079
  });
88720
- if (turnMatchesFallback && wedgedTurn != null && turnLiveForItsTopic(wedgedTurn)) {
89080
+ if (tearsDownLiveTurn && wedgedTurn != null) {
88721
89081
  process.stderr.write(`telegram gateway: ${formatTurnLifecycle("clear", "silence_fallback", wedgedTurn, Date.now())}
88722
89082
  `);
88723
89083
  endCurrentTurnForKey(wedgedTurn, fbKey);
88724
89084
  }
89085
+ const teardownNotice = decideFallbackTeardownNotice2({
89086
+ tearsDownLiveTurn,
89087
+ role: wedgedTurn?.role ?? null,
89088
+ finalAnswerDelivered: wedgedTurn?.finalAnswerDelivered ?? false,
89089
+ userAddressedTextDelivered,
89090
+ representWillFollow: isObligationOpenForTurn(wedgedTurn?.turnId ?? null),
89091
+ silenceMs: ctx.silenceMs
89092
+ });
89093
+ if (teardownNotice.send) {
89094
+ try {
89095
+ await sendSilenceText(fbChatId, ctx.threadId ?? null, teardownNotice.text, false);
89096
+ } catch (err) {
89097
+ process.stderr.write(`silence-poke teardown notice sendMessage failed chat=${fbChatId} thread=${ctx.threadId}: ${err}
89098
+ `);
89099
+ }
89100
+ } else {
89101
+ process.stderr.write(`telegram gateway: silence-poke teardown notice skipped reason=${teardownNotice.reason} ` + `chat=${fbChatId} thread=${ctx.threadId ?? "-"}
89102
+ `);
89103
+ }
88725
89104
  try {
88726
89105
  clearSilentEndState2(fbKey);
88727
89106
  } catch {}
88728
89107
  const fbSelfAgent = process.env.SWITCHROOM_AGENT_NAME ?? "";
88729
89108
  const fbRedeliver = redeliverBufferedInbound2(getPendingInboundBuffer(), fbSelfAgent, (m) => sendToAgent(fbSelfAgent, m), getInboundSpool(), trackRedeliveredInbound);
88730
- process.stderr.write(`telegram gateway: silence-poke framework-fallback ended wedged turn ` + `chat=${fbChatId} thread=${ctx.threadId ?? "-"} silence_ms=${ctx.silenceMs} ` + `currentTurn_nulled=${turnMatchesFallback} ` + `drained_buffered=${fbRedeliver.redelivered}/${fbRedeliver.drained}` + `${fbRedeliver.rebuffered > 0 ? ` rebuffered=${fbRedeliver.rebuffered}` : ""}` + `${fbExtraPurge.purged.length > 0 ? ` extra_keys_purged=${fbExtraPurge.purged.length}` : ""}
89109
+ process.stderr.write(`telegram gateway: silence-poke framework-fallback ended wedged turn ` + `chat=${fbChatId} thread=${ctx.threadId ?? "-"} silence_ms=${ctx.silenceMs} ` + `currentTurn_nulled=${tearsDownLiveTurn} ` + `drained_buffered=${fbRedeliver.redelivered}/${fbRedeliver.drained}` + `${fbRedeliver.rebuffered > 0 ? ` rebuffered=${fbRedeliver.rebuffered}` : ""}` + `${fbExtraPurge.purged.length > 0 ? ` extra_keys_purged=${fbExtraPurge.purged.length}` : ""}
88731
89110
  `);
88732
89111
  }
88733
89112
  };
@@ -89248,26 +89627,26 @@ function skillProposalKeyboard(id) {
89248
89627
  // ../src/self-improve/skill-proposals.ts
89249
89628
  import {
89250
89629
  closeSync as closeSync7,
89251
- existsSync as existsSync40,
89630
+ existsSync as existsSync41,
89252
89631
  mkdirSync as mkdirSync36,
89253
89632
  openSync as openSync7,
89254
- readFileSync as readFileSync37,
89633
+ readFileSync as readFileSync38,
89255
89634
  writeSync as writeSync5
89256
89635
  } from "node:fs";
89257
- import { join as join45 } from "node:path";
89636
+ import { join as join46 } from "node:path";
89258
89637
  import { randomUUID as randomUUID5 } from "node:crypto";
89259
89638
  var PROPOSALS_FILE2 = "skill-proposals.jsonl";
89260
89639
  var REJECTED_FILE2 = "skill-proposals-rejected.jsonl";
89261
89640
  var REJECTION_TTL_MS2 = 90 * 24 * 60 * 60 * 1000;
89262
89641
  var PROPOSAL_SIM_THRESHOLD = 0.5;
89263
89642
  function proposalsPath2(stateDir) {
89264
- return join45(stateDir, PROPOSALS_FILE2);
89643
+ return join46(stateDir, PROPOSALS_FILE2);
89265
89644
  }
89266
89645
  function rejectedPath2(stateDir) {
89267
- return join45(stateDir, REJECTED_FILE2);
89646
+ return join46(stateDir, REJECTED_FILE2);
89268
89647
  }
89269
89648
  function ensureDir3(stateDir) {
89270
- if (!existsSync40(stateDir)) {
89649
+ if (!existsSync41(stateDir)) {
89271
89650
  mkdirSync36(stateDir, { recursive: true, mode: 493 });
89272
89651
  }
89273
89652
  }
@@ -89281,11 +89660,11 @@ function appendLine2(path2, obj) {
89281
89660
  }
89282
89661
  }
89283
89662
  function readLines2(path2, isValid2) {
89284
- if (!existsSync40(path2))
89663
+ if (!existsSync41(path2))
89285
89664
  return [];
89286
89665
  let raw;
89287
89666
  try {
89288
- raw = readFileSync37(path2, "utf-8");
89667
+ raw = readFileSync38(path2, "utf-8");
89289
89668
  } catch {
89290
89669
  return [];
89291
89670
  }
@@ -89882,10 +90261,10 @@ function escapeBody2(s) {
89882
90261
  }
89883
90262
 
89884
90263
  // gateway/pid-file.ts
89885
- import { writeFileSync as writeFileSync33, readFileSync as readFileSync38, unlinkSync as unlinkSync19, renameSync as renameSync15 } from "node:fs";
90264
+ import { writeFileSync as writeFileSync34, readFileSync as readFileSync39, unlinkSync as unlinkSync19, renameSync as renameSync15 } from "node:fs";
89886
90265
  function writePidFile(path2, record) {
89887
90266
  const tmp = `${path2}.tmp-${process.pid}-${Date.now()}`;
89888
- writeFileSync33(tmp, JSON.stringify(record), "utf-8");
90267
+ writeFileSync34(tmp, JSON.stringify(record), "utf-8");
89889
90268
  renameSync15(tmp, path2);
89890
90269
  }
89891
90270
  function clearPidFile(path2) {
@@ -89901,10 +90280,10 @@ import {
89901
90280
  writeFile as writeFileAsync,
89902
90281
  readFile as readFileAsync
89903
90282
  } from "node:fs/promises";
89904
- import { readFileSync as readFileSync39 } from "node:fs";
90283
+ import { readFileSync as readFileSync40 } from "node:fs";
89905
90284
  function readCurrentBootId() {
89906
90285
  try {
89907
- const stat = readFileSync39("/proc/1/stat", "utf-8");
90286
+ const stat = readFileSync40("/proc/1/stat", "utf-8");
89908
90287
  const lastParen = stat.lastIndexOf(")");
89909
90288
  if (lastParen < 0)
89910
90289
  return null;
@@ -90107,15 +90486,15 @@ function safeCount(fn) {
90107
90486
  }
90108
90487
 
90109
90488
  // gateway/session-marker.ts
90110
- import { writeFileSync as writeFileSync34, readFileSync as readFileSync40, renameSync as renameSync16, unlinkSync as unlinkSync20 } from "node:fs";
90489
+ import { writeFileSync as writeFileSync35, readFileSync as readFileSync41, renameSync as renameSync16, unlinkSync as unlinkSync20 } from "node:fs";
90111
90490
  function writeSessionMarker(path2, marker) {
90112
90491
  const tmp = `${path2}.tmp-${process.pid}-${Date.now()}`;
90113
- writeFileSync34(tmp, JSON.stringify(marker), "utf-8");
90492
+ writeFileSync35(tmp, JSON.stringify(marker), "utf-8");
90114
90493
  renameSync16(tmp, path2);
90115
90494
  }
90116
90495
  function readSessionMarker(path2) {
90117
90496
  try {
90118
- const raw = readFileSync40(path2, "utf-8");
90497
+ const raw = readFileSync41(path2, "utf-8");
90119
90498
  const parsed = JSON.parse(raw);
90120
90499
  if (typeof parsed.pid === "number" && typeof parsed.startedAtMs === "number" && Number.isFinite(parsed.pid) && Number.isFinite(parsed.startedAtMs)) {
90121
90500
  return { pid: parsed.pid, startedAtMs: parsed.startedAtMs };
@@ -90137,16 +90516,16 @@ function shouldFireRestartBanner(input) {
90137
90516
  }
90138
90517
 
90139
90518
  // gateway/clean-shutdown-marker.ts
90140
- import { writeFileSync as writeFileSync35, readFileSync as readFileSync41, renameSync as renameSync17, unlinkSync as unlinkSync21 } from "node:fs";
90519
+ import { writeFileSync as writeFileSync36, readFileSync as readFileSync42, renameSync as renameSync17, unlinkSync as unlinkSync21 } from "node:fs";
90141
90520
  var DEFAULT_MAX_AGE_MS = 60000;
90142
90521
  function writeCleanShutdownMarker(path2, marker) {
90143
90522
  const tmp = `${path2}.tmp-${process.pid}-${Date.now()}`;
90144
- writeFileSync35(tmp, JSON.stringify(marker), "utf-8");
90523
+ writeFileSync36(tmp, JSON.stringify(marker), "utf-8");
90145
90524
  renameSync17(tmp, path2);
90146
90525
  }
90147
90526
  function readCleanShutdownMarker(path2) {
90148
90527
  try {
90149
- const raw = readFileSync41(path2, "utf-8");
90528
+ const raw = readFileSync42(path2, "utf-8");
90150
90529
  const parsed = JSON.parse(raw);
90151
90530
  if (typeof parsed.ts === "number" && Number.isFinite(parsed.ts) && typeof parsed.signal === "string" && parsed.signal.length > 0) {
90152
90531
  const out = { ts: parsed.ts, signal: parsed.signal };
@@ -90518,16 +90897,16 @@ function classifyAdminGate(text4, myAgentName) {
90518
90897
 
90519
90898
  // subagent-watcher.ts
90520
90899
  import {
90521
- existsSync as existsSync41,
90900
+ existsSync as existsSync42,
90522
90901
  openSync as openSync8,
90523
90902
  readSync as readSync2,
90524
90903
  statSync as statSync13,
90525
90904
  closeSync as closeSync8,
90526
90905
  watch,
90527
90906
  readdirSync as readdirSync8,
90528
- readFileSync as readFileSync42
90907
+ readFileSync as readFileSync43
90529
90908
  } from "fs";
90530
- import { join as join46 } from "path";
90909
+ import { join as join47 } from "path";
90531
90910
 
90532
90911
  // session-tail.ts
90533
90912
  function sanitizeCwdToProjectName2(cwd) {
@@ -90877,7 +91256,7 @@ function backfillJsonlAgentId(db3, jsonlPath, agentId, log) {
90877
91256
  const metaPath = jsonlPath.replace(/\.jsonl$/, ".meta.json");
90878
91257
  let meta;
90879
91258
  try {
90880
- const raw = readFileSync42(metaPath, "utf8");
91259
+ const raw = readFileSync43(metaPath, "utf8");
90881
91260
  meta = JSON.parse(raw);
90882
91261
  } catch {
90883
91262
  log?.(`subagent-watcher: backfill skip ${agentId} \u2014 meta.json not readable at ${metaPath}`);
@@ -91332,7 +91711,7 @@ function startSubagentWatcher(config) {
91332
91711
  clearTimeout(ref.ref);
91333
91712
  });
91334
91713
  const fs2 = config.fs ?? {
91335
- existsSync: existsSync41,
91714
+ existsSync: existsSync42,
91336
91715
  readdirSync: readdirSync8,
91337
91716
  statSync: statSync13,
91338
91717
  openSync: openSync8,
@@ -91850,8 +92229,8 @@ function startSubagentWatcher(config) {
91850
92229
  if (stopped)
91851
92230
  return;
91852
92231
  pruneVanishedDirWatchers();
91853
- const claudeHome = join46(agentDir, ".claude");
91854
- const projectsRoot = join46(claudeHome, "projects");
92232
+ const claudeHome = join47(agentDir, ".claude");
92233
+ const projectsRoot = join47(claudeHome, "projects");
91855
92234
  if (!fs2.existsSync(projectsRoot))
91856
92235
  return;
91857
92236
  let projectDirs;
@@ -91885,7 +92264,7 @@ function startSubagentWatcher(config) {
91885
92264
  continue;
91886
92265
  }
91887
92266
  warnedForeignSlugs.delete(pDir);
91888
- const projectPath = join46(projectsRoot, pDir);
92267
+ const projectPath = join47(projectsRoot, pDir);
91889
92268
  let sessionDirs;
91890
92269
  try {
91891
92270
  sessionDirs = fs2.readdirSync(projectPath);
@@ -91895,7 +92274,7 @@ function startSubagentWatcher(config) {
91895
92274
  for (const sDir of sessionDirs) {
91896
92275
  if (sDir.endsWith(".jsonl"))
91897
92276
  continue;
91898
- const subagentsPath = join46(projectPath, sDir, "subagents");
92277
+ const subagentsPath = join47(projectPath, sDir, "subagents");
91899
92278
  if (!fs2.existsSync(subagentsPath))
91900
92279
  continue;
91901
92280
  const watchAndScan = (dirPath) => {
@@ -91904,7 +92283,7 @@ function startSubagentWatcher(config) {
91904
92283
  const w = fs2.watch(dirPath, (_event, filename) => {
91905
92284
  if (!filename || !filename.toString().startsWith("agent-") || !filename.toString().endsWith(".jsonl"))
91906
92285
  return;
91907
- const filePath = join46(dirPath, filename.toString());
92286
+ const filePath = join47(dirPath, filename.toString());
91908
92287
  if (!knownFiles.has(filePath)) {
91909
92288
  scanSubagentsDir(dirPath);
91910
92289
  }
@@ -91918,7 +92297,7 @@ function startSubagentWatcher(config) {
91918
92297
  scanSubagentsDir(dirPath);
91919
92298
  };
91920
92299
  watchAndScan(subagentsPath);
91921
- const workflowsPath = join46(subagentsPath, "workflows");
92300
+ const workflowsPath = join47(subagentsPath, "workflows");
91922
92301
  if (fs2.existsSync(workflowsPath)) {
91923
92302
  let wfDirs;
91924
92303
  try {
@@ -91928,7 +92307,7 @@ function startSubagentWatcher(config) {
91928
92307
  }
91929
92308
  for (const wfDir of wfDirs) {
91930
92309
  try {
91931
- const wfPath = join46(workflowsPath, wfDir);
92310
+ const wfPath = join47(workflowsPath, wfDir);
91932
92311
  if (!fs2.statSync(wfPath).isDirectory())
91933
92312
  continue;
91934
92313
  watchAndScan(wfPath);
@@ -91948,7 +92327,7 @@ function startSubagentWatcher(config) {
91948
92327
  for (const e of entries) {
91949
92328
  if (!e.startsWith("agent-") || !e.endsWith(".jsonl"))
91950
92329
  continue;
91951
- const filePath = join46(subagentsPath, e);
92330
+ const filePath = join47(subagentsPath, e);
91952
92331
  if (knownFiles.has(filePath))
91953
92332
  continue;
91954
92333
  const agentId = e.slice("agent-".length, -".jsonl".length);
@@ -92077,20 +92456,20 @@ function startSubagentWatcher(config) {
92077
92456
  // ../src/worktree/registry.ts
92078
92457
  import {
92079
92458
  mkdirSync as mkdirSync37,
92080
- writeFileSync as writeFileSync36,
92081
- readFileSync as readFileSync43,
92459
+ writeFileSync as writeFileSync37,
92460
+ readFileSync as readFileSync44,
92082
92461
  readdirSync as readdirSync9,
92083
92462
  unlinkSync as unlinkSync22,
92084
- existsSync as existsSync42,
92463
+ existsSync as existsSync43,
92085
92464
  renameSync as renameSync18
92086
92465
  } from "node:fs";
92087
- import { join as join47, resolve as resolve10 } from "node:path";
92466
+ import { join as join48, resolve as resolve10 } from "node:path";
92088
92467
  import { homedir as homedir16 } from "node:os";
92089
92468
  function registryDir() {
92090
- return resolve10(process.env.SWITCHROOM_WORKTREE_DIR ?? join47(homedir16(), ".switchroom", "worktrees"));
92469
+ return resolve10(process.env.SWITCHROOM_WORKTREE_DIR ?? join48(homedir16(), ".switchroom", "worktrees"));
92091
92470
  }
92092
92471
  function recordPath(id) {
92093
- return join47(registryDir(), `${id}.json`);
92472
+ return join48(registryDir(), `${id}.json`);
92094
92473
  }
92095
92474
  function ensureDir4() {
92096
92475
  mkdirSync37(registryDir(), { recursive: true });
@@ -92099,14 +92478,14 @@ function writeRecord(record) {
92099
92478
  ensureDir4();
92100
92479
  const target = recordPath(record.id);
92101
92480
  const tmp = `${target}.tmp${process.pid}`;
92102
- writeFileSync36(tmp, JSON.stringify(record, null, 2) + `
92481
+ writeFileSync37(tmp, JSON.stringify(record, null, 2) + `
92103
92482
  `, { mode: 384 });
92104
92483
  renameSync18(tmp, target);
92105
92484
  }
92106
92485
  function readRecord(id) {
92107
92486
  const path2 = recordPath(id);
92108
92487
  try {
92109
- const raw = readFileSync43(path2, "utf8");
92488
+ const raw = readFileSync44(path2, "utf8");
92110
92489
  return JSON.parse(raw);
92111
92490
  } catch {
92112
92491
  return null;
@@ -92136,7 +92515,7 @@ function touchHeartbeat(id, onAfterRead) {
92136
92515
  writeRecord({ ...rec, heartbeatAt: new Date().toISOString() });
92137
92516
  }
92138
92517
  function recordExists(id) {
92139
- return existsSync42(recordPath(id));
92518
+ return existsSync43(recordPath(id));
92140
92519
  }
92141
92520
 
92142
92521
  // worktree-watch-cwds.ts
@@ -92268,15 +92647,15 @@ function determineRestartReason(opts) {
92268
92647
  init_boot_card();
92269
92648
 
92270
92649
  // gateway/update-announce.ts
92271
- import { existsSync as existsSync47, mkdirSync as mkdirSync41, openSync as openSync9, closeSync as closeSync9, readFileSync as readFileSync50 } from "node:fs";
92272
- import { join as join52 } from "node:path";
92650
+ import { existsSync as existsSync48, mkdirSync as mkdirSync41, openSync as openSync9, closeSync as closeSync9, readFileSync as readFileSync51 } from "node:fs";
92651
+ import { join as join53 } from "node:path";
92273
92652
  import { homedir as homedir18 } from "node:os";
92274
92653
 
92275
92654
  // ../src/host-control/audit-reader.ts
92276
92655
  import { homedir as homedir17 } from "node:os";
92277
- import { join as join51 } from "node:path";
92656
+ import { join as join52 } from "node:path";
92278
92657
  function defaultAuditLogPath(home2 = homedir17()) {
92279
- return join51(home2, ".switchroom", "host-control-audit.log");
92658
+ return join52(home2, ".switchroom", "host-control-audit.log");
92280
92659
  }
92281
92660
  function parseAuditLine(line) {
92282
92661
  const trimmed = line.trim();
@@ -92399,8 +92778,8 @@ function readAndFilter(raw, filters, limit) {
92399
92778
  var DEFAULT_LOOKBACK_MS = 10 * 60 * 1000;
92400
92779
  function readLastTerminalUpdateAudit(opts = {}) {
92401
92780
  const path2 = opts.auditLogPath ?? defaultAuditLogPath();
92402
- const exists = opts.exists ?? existsSync47;
92403
- const readFile = opts.readFile ?? ((p) => readFileSync50(p, "utf-8"));
92781
+ const exists = opts.exists ?? existsSync48;
92782
+ const readFile = opts.readFile ?? ((p) => readFileSync51(p, "utf-8"));
92404
92783
  if (!exists(path2))
92405
92784
  return null;
92406
92785
  let raw;
@@ -92461,15 +92840,15 @@ function renderUpdateOutcomeLine(entry) {
92461
92840
  `);
92462
92841
  }
92463
92842
  function claimUpdateAnnouncement(requestId, opts = {}) {
92464
- const stateDir = opts.stateDir ?? process.env.TELEGRAM_STATE_DIR ?? join52(homedir18(), ".switchroom");
92465
- const dir = join52(stateDir, "update-announced");
92843
+ const stateDir = opts.stateDir ?? process.env.TELEGRAM_STATE_DIR ?? join53(homedir18(), ".switchroom");
92844
+ const dir = join53(stateDir, "update-announced");
92466
92845
  try {
92467
92846
  mkdirSync41(dir, { recursive: true });
92468
92847
  } catch {
92469
92848
  return false;
92470
92849
  }
92471
92850
  const safeId = requestId.replace(/[^A-Za-z0-9_.-]/g, "_").slice(0, 200);
92472
- const path2 = join52(dir, safeId);
92851
+ const path2 = join53(dir, safeId);
92473
92852
  try {
92474
92853
  const fd = openSync9(path2, "wx");
92475
92854
  closeSync9(fd);
@@ -92489,7 +92868,7 @@ function maybeRenderUpdateAnnouncement(opts = {}) {
92489
92868
 
92490
92869
  // issues-card.ts
92491
92870
  init_card_format();
92492
- import { readFileSync as readFileSync51, writeFileSync as writeFileSync41 } from "node:fs";
92871
+ import { readFileSync as readFileSync52, writeFileSync as writeFileSync42 } from "node:fs";
92493
92872
  var SEVERITY_EMOJI = {
92494
92873
  info: "\u2139\ufe0f",
92495
92874
  warn: "\u26a0\ufe0f",
@@ -92581,7 +92960,7 @@ function extractRetryAfterSecs2(err) {
92581
92960
  var COOLDOWN_JITTER_MS2 = 500;
92582
92961
  function readPersistedMessageId(path2, log) {
92583
92962
  try {
92584
- const raw = readFileSync51(path2, "utf8");
92963
+ const raw = readFileSync52(path2, "utf8");
92585
92964
  const parsed = JSON.parse(raw);
92586
92965
  const v = parsed.messageId;
92587
92966
  if (typeof v === "number" && Number.isInteger(v) && v > 0)
@@ -92597,7 +92976,7 @@ function readPersistedMessageId(path2, log) {
92597
92976
  }
92598
92977
  function writePersistedMessageId(path2, messageId, log) {
92599
92978
  try {
92600
- writeFileSync41(path2, JSON.stringify({ messageId }) + `
92979
+ writeFileSync42(path2, JSON.stringify({ messageId }) + `
92601
92980
  `, { mode: 384 });
92602
92981
  } catch (err) {
92603
92982
  log(`issues-card: persist write failed (${err.message})`);
@@ -92690,24 +93069,24 @@ function createIssuesCardHandle(opts) {
92690
93069
  }
92691
93070
 
92692
93071
  // issues-watcher.ts
92693
- import { existsSync as existsSync49, statSync as statSync16 } from "node:fs";
92694
- import { join as join54 } from "node:path";
93072
+ import { existsSync as existsSync50, statSync as statSync16 } from "node:fs";
93073
+ import { join as join55 } from "node:path";
92695
93074
 
92696
93075
  // ../src/issues/store.ts
92697
93076
  import {
92698
93077
  closeSync as closeSync10,
92699
- existsSync as existsSync48,
93078
+ existsSync as existsSync49,
92700
93079
  mkdirSync as mkdirSync42,
92701
93080
  openSync as openSync10,
92702
93081
  readdirSync as readdirSync11,
92703
- readFileSync as readFileSync52,
93082
+ readFileSync as readFileSync53,
92704
93083
  renameSync as renameSync21,
92705
93084
  statSync as statSync15,
92706
93085
  unlinkSync as unlinkSync23,
92707
- writeFileSync as writeFileSync42,
93086
+ writeFileSync as writeFileSync43,
92708
93087
  writeSync as writeSync6
92709
93088
  } from "node:fs";
92710
- import { join as join53 } from "node:path";
93089
+ import { join as join54 } from "node:path";
92711
93090
  import { randomBytes as randomBytes8 } from "node:crypto";
92712
93091
  import { execSync } from "node:child_process";
92713
93092
 
@@ -92726,12 +93105,12 @@ init_redact();
92726
93105
  var ISSUES_FILE = "issues.jsonl";
92727
93106
  var ISSUES_LOCK = "issues.lock";
92728
93107
  function readAll(stateDir) {
92729
- const path2 = join53(stateDir, ISSUES_FILE);
92730
- if (!existsSync48(path2))
93108
+ const path2 = join54(stateDir, ISSUES_FILE);
93109
+ if (!existsSync49(path2))
92731
93110
  return [];
92732
93111
  let raw;
92733
93112
  try {
92734
- raw = readFileSync52(path2, "utf-8");
93113
+ raw = readFileSync53(path2, "utf-8");
92735
93114
  } catch {
92736
93115
  return [];
92737
93116
  }
@@ -92763,7 +93142,7 @@ function list2(stateDir, opts = {}) {
92763
93142
  });
92764
93143
  }
92765
93144
  function resolve11(stateDir, fingerprint, nowFn = Date.now) {
92766
- if (!existsSync48(join53(stateDir, ISSUES_FILE)))
93145
+ if (!existsSync49(join54(stateDir, ISSUES_FILE)))
92767
93146
  return 0;
92768
93147
  return withLock(stateDir, () => {
92769
93148
  const all2 = readAll(stateDir);
@@ -92781,13 +93160,13 @@ function resolve11(stateDir, fingerprint, nowFn = Date.now) {
92781
93160
  });
92782
93161
  }
92783
93162
  function writeAll(stateDir, events) {
92784
- const path2 = join53(stateDir, ISSUES_FILE);
93163
+ const path2 = join54(stateDir, ISSUES_FILE);
92785
93164
  sweepOrphanTmpFiles(stateDir);
92786
93165
  const tmp = `${path2}.tmp-${process.pid}-${randomBytes8(4).toString("hex")}`;
92787
93166
  const body = events.length === 0 ? "" : events.map((e) => JSON.stringify(e)).join(`
92788
93167
  `) + `
92789
93168
  `;
92790
- writeFileSync42(tmp, body, "utf-8");
93169
+ writeFileSync43(tmp, body, "utf-8");
92791
93170
  renameSync21(tmp, path2);
92792
93171
  }
92793
93172
  var ORPHAN_TMP_TTL_MS = 60000;
@@ -92803,7 +93182,7 @@ function sweepOrphanTmpFiles(stateDir) {
92803
93182
  for (const entry of entries) {
92804
93183
  if (!entry.startsWith(TMP_PREFIX))
92805
93184
  continue;
92806
- const tmpPath2 = join53(stateDir, entry);
93185
+ const tmpPath2 = join54(stateDir, entry);
92807
93186
  try {
92808
93187
  const stat = statSync15(tmpPath2);
92809
93188
  if (stat.mtimeMs < cutoff) {
@@ -92815,7 +93194,7 @@ function sweepOrphanTmpFiles(stateDir) {
92815
93194
  var LOCK_RETRY_MS = 25;
92816
93195
  var LOCK_TIMEOUT_MS = 1e4;
92817
93196
  function withLock(stateDir, fn) {
92818
- const lockPath = join53(stateDir, ISSUES_LOCK);
93197
+ const lockPath = join54(stateDir, ISSUES_LOCK);
92819
93198
  const startedAt = Date.now();
92820
93199
  let fd = null;
92821
93200
  while (fd === null) {
@@ -92850,7 +93229,7 @@ function withLock(stateDir, fn) {
92850
93229
  function tryStealStaleLock(lockPath) {
92851
93230
  let pidStr;
92852
93231
  try {
92853
- pidStr = readFileSync52(lockPath, "utf-8").trim();
93232
+ pidStr = readFileSync53(lockPath, "utf-8").trim();
92854
93233
  } catch {
92855
93234
  return true;
92856
93235
  }
@@ -92900,7 +93279,7 @@ function isIssueEvent(v) {
92900
93279
  // issues-watcher.ts
92901
93280
  var DEFAULT_POLL_INTERVAL_MS2 = 2000;
92902
93281
  function startIssuesWatcher(opts) {
92903
- const path2 = join54(opts.stateDir, ISSUES_FILE);
93282
+ const path2 = join55(opts.stateDir, ISSUES_FILE);
92904
93283
  const log = opts.log ?? (() => {});
92905
93284
  const intervalMs = opts.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS2;
92906
93285
  const setIntervalFn = opts.setInterval ?? setInterval;
@@ -92948,7 +93327,7 @@ function startIssuesWatcher(opts) {
92948
93327
  };
92949
93328
  }
92950
93329
  function defaultSignatureProvider(path2) {
92951
- if (!existsSync49(path2))
93330
+ if (!existsSync50(path2))
92952
93331
  return null;
92953
93332
  try {
92954
93333
  const stat = statSync16(path2);
@@ -93659,8 +94038,8 @@ function readBashCommand(inputPreview) {
93659
94038
  }
93660
94039
 
93661
94040
  // gateway/scoped-grant-store.ts
93662
- import { readFileSync as readFileSync53, writeFileSync as writeFileSync43 } from "node:fs";
93663
- import { join as join55 } from "node:path";
94041
+ import { readFileSync as readFileSync54, writeFileSync as writeFileSync44 } from "node:fs";
94042
+ import { join as join56 } from "node:path";
93664
94043
 
93665
94044
  // scoped-approval.ts
93666
94045
  var SCOPED_APPROVAL_DEFAULT_TTL_MS2 = 30 * 60 * 1000;
@@ -93702,11 +94081,11 @@ function scopedGrantPersistEnabled(env = process.env) {
93702
94081
  return env.SWITCHROOM_SCOPED_GRANT_PERSIST !== "0";
93703
94082
  }
93704
94083
  function createScopedGrantStore(stateDir, env = process.env) {
93705
- const filePath = join55(stateDir, "scoped-grants.json");
94084
+ const filePath = join56(stateDir, "scoped-grants.json");
93706
94085
  const enabled7 = scopedGrantPersistEnabled(env);
93707
94086
  function read() {
93708
94087
  try {
93709
- const raw = readFileSync53(filePath, "utf-8");
94088
+ const raw = readFileSync54(filePath, "utf-8");
93710
94089
  const parsed = JSON.parse(raw);
93711
94090
  return Array.isArray(parsed) ? parsed : [];
93712
94091
  } catch {
@@ -93724,7 +94103,7 @@ function createScopedGrantStore(stateDir, env = process.env) {
93724
94103
  if (!enabled7)
93725
94104
  return;
93726
94105
  try {
93727
- writeFileSync43(filePath, JSON.stringify(serializeScopedGrants(store2)), {
94106
+ writeFileSync44(filePath, JSON.stringify(serializeScopedGrants(store2)), {
93728
94107
  encoding: "utf-8",
93729
94108
  mode: 384
93730
94109
  });
@@ -94075,8 +94454,8 @@ function isDiffPreApproved(agentName3, unifiedDiff, deps) {
94075
94454
 
94076
94455
  // credits-watch.ts
94077
94456
  init_card_format();
94078
- import { readFileSync as readFileSync54, writeFileSync as writeFileSync44, existsSync as existsSync50, mkdirSync as mkdirSync43 } from "fs";
94079
- import { join as join56 } from "path";
94457
+ import { readFileSync as readFileSync55, writeFileSync as writeFileSync45, existsSync as existsSync51, mkdirSync as mkdirSync43 } from "fs";
94458
+ import { join as join57 } from "path";
94080
94459
  var STATE_FILE = "credits-watch.json";
94081
94460
  var DEFAULT_CREDIT_FATAL_REASONS = new Set;
94082
94461
  var KNOWN_CREDIT_REASONS = [
@@ -94098,12 +94477,12 @@ function emptyCreditState() {
94098
94477
  return { lastNotifiedReason: null, lastNotifiedAt: 0 };
94099
94478
  }
94100
94479
  function readClaudeJsonOverage(claudeConfigDir) {
94101
- const path2 = join56(claudeConfigDir, ".claude.json");
94102
- if (!existsSync50(path2))
94480
+ const path2 = join57(claudeConfigDir, ".claude.json");
94481
+ if (!existsSync51(path2))
94103
94482
  return null;
94104
94483
  let raw;
94105
94484
  try {
94106
- raw = readFileSync54(path2, "utf-8");
94485
+ raw = readFileSync55(path2, "utf-8");
94107
94486
  } catch {
94108
94487
  return null;
94109
94488
  }
@@ -94181,11 +94560,11 @@ function humanizeReason(reason) {
94181
94560
  }
94182
94561
  }
94183
94562
  function loadCreditState(stateDir) {
94184
- const path2 = join56(stateDir, STATE_FILE);
94185
- if (!existsSync50(path2))
94563
+ const path2 = join57(stateDir, STATE_FILE);
94564
+ if (!existsSync51(path2))
94186
94565
  return emptyCreditState();
94187
94566
  try {
94188
- const raw = readFileSync54(path2, "utf-8");
94567
+ const raw = readFileSync55(path2, "utf-8");
94189
94568
  const parsed = JSON.parse(raw);
94190
94569
  if (parsed && typeof parsed === "object" && (parsed.lastNotifiedReason === null || typeof parsed.lastNotifiedReason === "string") && typeof parsed.lastNotifiedAt === "number" && Number.isFinite(parsed.lastNotifiedAt)) {
94191
94570
  return {
@@ -94198,16 +94577,16 @@ function loadCreditState(stateDir) {
94198
94577
  }
94199
94578
  function saveCreditState(stateDir, state7) {
94200
94579
  mkdirSync43(stateDir, { recursive: true });
94201
- const path2 = join56(stateDir, STATE_FILE);
94202
- writeFileSync44(path2, JSON.stringify(state7, null, 2) + `
94580
+ const path2 = join57(stateDir, STATE_FILE);
94581
+ writeFileSync45(path2, JSON.stringify(state7, null, 2) + `
94203
94582
  `, { mode: 384 });
94204
94583
  }
94205
94584
 
94206
94585
  // quota-watch.ts
94207
94586
  init_auth_snapshot_format();
94208
94587
  init_card_format();
94209
- import { readFileSync as readFileSync55, writeFileSync as writeFileSync45, existsSync as existsSync51, mkdirSync as mkdirSync44 } from "fs";
94210
- import { join as join57 } from "path";
94588
+ import { readFileSync as readFileSync56, writeFileSync as writeFileSync46, existsSync as existsSync52, mkdirSync as mkdirSync44 } from "fs";
94589
+ import { join as join58 } from "path";
94211
94590
  var STATE_FILE2 = "quota-watch.json";
94212
94591
  function emptyQuotaWatchState() {
94213
94592
  return {};
@@ -94445,11 +94824,11 @@ function buildRecoveryMessage(agentName3, snap) {
94445
94824
  `);
94446
94825
  }
94447
94826
  function loadQuotaWatchState(stateDir) {
94448
- const path2 = join57(stateDir, STATE_FILE2);
94449
- if (!existsSync51(path2))
94827
+ const path2 = join58(stateDir, STATE_FILE2);
94828
+ if (!existsSync52(path2))
94450
94829
  return emptyQuotaWatchState();
94451
94830
  try {
94452
- const raw = readFileSync55(path2, "utf-8");
94831
+ const raw = readFileSync56(path2, "utf-8");
94453
94832
  const parsed = JSON.parse(raw);
94454
94833
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
94455
94834
  return emptyQuotaWatchState();
@@ -94467,8 +94846,8 @@ function loadQuotaWatchState(stateDir) {
94467
94846
  }
94468
94847
  function saveQuotaWatchState(stateDir, state7) {
94469
94848
  mkdirSync44(stateDir, { recursive: true });
94470
- const path2 = join57(stateDir, STATE_FILE2);
94471
- writeFileSync45(path2, JSON.stringify(state7, null, 2) + `
94849
+ const path2 = join58(stateDir, STATE_FILE2);
94850
+ writeFileSync46(path2, JSON.stringify(state7, null, 2) + `
94472
94851
  `, { mode: 384 });
94473
94852
  }
94474
94853
  function patchQuotaWatchState(current, accountLabel, accountState) {
@@ -94516,27 +94895,27 @@ init_auth_snapshot_format2();
94516
94895
  // gateway/turn-active-marker.ts
94517
94896
  import {
94518
94897
  closeSync as closeSync11,
94519
- existsSync as existsSync52,
94898
+ existsSync as existsSync53,
94520
94899
  mkdirSync as mkdirSync45,
94521
94900
  openSync as openSync11,
94522
- readFileSync as readFileSync56,
94901
+ readFileSync as readFileSync57,
94523
94902
  statSync as statSync17,
94524
94903
  unlinkSync as unlinkSync24,
94525
94904
  utimesSync as utimesSync2,
94526
- writeFileSync as writeFileSync46
94905
+ writeFileSync as writeFileSync47
94527
94906
  } from "node:fs";
94528
- import { join as join58 } from "node:path";
94907
+ import { join as join59 } from "node:path";
94529
94908
  var TURN_ACTIVE_MARKER_FILE2 = "turn-active.json";
94530
94909
  var TURN_ACTIVE_HARD_TTL_MS2 = 10 * 60000;
94531
94910
  var TURN_ACTIVE_IDLE_SWEEP_MS = 60000;
94532
94911
  function removeTurnActiveMarker2(stateDir) {
94533
94912
  try {
94534
- unlinkSync24(join58(stateDir, TURN_ACTIVE_MARKER_FILE2));
94913
+ unlinkSync24(join59(stateDir, TURN_ACTIVE_MARKER_FILE2));
94535
94914
  } catch {}
94536
94915
  }
94537
94916
  function sweepStaleTurnActiveMarker(stateDir, opts) {
94538
- const path2 = join58(stateDir, TURN_ACTIVE_MARKER_FILE2);
94539
- if (!existsSync52(path2))
94917
+ const path2 = join59(stateDir, TURN_ACTIVE_MARKER_FILE2);
94918
+ if (!existsSync53(path2))
94540
94919
  return false;
94541
94920
  const now = opts.now ?? Date.now();
94542
94921
  try {
@@ -94548,7 +94927,7 @@ function sweepStaleTurnActiveMarker(stateDir, opts) {
94548
94927
  return false;
94549
94928
  let payload = null;
94550
94929
  try {
94551
- payload = readFileSync56(path2, "utf8");
94930
+ payload = readFileSync57(path2, "utf8");
94552
94931
  } catch {}
94553
94932
  unlinkSync24(path2);
94554
94933
  if (opts.onRemove) {
@@ -94566,7 +94945,7 @@ function sweepStaleTurnActiveMarker(stateDir, opts) {
94566
94945
  }
94567
94946
  }
94568
94947
  function readTurnActiveMarkerAgeMs2(stateDir, now) {
94569
- const path2 = join58(stateDir, TURN_ACTIVE_MARKER_FILE2);
94948
+ const path2 = join59(stateDir, TURN_ACTIVE_MARKER_FILE2);
94570
94949
  try {
94571
94950
  const st = statSync17(path2);
94572
94951
  return (now ?? Date.now()) - st.mtimeMs;
@@ -94579,19 +94958,19 @@ function effectiveTurnAgeMs(markerAgeMs, turnStartedAt, now) {
94579
94958
  }
94580
94959
 
94581
94960
  // gateway/gateway-heartbeat.ts
94582
- import { mkdirSync as mkdirSync46, utimesSync as utimesSync3, writeFileSync as writeFileSync47 } from "node:fs";
94583
- import { join as join59 } from "node:path";
94961
+ import { mkdirSync as mkdirSync46, utimesSync as utimesSync3, writeFileSync as writeFileSync48 } from "node:fs";
94962
+ import { join as join60 } from "node:path";
94584
94963
  var GATEWAY_HEARTBEAT_FILE = "gateway-heartbeat";
94585
94964
  var GATEWAY_HEARTBEAT_INTERVAL_MS = 15000;
94586
94965
  function touchGatewayHeartbeat(stateDir) {
94587
- const path2 = join59(stateDir, GATEWAY_HEARTBEAT_FILE);
94966
+ const path2 = join60(stateDir, GATEWAY_HEARTBEAT_FILE);
94588
94967
  const now = new Date;
94589
94968
  try {
94590
94969
  utimesSync3(path2, now, now);
94591
94970
  } catch {
94592
94971
  try {
94593
94972
  mkdirSync46(stateDir, { recursive: true });
94594
- writeFileSync47(path2, `${Date.now()}
94973
+ writeFileSync48(path2, `${Date.now()}
94595
94974
  `, { mode: 384 });
94596
94975
  } catch {}
94597
94976
  }
@@ -94758,10 +95137,10 @@ function startOutboxSweep(deps) {
94758
95137
  }
94759
95138
 
94760
95139
  // ../src/build-info.ts
94761
- var VERSION = "0.19.17";
94762
- var COMMIT_SHA = "75114a8a";
94763
- var COMMIT_DATE = "2026-07-25T06:59:05+10:00";
94764
- var LATEST_PR = 3525;
95140
+ var VERSION2 = "0.19.18";
95141
+ var COMMIT_SHA = "d3a10dee";
95142
+ var COMMIT_DATE = "2026-07-25T15:13:16+10:00";
95143
+ var LATEST_PR = 3595;
94765
95144
  var COMMITS_AHEAD_OF_TAG = 0;
94766
95145
 
94767
95146
  // gateway/boot-version.ts
@@ -94839,11 +95218,11 @@ init_peercred();
94839
95218
  import * as net5 from "node:net";
94840
95219
  import * as fs2 from "node:fs";
94841
95220
  import { homedir as homedir19 } from "node:os";
94842
- import { join as join60 } from "node:path";
95221
+ import { join as join61 } from "node:path";
94843
95222
  var DEFAULT_TIMEOUT_MS4 = 2000;
94844
95223
  var UNLOCK_TIMEOUT_MS = 30000;
94845
- var LEGACY_SOCKET_PATH2 = join60(homedir19(), ".switchroom", "vault-broker.sock");
94846
- var OPERATOR_SOCKET_PATH2 = join60(homedir19(), ".switchroom", "broker-operator", "sock");
95224
+ var LEGACY_SOCKET_PATH2 = join61(homedir19(), ".switchroom", "vault-broker.sock");
95225
+ var OPERATOR_SOCKET_PATH2 = join61(homedir19(), ".switchroom", "broker-operator", "sock");
94847
95226
  function defaultBrokerSocketPath2() {
94848
95227
  if (fs2.existsSync(OPERATOR_SOCKET_PATH2))
94849
95228
  return OPERATOR_SOCKET_PATH2;
@@ -95726,7 +96105,7 @@ function resolveVaultApprovalPosture(broker) {
95726
96105
 
95727
96106
  // registry/turns-schema.ts
95728
96107
  import { chmodSync as chmodSync12, mkdirSync as mkdirSync47 } from "fs";
95729
- import { join as join61 } from "path";
96108
+ import { join as join62 } from "path";
95730
96109
  var DatabaseClass3 = null;
95731
96110
  function loadDatabaseClass3() {
95732
96111
  if (DatabaseClass3 != null)
@@ -95798,9 +96177,9 @@ function applySchema(db3) {
95798
96177
  }
95799
96178
  function openTurnsDb(agentDir) {
95800
96179
  const Database = loadDatabaseClass3();
95801
- const dir = join61(agentDir, "telegram");
96180
+ const dir = join62(agentDir, "telegram");
95802
96181
  mkdirSync47(dir, { recursive: true, mode: 448 });
95803
- const path2 = join61(dir, "registry.db");
96182
+ const path2 = join62(dir, "registry.db");
95804
96183
  const db3 = new Database(path2, { create: true });
95805
96184
  applySchema(db3);
95806
96185
  try {
@@ -95889,7 +96268,7 @@ function reapStaleOpenTurns(db3, opts) {
95889
96268
  const stamp = db3.prepare(`
95890
96269
  UPDATE turns
95891
96270
  SET ended_at = ?,
95892
- ended_via = 'restart',
96271
+ ended_via = 'reaped_stale',
95893
96272
  updated_at = ?
95894
96273
  WHERE turn_key = ? AND ended_at IS NULL
95895
96274
  `);
@@ -95905,6 +96284,7 @@ function reapStaleOpenTurns(db3, opts) {
95905
96284
  }
95906
96285
  var INTERRUPTED_VIA2 = new Set([
95907
96286
  "restart",
96287
+ "reaped_stale",
95908
96288
  "sigterm",
95909
96289
  "timeout",
95910
96290
  "unknown"
@@ -96131,7 +96511,7 @@ function selectResumeBuilder(endedVia, opts) {
96131
96511
  let kind;
96132
96512
  if (endedVia === "timeout")
96133
96513
  kind = "report";
96134
- else if (endedVia === "restart" || endedVia === "sigterm" || endedVia === "unknown")
96514
+ else if (endedVia === "restart" || endedVia === "reaped_stale" || endedVia === "sigterm" || endedVia === "unknown")
96135
96515
  kind = "resume";
96136
96516
  else if (endedVia == null)
96137
96517
  kind = "resume";
@@ -96144,7 +96524,7 @@ function selectResumeBuilder(endedVia, opts) {
96144
96524
  }
96145
96525
 
96146
96526
  // gateway/bridge-dead-watchdog.ts
96147
- import { readFileSync as readFileSync58, writeFileSync as writeFileSync48, renameSync as renameSync22, unlinkSync as unlinkSync25 } from "node:fs";
96527
+ import { readFileSync as readFileSync59, writeFileSync as writeFileSync49, renameSync as renameSync22, unlinkSync as unlinkSync25 } from "node:fs";
96148
96528
 
96149
96529
  // gateway/cron-session.ts
96150
96530
  var CRON_IDENTITY_SUFFIX2 = "-cron";
@@ -96177,7 +96557,7 @@ function readFreshCrashLogTail(path2, opts = {}) {
96177
96557
  const nowMs3 = opts.nowMs ?? Date.now();
96178
96558
  const freshWindowMs = opts.freshWindowMs ?? CRASH_LOG_FRESH_WINDOW_MS;
96179
96559
  const maxLines = opts.maxLines ?? CRASH_LOG_TAIL_LINES;
96180
- const readFile = opts.readFile ?? ((p) => readFileSync58(p, "utf8"));
96560
+ const readFile = opts.readFile ?? ((p) => readFileSync59(p, "utf8"));
96181
96561
  let raw;
96182
96562
  try {
96183
96563
  raw = readFile(path2);
@@ -96198,13 +96578,13 @@ function readFreshCrashLogTail(path2, opts = {}) {
96198
96578
  }
96199
96579
  function writeBridgeDeadEscalationMarker(path2, marker) {
96200
96580
  const tmp = `${path2}.tmp-${process.pid}-${Date.now()}`;
96201
- writeFileSync48(tmp, JSON.stringify(marker), "utf8");
96581
+ writeFileSync49(tmp, JSON.stringify(marker), "utf8");
96202
96582
  renameSync22(tmp, path2);
96203
96583
  }
96204
96584
  function consumeBridgeDeadEscalationMarker(path2, nowMs3 = Date.now(), maxAgeMs = ESCALATION_MARKER_MAX_AGE_MS) {
96205
96585
  let marker = null;
96206
96586
  try {
96207
- const parsed = JSON.parse(readFileSync58(path2, "utf8"));
96587
+ const parsed = JSON.parse(readFileSync59(path2, "utf8"));
96208
96588
  if (typeof parsed.ts === "number" && Number.isFinite(parsed.ts) && typeof parsed.reason === "string") {
96209
96589
  const age = nowMs3 - parsed.ts;
96210
96590
  if (age >= 0 && age < maxAgeMs) {
@@ -96357,15 +96737,16 @@ function createBridgeDeadWatchdog(opts) {
96357
96737
  }
96358
96738
 
96359
96739
  // gateway/boot-probes.ts
96360
- import { readFileSync as readFileSync59, readdirSync as readdirSync12, existsSync as existsSync54 } from "fs";
96740
+ import { readFileSync as readFileSync60, readdirSync as readdirSync12, existsSync as existsSync55 } from "fs";
96361
96741
  init_quota_cache();
96742
+ init_generation_stamp();
96362
96743
  init_quota_check();
96363
96744
  import { execFile as execFileCb2 } from "child_process";
96364
96745
  import { promisify as promisify2 } from "util";
96365
96746
  var execFile2 = promisify2(execFileCb2);
96366
96747
  var realProcFs2 = {
96367
96748
  readdir: (p) => readdirSync12(p),
96368
- readFile: (p) => readFileSync59(p, "utf-8")
96749
+ readFile: (p) => readFileSync60(p, "utf-8")
96369
96750
  };
96370
96751
  function findAgentProcessInContainer2(fs3 = realProcFs2) {
96371
96752
  let entries;
@@ -96615,7 +96996,7 @@ if (isGatewayMain) {
96615
96996
  shutdownAnalytics();
96616
96997
  });
96617
96998
  }
96618
- var STATE_DIR = process.env.TELEGRAM_STATE_DIR ?? join62(homedir20(), ".claude", "channels", "telegram");
96999
+ var STATE_DIR = process.env.TELEGRAM_STATE_DIR ?? join63(homedir20(), ".claude", "channels", "telegram");
96619
97000
  var permCardStore = createPermissionCardStore(STATE_DIR);
96620
97001
  var BLOCKED_APPROVALS_DIR = process.env.SWITCHROOM_BLOCKED_APPROVALS_DIR ?? "/state/blocked-approvals";
96621
97002
  var AGENT_NAME = process.env.SWITCHROOM_AGENT_NAME ?? "agent";
@@ -96654,7 +97035,7 @@ function alwaysAllowDrainDeps() {
96654
97035
  return {
96655
97036
  readConfigText: () => {
96656
97037
  const cfgPath = process.env.SWITCHROOM_CONFIG ?? SWITCHROOM_CONFIG ?? findConfigFile2();
96657
- return readFileSync60(cfgPath, "utf8");
97038
+ return readFileSync61(cfgPath, "utf8");
96658
97039
  },
96659
97040
  resolveAllowList: (_configText, agentName3) => {
96660
97041
  const cfg = loadConfig2();
@@ -96715,11 +97096,11 @@ function scheduleAlwaysAllowPersistDrain() {
96715
97096
  }, ALWAYS_ALLOW_DRAIN_INTERVAL_MS);
96716
97097
  timer3.unref?.();
96717
97098
  }
96718
- var ACCESS_FILE = join62(STATE_DIR, "access.json");
96719
- var APPROVED_DIR = join62(STATE_DIR, "approved");
96720
- var ENV_FILE = join62(STATE_DIR, ".env");
96721
- var INBOX_DIR = join62(STATE_DIR, "inbox");
96722
- var PEOPLE_FILE = join62(STATE_DIR, "people.json");
97099
+ var ACCESS_FILE = join63(STATE_DIR, "access.json");
97100
+ var APPROVED_DIR = join63(STATE_DIR, "approved");
97101
+ var ENV_FILE = join63(STATE_DIR, ".env");
97102
+ var INBOX_DIR = join63(STATE_DIR, "inbox");
97103
+ var PEOPLE_FILE = join63(STATE_DIR, "people.json");
96723
97104
  function triggerSelfRestart(targetAgent, reason, delayMs = 300) {
96724
97105
  const isDocker = process.env.SWITCHROOM_RUNTIME === "docker";
96725
97106
  const selfAgent = process.env.SWITCHROOM_AGENT_NAME;
@@ -96774,7 +97155,7 @@ function getClaudeCliVersion() {
96774
97155
  }
96775
97156
  function formatBootVersion() {
96776
97157
  return composeBootVersionString({
96777
- version: VERSION,
97158
+ version: VERSION2,
96778
97159
  commitSha: COMMIT_SHA,
96779
97160
  commitDate: COMMIT_DATE,
96780
97161
  latestPr: LATEST_PR,
@@ -96784,7 +97165,7 @@ function formatBootVersion() {
96784
97165
  }
96785
97166
  try {
96786
97167
  chmodSync14(ENV_FILE, 384);
96787
- for (const line of readFileSync60(ENV_FILE, "utf8").split(`
97168
+ for (const line of readFileSync61(ENV_FILE, "utf8").split(`
96788
97169
  `)) {
96789
97170
  const m = line.match(/^(\w+)=(.*)$/);
96790
97171
  if (m && process.env[m[1]] === undefined)
@@ -96805,7 +97186,7 @@ var bot;
96805
97186
  var lastGetUpdatesHeartbeatMs = Date.now();
96806
97187
  var GRAMMY_VERSION = (() => {
96807
97188
  try {
96808
- const raw = readFileSync60(new URL("../../node_modules/grammy/package.json", import.meta.url), "utf8");
97189
+ const raw = readFileSync61(new URL("../../node_modules/grammy/package.json", import.meta.url), "utf8");
96809
97190
  return JSON.parse(raw).version ?? "unknown";
96810
97191
  } catch {
96811
97192
  return "unknown";
@@ -96868,7 +97249,7 @@ function assertSendable(f) {
96868
97249
  } catch {
96869
97250
  throw new Error(`refusing to send file \u2014 cannot resolve real path: ${f}`);
96870
97251
  }
96871
- const inbox = join62(stateReal, "inbox");
97252
+ const inbox = join63(stateReal, "inbox");
96872
97253
  if (real.startsWith(stateReal + sep4) && !real.startsWith(inbox + sep4)) {
96873
97254
  throw new Error(`refusing to send channel state: ${f}`);
96874
97255
  }
@@ -96887,7 +97268,7 @@ function assertSendable(f) {
96887
97268
  }
96888
97269
  function readAccessFile() {
96889
97270
  try {
96890
- const raw = readFileSync60(ACCESS_FILE, "utf8");
97271
+ const raw = readFileSync61(ACCESS_FILE, "utf8");
96891
97272
  const parsed = JSON.parse(raw);
96892
97273
  const allowFrom = validateStringArray("allowFrom", parsed.allowFrom ?? []);
96893
97274
  const groups = {};
@@ -96949,7 +97330,7 @@ function loadAccess() {
96949
97330
  }
96950
97331
  function readPeopleFile() {
96951
97332
  try {
96952
- const raw = readFileSync60(PEOPLE_FILE, "utf8");
97333
+ const raw = readFileSync61(PEOPLE_FILE, "utf8");
96953
97334
  const parsed = JSON.parse(raw);
96954
97335
  if (!Array.isArray(parsed.entries))
96955
97336
  return [];
@@ -96973,7 +97354,7 @@ function saveAccess(a) {
96973
97354
  return;
96974
97355
  mkdirSync49(STATE_DIR, { recursive: true, mode: 448 });
96975
97356
  const tmp = ACCESS_FILE + ".tmp";
96976
- writeFileSync50(tmp, JSON.stringify(a, null, 2) + `
97357
+ writeFileSync51(tmp, JSON.stringify(a, null, 2) + `
96977
97358
  `, { mode: 384 });
96978
97359
  renameSync23(tmp, ACCESS_FILE);
96979
97360
  }
@@ -96993,7 +97374,7 @@ var HISTORY_ENABLED = HISTORY_ACCESS.historyEnabled !== false;
96993
97374
  if (isGatewayMain && HISTORY_ENABLED) {
96994
97375
  try {
96995
97376
  initHistory(STATE_DIR, HISTORY_ACCESS.historyRetentionDays ?? 30);
96996
- process.stderr.write(`telegram gateway: history capture enabled at ${join62(STATE_DIR, "history.db")}
97377
+ process.stderr.write(`telegram gateway: history capture enabled at ${join63(STATE_DIR, "history.db")}
96997
97378
  `);
96998
97379
  } catch (err) {
96999
97380
  process.stderr.write(`telegram gateway: history init failed (${err.message}) \u2014 capture disabled
@@ -97012,12 +97393,12 @@ if (isGatewayMain)
97012
97393
  let markerTurnKey = null;
97013
97394
  let markerAgeMs = null;
97014
97395
  try {
97015
- const markerPath = join62(STATE_DIR, TURN_ACTIVE_MARKER_FILE2);
97016
- if (existsSync56(markerPath)) {
97396
+ const markerPath = join63(STATE_DIR, TURN_ACTIVE_MARKER_FILE2);
97397
+ if (existsSync57(markerPath)) {
97017
97398
  const st = statSync20(markerPath);
97018
97399
  markerAgeMs = Date.now() - st.mtimeMs;
97019
97400
  try {
97020
- const payload = JSON.parse(readFileSync60(markerPath, "utf8"));
97401
+ const payload = JSON.parse(readFileSync61(markerPath, "utf8"));
97021
97402
  if (typeof payload.turnKey === "string" && payload.turnKey.length > 0) {
97022
97403
  markerTurnKey = payload.turnKey;
97023
97404
  }
@@ -97037,10 +97418,10 @@ if (isGatewayMain)
97037
97418
  process.stderr.write(`telegram gateway: turn-registry boot-reaper stamped ${reaped} orphaned turn(s)` + `${timeoutTurnKey ? ` (turnKey=${timeoutTurnKey} as 'timeout', markerAgeMs=${markerAgeMs})` : " as 'restart'"}
97038
97419
  `);
97039
97420
  } else {
97040
- process.stderr.write(`telegram gateway: turn-registry initialized at ${join62(agentDir, "telegram", "registry.db")}
97421
+ process.stderr.write(`telegram gateway: turn-registry initialized at ${join63(agentDir, "telegram", "registry.db")}
97041
97422
  `);
97042
97423
  }
97043
- const bridgeDeadMarker = consumeBridgeDeadEscalationMarker(join62(STATE_DIR, "bridge-dead-escalation.json"));
97424
+ const bridgeDeadMarker = consumeBridgeDeadEscalationMarker(join63(STATE_DIR, "bridge-dead-escalation.json"));
97044
97425
  if (bridgeDeadMarker != null) {
97045
97426
  bridgeDeadPriorStreak = bridgeDeadMarker.count ?? 1;
97046
97427
  process.stderr.write(`telegram gateway: boot: prior restart was a bridge-dead escalation (reason=${bridgeDeadMarker.reason}` + `, consecutive=${bridgeDeadPriorStreak}` + `${bridgeDeadMarker.crashTail ? `, crashTail=${bridgeDeadMarker.crashTail}` : ""})
@@ -97053,7 +97434,7 @@ if (isGatewayMain)
97053
97434
  const pending2 = findLatestTurnIfInterrupted(turnsDb);
97054
97435
  const selfAgent = process.env.SWITCHROOM_AGENT_NAME ?? "";
97055
97436
  if (pending2 != null && selfAgent) {
97056
- const bootResumeMarkerPath = process.env.SWITCHROOM_GATEWAY_CLEAN_SHUTDOWN_MARKER ?? join62(STATE_DIR, "clean-shutdown.json");
97437
+ const bootResumeMarkerPath = process.env.SWITCHROOM_GATEWAY_CLEAN_SHUTDOWN_MARKER ?? join63(STATE_DIR, "clean-shutdown.json");
97057
97438
  const bootResumeCleanMarker = readCleanShutdownMarker(bootResumeMarkerPath);
97058
97439
  const bootResumeForceAlways = process.env.SWITCHROOM_BOOT_RESUME_ALWAYS === "1";
97059
97440
  const bootResumeMode = parseBootResumeMode(process.env.SWITCHROOM_BOOT_RESUME);
@@ -97166,7 +97547,7 @@ if (isGatewayMain)
97166
97547
  `);
97167
97548
  }
97168
97549
  }
97169
- const pendingEnvPath = join62(agentDir, ".pending-turn.env");
97550
+ const pendingEnvPath = join63(agentDir, ".pending-turn.env");
97170
97551
  try {
97171
97552
  if (pending2 != null) {
97172
97553
  const lines = [
@@ -97180,13 +97561,13 @@ if (isGatewayMain)
97180
97561
  pending2.interrupt_reason != null ? `SWITCHROOM_PENDING_INTERRUPT_REASON=${pending2.interrupt_reason}` : `SWITCHROOM_PENDING_INTERRUPT_REASON=`
97181
97562
  ];
97182
97563
  const pendingEnvTmp = `${pendingEnvPath}.tmp-${process.pid}`;
97183
- writeFileSync50(pendingEnvTmp, lines.join(`
97564
+ writeFileSync51(pendingEnvTmp, lines.join(`
97184
97565
  `) + `
97185
97566
  `, { mode: 384 });
97186
97567
  renameSync23(pendingEnvTmp, pendingEnvPath);
97187
97568
  process.stderr.write(`telegram gateway: pending-turn env written to ${pendingEnvPath} turnKey=${pending2.turn_key} endedVia=${pending2.ended_via ?? "open"}
97188
97569
  `);
97189
- } else if (existsSync56(pendingEnvPath)) {
97570
+ } else if (existsSync57(pendingEnvPath)) {
97190
97571
  rmSync6(pendingEnvPath, { force: true });
97191
97572
  process.stderr.write(`telegram gateway: pending-turn env cleared (clean previous shutdown)
97192
97573
  `);
@@ -97294,7 +97675,7 @@ function checkApprovals() {
97294
97675
  return;
97295
97676
  }
97296
97677
  for (const senderId of files) {
97297
- const file = join62(APPROVED_DIR, senderId);
97678
+ const file = join63(APPROVED_DIR, senderId);
97298
97679
  bot.api.sendMessage(senderId, "Paired! Say hi to Claude.").then(() => rmSync6(file, { force: true }), (err) => {
97299
97680
  process.stderr.write(`telegram gateway: failed to send approval confirm: ${err}
97300
97681
  `);
@@ -97467,12 +97848,12 @@ function noteAgentOutputAt(key, ts) {
97467
97848
  lastAgentOutputAt.delete(oldest);
97468
97849
  }
97469
97850
  }
97470
- var OBLIGATION_STORE_PATH = join62(STATE_DIR, "obligations.json");
97851
+ var OBLIGATION_STORE_PATH = join63(STATE_DIR, "obligations.json");
97471
97852
  var obligationStoreFs = {
97472
- readFileSync: (p) => readFileSync60(p, "utf8"),
97473
- writeFileSync: (p, d) => writeFileSync50(p, d),
97853
+ readFileSync: (p) => readFileSync61(p, "utf8"),
97854
+ writeFileSync: (p, d) => writeFileSync51(p, d),
97474
97855
  renameSync: (a, b) => renameSync23(a, b),
97475
- existsSync: (p) => existsSync56(p)
97856
+ existsSync: (p) => existsSync57(p)
97476
97857
  };
97477
97858
  var obligationLedger = new ObligationLedger(OBLIGATION_REPRESENT_MAX, {
97478
97859
  onChange: STATIC || !OBLIGATION_LEDGER_ENABLED ? undefined : (snapshot) => persistObligations(OBLIGATION_STORE_PATH, obligationStoreFs, snapshot)
@@ -98879,7 +99260,7 @@ function sweepStaleAlwaysAllowCorrelations(now = Date.now()) {
98879
99260
  var MENTAL_MODEL_CORRELATION_TTL_MS = 720000;
98880
99261
  var pendingMentalModelCorrelations = createSweepableStore((entry, now) => now - entry.createdAt > MENTAL_MODEL_CORRELATION_TTL_MS);
98881
99262
  function mentalModelCorrelationKey(agentName3, unifiedDiff) {
98882
- return `${agentName3}::${createHash6("sha256").update(unifiedDiff).digest("hex")}`;
99263
+ return `${agentName3}::${createHash7("sha256").update(unifiedDiff).digest("hex")}`;
98883
99264
  }
98884
99265
  function sweepStaleMentalModelCorrelations(now = Date.now()) {
98885
99266
  pendingMentalModelCorrelations.sweep(now);
@@ -99815,28 +100196,28 @@ var statusPinState = new Map;
99815
100196
  var statusPinChatIds = new Map;
99816
100197
  var statusPinPinnedAt = new Map;
99817
100198
  var statusPinRightsCache = new PinRightsCache2;
99818
- var STATUS_PIN_STORE_PATH = join62(STATE_DIR, "status-pins.json");
100199
+ var STATUS_PIN_STORE_PATH = join63(STATE_DIR, "status-pins.json");
99819
100200
  var statusPinStoreFs = {
99820
- readFileSync: (p) => readFileSync60(p, "utf8"),
99821
- writeFileSync: (p, d) => writeFileSync50(p, d),
100201
+ readFileSync: (p) => readFileSync61(p, "utf8"),
100202
+ writeFileSync: (p, d) => writeFileSync51(p, d),
99822
100203
  renameSync: (a, b) => renameSync23(a, b),
99823
- existsSync: (p) => existsSync56(p)
100204
+ existsSync: (p) => existsSync57(p)
99824
100205
  };
99825
100206
  var statusPinPersistEnabled = !STATIC && PIN_STATUS_WHILE_WORKING;
99826
- var ACTIVITY_CARD_STORE_PATH = join62(STATE_DIR, "activity-cards-pending.json");
100207
+ var ACTIVITY_CARD_STORE_PATH = join63(STATE_DIR, "activity-cards-pending.json");
99827
100208
  var activityCardStoreFs = {
99828
- readFileSync: (p) => readFileSync60(p, "utf8"),
99829
- writeFileSync: (p, d) => writeFileSync50(p, d),
100209
+ readFileSync: (p) => readFileSync61(p, "utf8"),
100210
+ writeFileSync: (p, d) => writeFileSync51(p, d),
99830
100211
  renameSync: (a, b) => renameSync23(a, b),
99831
- existsSync: (p) => existsSync56(p)
100212
+ existsSync: (p) => existsSync57(p)
99832
100213
  };
99833
100214
  var activityCardPersistEnabled = !STATIC;
99834
- var QUEUED_CARD_STORE_PATH = join62(STATE_DIR, "queued-cards-pending.json");
100215
+ var QUEUED_CARD_STORE_PATH = join63(STATE_DIR, "queued-cards-pending.json");
99835
100216
  var queuedCardStoreFs = {
99836
- readFileSync: (p) => readFileSync60(p, "utf8"),
99837
- writeFileSync: (p, d) => writeFileSync50(p, d),
100217
+ readFileSync: (p) => readFileSync61(p, "utf8"),
100218
+ writeFileSync: (p, d) => writeFileSync51(p, d),
99838
100219
  renameSync: (a, b) => renameSync23(a, b),
99839
- existsSync: (p) => existsSync56(p)
100220
+ existsSync: (p) => existsSync57(p)
99840
100221
  };
99841
100222
  var queuedCardPersistEnabled = !STATIC;
99842
100223
  function persistQueuedCard(key, chatId, threadId, messageId) {
@@ -99961,7 +100342,7 @@ async function runMidSessionCardReaper() {
99961
100342
  now
99962
100343
  });
99963
100344
  if (reaped > 0) {
99964
- process.stderr.write(`telegram gateway: mid-session reaper stamped ${reaped} orphaned turn(s) as 'restart' (${reapedTurnKeys.join(",")})
100345
+ process.stderr.write(`telegram gateway: mid-session reaper stamped ${reaped} orphaned turn(s) as 'reaped_stale' (${reapedTurnKeys.join(",")})
99965
100346
  `);
99966
100347
  }
99967
100348
  } catch (err) {
@@ -100209,12 +100590,12 @@ var getPinnedProgressCardMessageId = null;
100209
100590
  var completeProgressCardTurn = null;
100210
100591
  var subagentWatcher = null;
100211
100592
  var workerActivityFeed = null;
100212
- var SOCKET_PATH = process.env.SWITCHROOM_GATEWAY_SOCKET ?? join62(STATE_DIR, "gateway.sock");
100593
+ var SOCKET_PATH = process.env.SWITCHROOM_GATEWAY_SOCKET ?? join63(STATE_DIR, "gateway.sock");
100213
100594
  if (isGatewayMain)
100214
100595
  mkdirSync49(STATE_DIR, { recursive: true, mode: 448 });
100215
- var GATEWAY_PID_PATH = process.env.SWITCHROOM_GATEWAY_PID_FILE ?? join62(STATE_DIR, "gateway.pid.json");
100216
- var GATEWAY_SESSION_MARKER_PATH = process.env.SWITCHROOM_GATEWAY_SESSION_MARKER ?? join62(STATE_DIR, "gateway-session.json");
100217
- var GATEWAY_CLEAN_SHUTDOWN_MARKER_PATH = process.env.SWITCHROOM_GATEWAY_CLEAN_SHUTDOWN_MARKER ?? join62(STATE_DIR, "clean-shutdown.json");
100596
+ var GATEWAY_PID_PATH = process.env.SWITCHROOM_GATEWAY_PID_FILE ?? join63(STATE_DIR, "gateway.pid.json");
100597
+ var GATEWAY_SESSION_MARKER_PATH = process.env.SWITCHROOM_GATEWAY_SESSION_MARKER ?? join63(STATE_DIR, "gateway-session.json");
100598
+ var GATEWAY_CLEAN_SHUTDOWN_MARKER_PATH = process.env.SWITCHROOM_GATEWAY_CLEAN_SHUTDOWN_MARKER ?? join63(STATE_DIR, "clean-shutdown.json");
100218
100599
  var GATEWAY_STARTED_AT_MS = Date.now();
100219
100600
  var BOOT_CARD_ENABLED = process.env.SWITCHROOM_BOOT_CARD !== "false";
100220
100601
  var activeBootCard = null;
@@ -100243,7 +100624,7 @@ function ensureIssuesCard(chatId, threadId) {
100243
100624
  bot: botApi,
100244
100625
  log: (msg) => process.stderr.write(`telegram gateway: ${msg}
100245
100626
  `),
100246
- persistPath: join62(stateDir, "issues-card.json")
100627
+ persistPath: join63(stateDir, "issues-card.json")
100247
100628
  });
100248
100629
  activeIssuesWatcher = startIssuesWatcher({
100249
100630
  stateDir,
@@ -100335,10 +100716,12 @@ function gatewayLivenessWiringDeps() {
100335
100716
  SILENCE_FALLBACK_HARD_MS,
100336
100717
  SILENCE_FLOOR_MS,
100337
100718
  SILENCE_DEFER_INFLIGHT_TOOLS,
100719
+ isObligationOpenForTurn: (originTurnId) => OBLIGATION_LEDGER_ENABLED && originTurnId != null && obligationLedger.isOpen(originTurnId),
100338
100720
  TURN_PREVIEW_MAX,
100339
100721
  STATE_DIR,
100340
100722
  isLegitimatelyWorking,
100341
100723
  getCurrentTurn: () => currentTurn,
100724
+ getCurrentTurnForKey: (key) => currentTurnMap.get(key),
100342
100725
  getInFlightUpdate: () => inFlightUpdate,
100343
100726
  getTurnsDb: () => turnsDb,
100344
100727
  getInboundSpool: () => inboundSpool,
@@ -100441,13 +100824,13 @@ if (isGatewayMain)
100441
100824
  var inboundSpool;
100442
100825
  if (isGatewayMain)
100443
100826
  inboundSpool = STATIC ? undefined : createInboundSpool({
100444
- path: join62(STATE_DIR, "inbound-spool.jsonl"),
100827
+ path: join63(STATE_DIR, "inbound-spool.jsonl"),
100445
100828
  fs: {
100446
100829
  appendFileSync: (p, d) => appendFileSync9(p, d),
100447
- readFileSync: (p) => readFileSync60(p, "utf8"),
100448
- writeFileSync: (p, d) => writeFileSync50(p, d),
100830
+ readFileSync: (p) => readFileSync61(p, "utf8"),
100831
+ writeFileSync: (p, d) => writeFileSync51(p, d),
100449
100832
  renameSync: (a, b) => renameSync23(a, b),
100450
- existsSync: (p) => existsSync56(p),
100833
+ existsSync: (p) => existsSync57(p),
100451
100834
  statSizeSync: (p) => statSync20(p).size
100452
100835
  },
100453
100836
  onDegraded: (info) => {
@@ -100509,13 +100892,13 @@ async function maybeRedeliverUndeliveredAnswer() {
100509
100892
  let transcriptText;
100510
100893
  try {
100511
100894
  const projectsDir = getProjectsDirForCwd();
100512
- const path2 = join62(projectsDir, `${sessionId}.jsonl`);
100513
- if (!existsSync56(path2)) {
100895
+ const path2 = join63(projectsDir, `${sessionId}.jsonl`);
100896
+ if (!existsSync57(path2)) {
100514
100897
  process.stderr.write(`telegram gateway: crash-redelivery \u2014 transcript not found for turnKey=${turn.turn_key} session=${sessionId} (${path2}); skipping
100515
100898
  `);
100516
100899
  return;
100517
100900
  }
100518
- transcriptText = readFileSync60(path2, "utf8");
100901
+ transcriptText = readFileSync61(path2, "utf8");
100519
100902
  } catch (err) {
100520
100903
  process.stderr.write(`telegram gateway: crash-redelivery \u2014 transcript read failed turnKey=${turn.turn_key}: ${err.message}
100521
100904
  `);
@@ -100674,8 +101057,8 @@ var bridgeDeadWatchdog = createBridgeDeadWatchdog({
100674
101057
  isSessionAlive: () => findAgentProcessInContainer2()?.comm === "claude",
100675
101058
  isShuttingDown: () => shuttingDown,
100676
101059
  escalate: (reason) => triggerSelfRestart(process.env.SWITCHROOM_AGENT_NAME ?? "", reason, 1500),
100677
- crashLogPath: join62(STATE_DIR, "bridge-crash.log"),
100678
- markerPath: join62(STATE_DIR, "bridge-dead-escalation.json"),
101060
+ crashLogPath: join63(STATE_DIR, "bridge-crash.log"),
101061
+ markerPath: join63(STATE_DIR, "bridge-dead-escalation.json"),
100679
101062
  log: (line) => process.stderr.write(`${line}
100680
101063
  `),
100681
101064
  priorStreak: bridgeDeadPriorStreak,
@@ -100781,8 +101164,8 @@ if (isGatewayMain)
100781
101164
  probeQuotaViaBroker: (t) => probeQuotaForBootCard(agentSlug, t),
100782
101165
  tmuxSupervisor: process.env.SWITCHROOM_TMUX_SUPERVISOR === "1",
100783
101166
  dockerMode: process.env.SWITCHROOM_RUNTIME === "docker",
100784
- configSnapshotPath: join62(resolvedAgentDirForCard, ".config-snapshot.json"),
100785
- bootCardStatePath: join62(resolvedAgentDirForCard, ".boot-card-msgid.json"),
101167
+ configSnapshotPath: join63(resolvedAgentDirForCard, ".config-snapshot.json"),
101168
+ bootCardStatePath: join63(resolvedAgentDirForCard, ".boot-card-msgid.json"),
100786
101169
  floodStatePath: FLOOD_STATE_PATH,
100787
101170
  ...updateOutcomeLine ? { updateOutcomeLine } : {}
100788
101171
  }, ackMsgId).then((handle) => {
@@ -100834,6 +101217,7 @@ if (isGatewayMain)
100834
101217
  turnTypingLoop.stopAll();
100835
101218
  typingEmitter.reset();
100836
101219
  },
101220
+ onTurnKeyEnded: (key) => endTurn(key),
100837
101221
  onDanglingTurnsSwept: () => {
100838
101222
  if (currentTurn != null) {
100839
101223
  process.stderr.write(`telegram gateway: disconnect-flush nulled currentTurn (bridge died with turn in flight)
@@ -101449,7 +101833,7 @@ if (isGatewayMain)
101449
101833
  const receiverUid = receiverUidRaw ? Number(receiverUidRaw) : NaN;
101450
101834
  if (Number.isInteger(receiverUid))
101451
101835
  allowedUids.push(receiverUid);
101452
- const socketPath = join62(STATE_DIR, "webhook.sock");
101836
+ const socketPath = join63(STATE_DIR, "webhook.sock");
101453
101837
  const webhookInject = (agentName3, inbound) => {
101454
101838
  const msg = inbound;
101455
101839
  const delivered = ipcServer.sendToAgent(agentName3, msg);
@@ -101677,9 +102061,9 @@ function redactOutboundText(text5, site) {
101677
102061
  var VOICE_OUT_DEFAULT_CHUNK_CHARS = 600;
101678
102062
  var VOICE_OUT_HARD_CHUNK_CAP = 4096;
101679
102063
  var voiceOnDemandCache = new VoiceOnDemandCache({
101680
- persistPath: join62(STATE_DIR, "voice-ondemand.json")
102064
+ persistPath: join63(STATE_DIR, "voice-ondemand.json")
101681
102065
  });
101682
- var VOICE_CACHE_DIR = join62(STATE_DIR, "voice-cache");
102066
+ var VOICE_CACHE_DIR = join63(STATE_DIR, "voice-cache");
101683
102067
  var voicePreSynthQueue = new PreSynthQueue({
101684
102068
  runJob: async (job) => {
101685
102069
  const sidecarToken = await materializeSidecarToken2();
@@ -102081,11 +102465,11 @@ async function executeSendGif(rawArgs) {
102081
102465
  };
102082
102466
  }
102083
102467
  async function publishToTelegraph(text5, shortName, authorName) {
102084
- const accountPath = join62(STATE_DIR, "telegraph-account.json");
102468
+ const accountPath = join63(STATE_DIR, "telegraph-account.json");
102085
102469
  let account = null;
102086
102470
  try {
102087
- if (existsSync56(accountPath)) {
102088
- const raw = readFileSync60(accountPath, "utf-8");
102471
+ if (existsSync57(accountPath)) {
102472
+ const raw = readFileSync61(accountPath, "utf-8");
102089
102473
  const parsed = JSON.parse(raw);
102090
102474
  if (parsed.shortName && parsed.accessToken) {
102091
102475
  account = parsed;
@@ -102105,7 +102489,7 @@ async function publishToTelegraph(text5, shortName, authorName) {
102105
102489
  account = created.value;
102106
102490
  try {
102107
102491
  mkdirSync49(STATE_DIR, { recursive: true, mode: 448 });
102108
- writeFileSync50(accountPath, JSON.stringify(account, null, 2), { mode: 384 });
102492
+ writeFileSync51(accountPath, JSON.stringify(account, null, 2), { mode: 384 });
102109
102493
  } catch (err) {
102110
102494
  process.stderr.write(`telegram gateway: telegraph cache write failed: ${err.message}
102111
102495
  `);
@@ -102221,7 +102605,7 @@ _The secret was NOT saved. The agent can re-request with \`request_secret\`._`,
102221
102605
  }
102222
102606
  function readLiveSwitchroomConfigText() {
102223
102607
  const cfgPath = process.env.SWITCHROOM_CONFIG ?? findConfigFile2();
102224
- return readFileSync60(cfgPath, "utf8");
102608
+ return readFileSync61(cfgPath, "utf8");
102225
102609
  }
102226
102610
  async function executeReact(args) {
102227
102611
  if (!args.chat_id)
@@ -102262,7 +102646,7 @@ async function executeDownloadAttachment(args) {
102262
102646
  });
102263
102647
  mkdirSync49(INBOX_DIR, { recursive: true, mode: 448 });
102264
102648
  assertInsideInbox2(INBOX_DIR, dlPath);
102265
- writeFileSync50(dlPath, buf, { mode: 384 });
102649
+ writeFileSync51(dlPath, buf, { mode: 384 });
102266
102650
  return { content: [{ type: "text", text: dlPath }] };
102267
102651
  }
102268
102652
  async function executeEditMessage(args) {
@@ -102645,6 +103029,12 @@ var handbackPreturnSignal = createHandbackPreturnSignal({
102645
103029
  isTurnSettled: (key) => {
102646
103030
  const live = currentTurnMap.get(key);
102647
103031
  return live != null && (live.finalAnswerDelivered || live.endedAt != null);
103032
+ },
103033
+ hasLiveTurn: (key) => {
103034
+ const live = currentTurnMap.get(key);
103035
+ if (live == null || live.endedAt != null)
103036
+ return false;
103037
+ return statusKey(live.sessionChatId, live.sessionThreadId) === key;
102648
103038
  }
102649
103039
  });
102650
103040
  var memoryLegibilityStager = new MemoryLegibilityStager;
@@ -103622,14 +104012,14 @@ function restartMarkerPath() {
103622
104012
  const agentDir = resolveAgentDirFromEnv();
103623
104013
  if (!agentDir)
103624
104014
  return null;
103625
- return join62(agentDir, "restart-pending.json");
104015
+ return join63(agentDir, "restart-pending.json");
103626
104016
  }
103627
104017
  function writeRestartMarker(marker) {
103628
104018
  const p = restartMarkerPath();
103629
104019
  if (!p)
103630
104020
  return;
103631
104021
  try {
103632
- writeFileSync50(p, JSON.stringify(marker));
104022
+ writeFileSync51(p, JSON.stringify(marker));
103633
104023
  lastPlannedRestartAt = Date.now();
103634
104024
  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}
103635
104025
  `);
@@ -103648,7 +104038,7 @@ function readRestartMarker() {
103648
104038
  if (!p)
103649
104039
  return null;
103650
104040
  try {
103651
- return JSON.parse(readFileSync60(p, "utf8"));
104041
+ return JSON.parse(readFileSync61(p, "utf8"));
103652
104042
  } catch {
103653
104043
  return null;
103654
104044
  }
@@ -103797,7 +104187,7 @@ var _dockerReachable;
103797
104187
  function isDockerReachable() {
103798
104188
  if (_dockerReachable !== undefined)
103799
104189
  return _dockerReachable;
103800
- if (!existsSync56("/var/run/docker.sock")) {
104190
+ if (!existsSync57("/var/run/docker.sock")) {
103801
104191
  _dockerReachable = false;
103802
104192
  return _dockerReachable;
103803
104193
  }
@@ -103814,12 +104204,12 @@ function _resetDockerReachableCache() {
103814
104204
  }
103815
104205
  function spawnSwitchroomDetached(args, onFailure) {
103816
104206
  const fullArgs = SWITCHROOM_CONFIG ? ["--config", SWITCHROOM_CONFIG, ...args] : args;
103817
- const logPath = join62(STATE_DIR, "detached-spawn.log");
104207
+ const logPath = join63(STATE_DIR, "detached-spawn.log");
103818
104208
  let outFd = null;
103819
104209
  try {
103820
104210
  mkdirSync49(STATE_DIR, { recursive: true });
103821
104211
  outFd = openSync12(logPath, "a");
103822
- writeFileSync50(logPath, `
104212
+ writeFileSync51(logPath, `
103823
104213
  [${new Date().toISOString()}] spawn ${SWITCHROOM_CLI} ${fullArgs.join(" ")}
103824
104214
  `, { flag: "a" });
103825
104215
  } catch {}
@@ -103845,7 +104235,7 @@ function spawnSwitchroomDetached(args, onFailure) {
103845
104235
  return;
103846
104236
  let tail = "";
103847
104237
  try {
103848
- const full = readFileSync60(logPath, "utf8");
104238
+ const full = readFileSync61(logPath, "utf8");
103849
104239
  tail = full.split(`
103850
104240
  `).slice(-30).join(`
103851
104241
  `).trim();
@@ -104107,10 +104497,10 @@ ${preBlock(formatSwitchroomOutput(detail))}`, { html: true });
104107
104497
  }
104108
104498
  function readRecentDenialsForAgent(agentName3, windowMs, limit) {
104109
104499
  try {
104110
- const auditPath = join62(homedir20(), ".switchroom", "vault-audit.log");
104111
- if (!existsSync56(auditPath))
104500
+ const auditPath = join63(homedir20(), ".switchroom", "vault-audit.log");
104501
+ if (!existsSync57(auditPath))
104112
104502
  return [];
104113
- const raw = readFileSync60(auditPath, "utf8");
104503
+ const raw = readFileSync61(auditPath, "utf8");
104114
104504
  return recentDenialsFromAuditLog(raw, { agentName: agentName3, windowMs, limit });
104115
104505
  } catch {
104116
104506
  return [];
@@ -104161,7 +104551,7 @@ async function buildAgentMetadata(agentName3) {
104161
104551
  try {
104162
104552
  const agentDir = resolveAgentDirFromEnv();
104163
104553
  if (agentDir) {
104164
- const raw = readFileSync60(join62(agentDir, ".claude", ".claude.json"), "utf8");
104554
+ const raw = readFileSync61(join63(agentDir, ".claude", ".claude.json"), "utf8");
104165
104555
  claudeJson = JSON.parse(raw);
104166
104556
  }
104167
104557
  } catch {}
@@ -104290,7 +104680,7 @@ function buildModelDeps(restartCtx) {
104290
104680
  try {
104291
104681
  const agentDir = resolveAgentDirFromEnv();
104292
104682
  if (agentDir) {
104293
- const local = await fetchQuota2({ claudeConfigDir: join62(agentDir, ".claude") });
104683
+ const local = await fetchQuota2({ claudeConfigDir: join63(agentDir, ".claude") });
104294
104684
  if (local.ok)
104295
104685
  return formatQuotaLine2(local.data);
104296
104686
  }
@@ -104536,9 +104926,9 @@ function effortMenuReplyMarkup(reply) {
104536
104926
  function flushAgentHandoff(agentDir) {
104537
104927
  let removed = 0;
104538
104928
  for (const fname of [".handoff.md", ".handoff-topic"]) {
104539
- const p = join62(agentDir, fname);
104929
+ const p = join63(agentDir, fname);
104540
104930
  try {
104541
- if (existsSync56(p)) {
104931
+ if (existsSync57(p)) {
104542
104932
  unlinkSync27(p);
104543
104933
  removed++;
104544
104934
  }
@@ -104594,7 +104984,7 @@ async function handleNewCommand(ctx) {
104594
104984
  writeRestartMarker({ chat_id: chatId, thread_id: threadId ?? null, ack_message_id: ackId, ts: Date.now() });
104595
104985
  if (agentDir != null) {
104596
104986
  try {
104597
- writeFileSync50(join62(agentDir, ".force-fresh-session"), `${kind} at ${new Date().toISOString()}
104987
+ writeFileSync51(join63(agentDir, ".force-fresh-session"), `${kind} at ${new Date().toISOString()}
104598
104988
  `, "utf8");
104599
104989
  } catch (err) {
104600
104990
  process.stderr.write(`telegram gateway: failed to write force-fresh marker: ${err}
@@ -104707,16 +105097,16 @@ function buildFolderPickerDeps() {
104707
105097
  };
104708
105098
  }
104709
105099
  var lockoutOps = {
104710
- readFileSync: (p, enc) => readFileSync60(p, enc),
104711
- writeFileSync: (p, data, opts) => writeFileSync50(p, data, opts),
104712
- existsSync: (p) => existsSync56(p),
105100
+ readFileSync: (p, enc) => readFileSync61(p, enc),
105101
+ writeFileSync: (p, data, opts) => writeFileSync51(p, data, opts),
105102
+ existsSync: (p) => existsSync57(p),
104713
105103
  mkdirSync: (p, opts) => mkdirSync49(p, opts),
104714
- joinPath: (...parts) => join62(...parts)
105104
+ joinPath: (...parts) => join63(...parts)
104715
105105
  };
104716
105106
  var FLEET_FALLBACK_DEDUP_MS = 30000;
104717
105107
  function isAuthBrokerSocketReachable() {
104718
105108
  try {
104719
- return existsSync56(resolveAuthBrokerSocketPath2());
105109
+ return existsSync57(resolveAuthBrokerSocketPath2());
104720
105110
  } catch {
104721
105111
  return false;
104722
105112
  }
@@ -104971,7 +105361,7 @@ async function runCreditWatch() {
104971
105361
  if (!agentDir)
104972
105362
  return;
104973
105363
  const agentName3 = getMyAgentName();
104974
- const claudeConfigDir = join62(agentDir, ".claude");
105364
+ const claudeConfigDir = join63(agentDir, ".claude");
104975
105365
  const stateDir = STATE_DIR;
104976
105366
  const reason = readClaudeJsonOverage(claudeConfigDir);
104977
105367
  const prev = loadCreditState(stateDir);
@@ -106727,7 +107117,7 @@ Send \`/auth ${parsed.provider} cancel\` to abort.`, { html: true });
106727
107117
  await switchroomReply(ctx, "**/usage:** cannot resolve agent dir.", { html: true });
106728
107118
  return;
106729
107119
  }
106730
- const result = await fetchQuota2({ claudeConfigDir: join62(agentDir, ".claude") });
107120
+ const result = await fetchQuota2({ claudeConfigDir: join63(agentDir, ".claude") });
106731
107121
  if (!result.ok) {
106732
107122
  await switchroomReply(ctx, `**/usage:** ${escapeHtmlForTg2(result.reason)}`, { html: true });
106733
107123
  return;
@@ -107113,7 +107503,7 @@ ${interimLabel}` : interimLabel
107113
107503
  const unifiedDiff = (() => {
107114
107504
  try {
107115
107505
  const cfgPath = process.env.SWITCHROOM_CONFIG ?? SWITCHROOM_CONFIG ?? findConfigFile2();
107116
- const raw = readFileSync60(cfgPath, "utf8");
107506
+ const raw = readFileSync61(cfgPath, "utf8");
107117
107507
  return synthesizeAllowRuleDiff({ agentName: agentName3, rule: chosen.rule, configText: raw });
107118
107508
  } catch (err) {
107119
107509
  process.stderr.write(`telegram gateway: always-allow diff synth failed: ${err.message}
@@ -107970,7 +108360,7 @@ async function startGateway() {
107970
108360
  return;
107971
108361
  }
107972
108362
  })();
107973
- const resolvedAgentDirForBootCard = agentDir ?? join62(homedir20(), ".switchroom", "agents", agentSlug);
108363
+ const resolvedAgentDirForBootCard = agentDir ?? join63(homedir20(), ".switchroom", "agents", agentSlug);
107974
108364
  const handle = await startBootCard(chatId, threadId, botApiForCard, {
107975
108365
  agentName: agentDisplayName,
107976
108366
  agentSlug,
@@ -107984,8 +108374,8 @@ async function startGateway() {
107984
108374
  probeQuotaViaBroker: (t) => probeQuotaForBootCard(agentSlug, t),
107985
108375
  tmuxSupervisor: process.env.SWITCHROOM_TMUX_SUPERVISOR === "1",
107986
108376
  dockerMode: process.env.SWITCHROOM_RUNTIME === "docker",
107987
- configSnapshotPath: join62(resolvedAgentDirForBootCard, ".config-snapshot.json"),
107988
- bootCardStatePath: join62(resolvedAgentDirForBootCard, ".boot-card-msgid.json"),
108377
+ configSnapshotPath: join63(resolvedAgentDirForBootCard, ".config-snapshot.json"),
108378
+ bootCardStatePath: join63(resolvedAgentDirForBootCard, ".boot-card-msgid.json"),
107989
108379
  floodStatePath: FLOOD_STATE_PATH,
107990
108380
  ...updateOutcomeLine ? { updateOutcomeLine } : {}
107991
108381
  }, ackMsgId);
@@ -108016,10 +108406,10 @@ async function startGateway() {
108016
108406
  try {
108017
108407
  const smAgentDir = resolveAgentDirFromEnv();
108018
108408
  if (smAgentDir) {
108019
- const activePath = join62(smAgentDir, ".active-session-model");
108020
- if (existsSync56(activePath)) {
108409
+ const activePath = join63(smAgentDir, ".active-session-model");
108410
+ if (existsSync57(activePath)) {
108021
108411
  try {
108022
- const launched = readFileSync60(activePath, "utf8").trim();
108412
+ const launched = readFileSync61(activePath, "utf8").trim();
108023
108413
  const configured = (() => {
108024
108414
  const d = switchroomExecJson(["agent", "list"]);
108025
108415
  const raw = d?.agents?.find((a) => a.name === getMyAgentName())?.model ?? null;
@@ -108052,24 +108442,24 @@ async function startGateway() {
108052
108442
  deliverModelSwitchBootNotice({
108053
108443
  ...modelBootCardDeps,
108054
108444
  confirmation,
108055
- hasSessionModelAlert: existsSync56(join62(smAgentDir, ".session-model-alert"))
108445
+ hasSessionModelAlert: existsSync57(join63(smAgentDir, ".session-model-alert"))
108056
108446
  });
108057
108447
  }
108058
108448
  } catch {}
108059
108449
  }
108060
- const activeEffortPath = join62(smAgentDir, ".active-session-effort");
108061
- if (existsSync56(activeEffortPath)) {
108450
+ const activeEffortPath = join63(smAgentDir, ".active-session-effort");
108451
+ if (existsSync57(activeEffortPath)) {
108062
108452
  try {
108063
- const launchedEffort = readFileSync60(activeEffortPath, "utf8").trim();
108453
+ const launchedEffort = readFileSync61(activeEffortPath, "utf8").trim();
108064
108454
  const configuredEffort = getConfiguredEffortForPersist();
108065
108455
  sessionEffortOverride = launchedEffort.length > 0 && launchedEffort !== configuredEffort ? launchedEffort : null;
108066
108456
  } catch {}
108067
108457
  }
108068
- const alertPath = join62(smAgentDir, ".session-model-alert");
108069
- if (existsSync56(alertPath)) {
108458
+ const alertPath = join63(smAgentDir, ".session-model-alert");
108459
+ if (existsSync57(alertPath)) {
108070
108460
  let alertText = null;
108071
108461
  try {
108072
- alertText = readFileSync60(alertPath, "utf8").trim();
108462
+ alertText = readFileSync61(alertPath, "utf8").trim();
108073
108463
  } catch {
108074
108464
  alertText = null;
108075
108465
  }