switchroom 0.19.48 → 0.20.0

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 (50) hide show
  1. package/dist/agent-scheduler/index.js +18 -1
  2. package/dist/auth-broker/index.js +19 -2
  3. package/dist/buzz-gateway/index.js +9207 -0
  4. package/dist/cli/notion-write-pretool.mjs +18 -1
  5. package/dist/cli/switchroom.js +63 -4
  6. package/dist/host-control/main.js +20 -3
  7. package/dist/vault/approvals/kernel-server.js +19 -2
  8. package/dist/vault/broker/server.js +19 -2
  9. package/package.json +4 -3
  10. package/profiles/_base/start.sh.hbs +78 -1
  11. package/profiles/default/CLAUDE.md.hbs +1 -1
  12. package/skills/dev-protocol/SKILL.md +30 -1
  13. package/skills/switchroom-architecture/SKILL.md +5 -0
  14. package/skills/switchroom-cli/SKILL.md +1 -1
  15. package/telegram-plugin/dist/bridge/bridge.js +7 -4
  16. package/telegram-plugin/dist/gateway/gateway.js +1149 -247
  17. package/telegram-plugin/dist/server.js +7 -4
  18. package/telegram-plugin/gateway/boot-briefing-builder.ts +458 -0
  19. package/telegram-plugin/gateway/boot-briefing-wiring.ts +170 -0
  20. package/telegram-plugin/gateway/buzz-mirror.ts +329 -0
  21. package/telegram-plugin/gateway/buzz-type-guards.ts +34 -0
  22. package/telegram-plugin/gateway/channel-route.ts +272 -0
  23. package/telegram-plugin/gateway/gateway.ts +73 -81
  24. package/telegram-plugin/gateway/inbound-spool.ts +33 -1
  25. package/telegram-plugin/gateway/ipc-protocol.ts +81 -2
  26. package/telegram-plugin/gateway/ipc-server.ts +197 -2
  27. package/telegram-plugin/gateway/outbound-send-path.ts +37 -1
  28. package/telegram-plugin/gateway/pending-turn-env.ts +61 -0
  29. package/telegram-plugin/gateway/stream-render.ts +21 -0
  30. package/telegram-plugin/gateway/subagent-handback-marker.ts +12 -0
  31. package/telegram-plugin/gateway/user-failure-notices.ts +172 -0
  32. package/telegram-plugin/history.ts +15 -0
  33. package/telegram-plugin/llm-error-present.ts +9 -4
  34. package/telegram-plugin/model-unavailable.ts +4 -0
  35. package/telegram-plugin/operator-events.fixtures.json +12 -12
  36. package/telegram-plugin/operator-events.ts +81 -9
  37. package/telegram-plugin/session-tail.ts +7 -1
  38. package/telegram-plugin/tests/boot-briefing-builder.test.ts +604 -0
  39. package/telegram-plugin/tests/buzz-mirror.test.ts +242 -0
  40. package/telegram-plugin/tests/buzz-origin-stamp-gate.test.ts +159 -0
  41. package/telegram-plugin/tests/channel-route.test.ts +306 -0
  42. package/telegram-plugin/tests/inbound-spool.test.ts +47 -0
  43. package/telegram-plugin/tests/ipc-server-buzz-dedup.test.ts +124 -0
  44. package/telegram-plugin/tests/ipc-server-buzz-peer.test.ts +269 -0
  45. package/telegram-plugin/tests/operator-events-session-tail.test.ts +63 -0
  46. package/telegram-plugin/tests/operator-events.test.ts +71 -7
  47. package/telegram-plugin/tests/user-failure-notices.test.ts +165 -0
  48. package/telegram-plugin/voice-normalize-text.ts +5 -0
  49. package/vendor/hindsight-memory/scripts/directive_verify.py +4 -0
  50. package/vendor/hindsight-memory/scripts/recall.py +7 -2
@@ -11591,7 +11591,7 @@ __export(exports_client, {
11591
11591
  });
11592
11592
  import * as net from "node:net";
11593
11593
  import { homedir as homedir2 } from "node:os";
11594
- import { randomUUID } from "node:crypto";
11594
+ import { randomUUID as randomUUID2 } from "node:crypto";
11595
11595
  import { join as join3 } from "node:path";
11596
11596
  function reviveDate(v) {
11597
11597
  if (v == null)
@@ -11649,7 +11649,7 @@ class AuthBrokerClient {
11649
11649
  async getCredentials(provider, account) {
11650
11650
  const base = {
11651
11651
  v: PROTOCOL_VERSION,
11652
- id: randomUUID(),
11652
+ id: randomUUID2(),
11653
11653
  op: "get-credentials"
11654
11654
  };
11655
11655
  let req = base;
@@ -11663,7 +11663,7 @@ class AuthBrokerClient {
11663
11663
  async listState() {
11664
11664
  const data = await this.send({
11665
11665
  v: PROTOCOL_VERSION,
11666
- id: randomUUID(),
11666
+ id: randomUUID2(),
11667
11667
  op: "list-state"
11668
11668
  });
11669
11669
  return data;
@@ -11671,7 +11671,7 @@ class AuthBrokerClient {
11671
11671
  async listGoogleAccounts() {
11672
11672
  const data = await this.send({
11673
11673
  v: PROTOCOL_VERSION,
11674
- id: randomUUID(),
11674
+ id: randomUUID2(),
11675
11675
  op: "list-google-accounts"
11676
11676
  });
11677
11677
  return data;
@@ -11679,7 +11679,7 @@ class AuthBrokerClient {
11679
11679
  async listMicrosoftAccounts() {
11680
11680
  const data = await this.send({
11681
11681
  v: PROTOCOL_VERSION,
11682
- id: randomUUID(),
11682
+ id: randomUUID2(),
11683
11683
  op: "list-microsoft-accounts"
11684
11684
  });
11685
11685
  return data;
@@ -11687,7 +11687,7 @@ class AuthBrokerClient {
11687
11687
  async probeQuota(accounts, timeoutMs, forceLive) {
11688
11688
  const data = await this.send({
11689
11689
  v: PROTOCOL_VERSION,
11690
- id: randomUUID(),
11690
+ id: randomUUID2(),
11691
11691
  op: "probe-quota",
11692
11692
  accounts: [...accounts],
11693
11693
  ...timeoutMs !== undefined ? { timeoutMs } : {},
@@ -11705,7 +11705,7 @@ class AuthBrokerClient {
11705
11705
  async getExternalSpend(forceLive) {
11706
11706
  const data = await this.send({
11707
11707
  v: PROTOCOL_VERSION,
11708
- id: randomUUID(),
11708
+ id: randomUUID2(),
11709
11709
  op: "get-external-spend",
11710
11710
  ...forceLive ? { forceLive: true } : {}
11711
11711
  });
@@ -11714,21 +11714,21 @@ class AuthBrokerClient {
11714
11714
  async setActive(account) {
11715
11715
  const data = await this.send({
11716
11716
  v: PROTOCOL_VERSION,
11717
- id: randomUUID(),
11717
+ id: randomUUID2(),
11718
11718
  op: "set-active",
11719
11719
  account
11720
11720
  });
11721
11721
  return data;
11722
11722
  }
11723
11723
  async markExhausted(until) {
11724
- const req = until !== undefined ? { v: PROTOCOL_VERSION, id: randomUUID(), op: "mark-exhausted", until } : { v: PROTOCOL_VERSION, id: randomUUID(), op: "mark-exhausted" };
11724
+ const req = until !== undefined ? { v: PROTOCOL_VERSION, id: randomUUID2(), op: "mark-exhausted", until } : { v: PROTOCOL_VERSION, id: randomUUID2(), op: "mark-exhausted" };
11725
11725
  const data = await this.send(req);
11726
11726
  return data;
11727
11727
  }
11728
11728
  async markThrottled(until) {
11729
11729
  const data = await this.send({
11730
11730
  v: PROTOCOL_VERSION,
11731
- id: randomUUID(),
11731
+ id: randomUUID2(),
11732
11732
  op: "mark-throttled",
11733
11733
  until
11734
11734
  });
@@ -11737,7 +11737,7 @@ class AuthBrokerClient {
11737
11737
  async claimNotification(key, windowMs) {
11738
11738
  const data = await this.send({
11739
11739
  v: PROTOCOL_VERSION,
11740
- id: randomUUID(),
11740
+ id: randomUUID2(),
11741
11741
  op: "claim-notification",
11742
11742
  key,
11743
11743
  windowMs
@@ -11747,7 +11747,7 @@ class AuthBrokerClient {
11747
11747
  async refreshAccount(account) {
11748
11748
  const data = await this.send({
11749
11749
  v: PROTOCOL_VERSION,
11750
- id: randomUUID(),
11750
+ id: randomUUID2(),
11751
11751
  op: "refresh-account",
11752
11752
  account
11753
11753
  });
@@ -11756,7 +11756,7 @@ class AuthBrokerClient {
11756
11756
  async addAccount(label, credentials, replace, provider) {
11757
11757
  const base = {
11758
11758
  v: PROTOCOL_VERSION,
11759
- id: randomUUID(),
11759
+ id: randomUUID2(),
11760
11760
  op: "add-account",
11761
11761
  label,
11762
11762
  credentials
@@ -11769,7 +11769,7 @@ class AuthBrokerClient {
11769
11769
  async rmAccount(label, provider) {
11770
11770
  const base = {
11771
11771
  v: PROTOCOL_VERSION,
11772
- id: randomUUID(),
11772
+ id: randomUUID2(),
11773
11773
  op: "rm-account",
11774
11774
  label
11775
11775
  };
@@ -11780,7 +11780,7 @@ class AuthBrokerClient {
11780
11780
  async setOverride(agent, account) {
11781
11781
  const data = await this.send({
11782
11782
  v: PROTOCOL_VERSION,
11783
- id: randomUUID(),
11783
+ id: randomUUID2(),
11784
11784
  op: "set-override",
11785
11785
  agent,
11786
11786
  account
@@ -21547,7 +21547,7 @@ var init_observation_scopes = __esm(() => {
21547
21547
  });
21548
21548
 
21549
21549
  // ../src/config/schema.ts
21550
- var CodeRepoEntrySchema, AgentBindMountSchema, HttpDiffPollSchema, PollSpecSchema, TelegramMessageActionSchema, WebhookActionSchema, ActionSpecSchema, ScheduleEntrySchema, AgentSoulSchema, AgentToolsSchema, ObservationScopesSchema, ObservationScopeStrategySchema, AntiConfabulationDirectiveSchema, AgentMemorySchema, HookEntrySchema, AgentHooksSchema, SubagentSchema, SessionSchema, SessionContinuitySchema, webhookDispatchRule, TelegramChannelSchema, ChannelsSchema, TIMEZONE_REGEX, ApproverIdSchema, GoogleWorkspaceTierSchema, GoogleServiceTokenSchema, 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;
21550
+ var CodeRepoEntrySchema, AgentBindMountSchema, HttpDiffPollSchema, PollSpecSchema, TelegramMessageActionSchema, WebhookActionSchema, ActionSpecSchema, ScheduleEntrySchema, AgentSoulSchema, AgentToolsSchema, ObservationScopesSchema, ObservationScopeStrategySchema, AntiConfabulationDirectiveSchema, AgentMemorySchema, HookEntrySchema, AgentHooksSchema, SubagentSchema, SessionSchema, SessionContinuitySchema, webhookDispatchRule, TelegramChannelSchema, BuzzChannelSchema, ChannelsSchema, TIMEZONE_REGEX, ApproverIdSchema, GoogleWorkspaceTierSchema, GoogleServiceTokenSchema, 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;
21551
21551
  var init_schema = __esm(() => {
21552
21552
  init_zod();
21553
21553
  init_observation_scopes();
@@ -21794,6 +21794,7 @@ var init_schema = __esm(() => {
21794
21794
  max_turns_in_briefing: exports_external.number().int().positive().optional().describe("Cap on recent user/assistant turn pairs fed to the summarizer."),
21795
21795
  resume_mode: exports_external.enum(["auto", "continue", "handoff", "none"]).optional().describe("How to resume the next session. 'handoff' (default as of #362) " + "never passes --continue; a fresh Claude starts each restart and " + "reads a briefing assembled from recent Telegram messages, Hindsight " + "recall, and today's daily memory file. 'auto' uses --continue when " + "the latest JSONL is smaller than resume_max_bytes, else falls back " + "to the handoff briefing. 'continue' always passes --continue. " + "'none' starts completely fresh every time."),
21796
21796
  resume_max_bytes: exports_external.number().int().positive().optional().describe("Byte threshold above which 'auto' mode falls back to handoff " + "instead of --continue. Default 2_000_000 (~2MB). Large transcripts " + "can blow out the context window even with prefix caching, and " + "--continue replay is known-fragile at scale."),
21797
+ briefing: exports_external.enum(["gateway", "legacy"]).optional().describe("Which mechanism assembles the fresh-session reorientation briefing " + "(default 'legacy'). 'legacy' keeps today's behaviour: the Stop-hook " + ".handoff.md and/or bin/handoff-briefing.sh, injected via " + "--append-system-prompt. 'gateway' moves it to a gateway boot-time " + "builder sourced from the durable history.db (crash-independent, " + "surface-scoped, token-budgeted) and injects it as a synthetic " + '<channel source="boot_briefing"> inbound over the durable spool \u2014 ' + "keeping the system-prompt prefix stable for cross-session prompt " + "caching. Suppressed automatically when resume_mode is " + "'continue'/'auto' (the transcript may be replayed) and on a /reset " + "force-fresh boot. Threaded to the gateway as " + "SWITCHROOM_SESSION_BRIEFING."),
21797
21798
  boot_resume: exports_external.enum(["always", "in-flight", "never"]).optional().describe("How the gateway auto-resumes a turn that was IN FLIGHT when the " + "agent restarted. 'in-flight' (default) resumes genuinely " + "interrupted work even after a deliberate/operator restart \u2014 a " + "sanctioned restart landing mid-turn no longer silently drops the " + "work. 'always' forces resume unconditionally (same as the " + "SWITCHROOM_BOOT_RESUME_ALWAYS=1 escape hatch). 'never' is the " + "quota-saving posture: don't auto-replay work across a clean " + "restart \u2014 but the user is STILL sent a passive notice of what was " + "in flight (silence is never used). Independent of the at-most-once " + "resume ledger and the bounded resume-chain loop-guard, which always " + "apply. Threaded to the gateway as SWITCHROOM_BOOT_RESUME."),
21798
21799
  session_retention_max_count: exports_external.number().int().nonnegative().optional().describe("Session-JSONL retention (issue #2792): keep at most this many " + "newest session transcripts under .claude/projects; older ones " + "past both this count and the age bound are pruned by the Stop " + "hook. The newest sessions (and the handoff source) are always " + "kept. Default 20; set 0 to disable the count bound."),
21799
21800
  session_retention_max_age_days: exports_external.number().int().nonnegative().optional().describe("Session-JSONL retention (issue #2792): prune session transcripts " + "older than this many days (a file is deleted only when it is BOTH " + "over the count bound and older than this). Default 30; set 0 to " + "disable the age bound.")
@@ -21926,8 +21927,24 @@ var init_schema = __esm(() => {
21926
21927
  }
21927
21928
  return tg;
21928
21929
  });
21930
+ BuzzChannelSchema = exports_external.object({
21931
+ enabled: exports_external.boolean().default(false).describe("Master switch for the per-agent Buzz sidecar. Default false \u2014 the " + "channel ships dark; start.sh forks the sidecar only when true."),
21932
+ relay_url: exports_external.string().regex(/^wss?:\/\//, "relay_url must be a ws:// or wss:// URL").describe("CANONICAL WebSocket URL of the closed Buzz relay \u2014 the exact string " + "the relay expects in the NIP-42 `relay` auth tag (e.g. " + "'ws://127.0.0.1:3000'). A live probe proved the relay validates this " + "tag as an exact string match against its own URL BEFORE the " + "membership check, so it is the relay's advertised identity, NOT " + "necessarily the address the sidecar dials. Set relay_dial_url when " + "the reachable address differs (a docker-network IP)."),
21933
+ relay_dial_url: exports_external.string().regex(/^wss?:\/\//, "relay_dial_url must be a ws:// or wss:// URL").optional().describe("Reachable ws:// / wss:// address the sidecar DIALS when it differs " + "from the canonical relay_url (e.g. a docker-network IP the relay's " + "own 127.0.0.1 can't stand in for). The NIP-42 auth tag still uses " + "relay_url. Defaults to relay_url when unset."),
21934
+ relay_host: exports_external.string().regex(/^(\[[0-9a-fA-F:]+\]|[^\s/?#:@]+)(:\d+)?$/, "relay_host must be a bare host[:port] authority \u2014 no scheme, path, or userinfo (e.g. '127.0.0.1:3000')").describe("REQUIRED HTTP Host header authority sent verbatim on the WS upgrade " + "(e.g. '127.0.0.1:3000', port included). The relay resolves its " + "community from this header before the upgrade and returns HTTP 404 if " + "it is missing/wrong, so it must match the relay's configured " + "authority and is deployment config, never derived from the dial URL."),
21935
+ nsec_vault_key: exports_external.string().default("buzz/{agent}-nsec").describe("Vault KEY NAME for the agent's Nostr secret key. Broker-fetched " + "in-process at sidecar boot; NEVER resolved into env or logged. " + "'{agent}' is substituted with the agent name."),
21936
+ operator_pubkey: exports_external.string().regex(/^(npub1[02-9ac-hj-np-z]{58}|[0-9a-f]{64})$/, "operator_pubkey must be a bech32 npub or 64-char hex pubkey").describe("The operator's Nostr pubkey (npub or hex). Always in the effective " + "inbound allowlist \u2014 the fail-closed default is operator-only."),
21937
+ authorized_pubkeys: exports_external.array(exports_external.string()).default([]).describe("Additional pubkeys (npub or hex) whose signed events may become " + "turns. Effective allowlist = this \u222a {operator_pubkey}. Empty by " + "default (operator-only)."),
21938
+ mirror: exports_external.enum(["both", "origin", "off"]).default("both").describe("Cross-surface mirror mode. 'both' answers on the origin channel AND " + "mirrors a copy to the other; 'off' is a true kill-switch that disables " + "the channel in BOTH directions (the inbound sidecar exits idle). " + "Phase 2b (S2): 'origin' is DEFERRED \u2014 the hub's mirror hook lives only " + "in sendReply, so 'origin' cannot be honored soundly; a configured " + "'origin' is degraded to 'off' (dark) at runtime by both the sidecar " + "config loader and the hub (channel-route.ts parseConfiguredMirrorMode). " + "Only 'both' and 'off' ship live in 2b."),
21939
+ chat_id: exports_external.string().min(1, "chat_id must be a non-empty Telegram chat id").describe("Telegram chat id an injected Buzz turn is routed to. Phase 1 is " + "inbound-only, so the agent's reply lands here on Telegram (the " + "authoritative surface); in later phases this is the chat the Buzz " + "turn's Telegram copy maps to. Required \u2014 the sidecar refuses to run " + "live without it (BUZZ_CHAT_ID)."),
21940
+ default_channel_id: exports_external.string().describe("Relay-minted group UUID (the NIP-29 `h` tag) the sidecar subscribes " + "to and stamps on injected turns."),
21941
+ channel_map: exports_external.record(exports_external.string(), exports_external.string()).default({}).describe("Optional map of extra group UUIDs \u2192 friendly labels."),
21942
+ pubkey_names: exports_external.record(exports_external.string(), exports_external.string()).default({}).describe("Optional petnames: hex/npub pubkey \u2192 display name, used to label " + "the sender on injected turns."),
21943
+ pinned_relay_digest: exports_external.string().optional().describe("Pinned relay image digest (M4). The compat-check warns on mismatch; " + "advisory in Phase 1.")
21944
+ }).strict();
21929
21945
  ChannelsSchema = exports_external.object({
21930
- telegram: TelegramChannelSchema
21946
+ telegram: TelegramChannelSchema,
21947
+ buzz: BuzzChannelSchema.optional()
21931
21948
  }).optional();
21932
21949
  TIMEZONE_REGEX = /^UTC$|^[A-Z][A-Za-z0-9_+-]+(\/[A-Z][A-Za-z0-9_+-]+){1,2}$/;
21933
21950
  ApproverIdSchema = exports_external.union([exports_external.number(), exports_external.string().regex(/^\d+$/)]);
@@ -29758,6 +29775,7 @@ __export(exports_history, {
29758
29775
  hasOutboundDeliveredSince: () => hasOutboundDeliveredSince2,
29759
29776
  getRecentOutboundCount: () => getRecentOutboundCount,
29760
29777
  getLatestInboundMessageId: () => getLatestInboundMessageId2,
29778
+ getHistoryDbForBriefing: () => getHistoryDbForBriefing,
29761
29779
  deliveryTextMatch: () => deliveryTextMatch2,
29762
29780
  deleteFromHistory: () => deleteFromHistory2,
29763
29781
  checkpointWal: () => checkpointWal2,
@@ -29901,6 +29919,9 @@ function verifyHistoryWritable2() {
29901
29919
  } catch {}
29902
29920
  }
29903
29921
  }
29922
+ function getHistoryDbForBriefing() {
29923
+ return db2;
29924
+ }
29904
29925
  function _resetForTests() {
29905
29926
  if (db2 != null) {
29906
29927
  db2.close();
@@ -38221,7 +38242,7 @@ __export(exports_tmux2, {
38221
38242
  captureAgentPane: () => captureAgentPane2
38222
38243
  });
38223
38244
  import { execFileSync as execFileSync8 } from "node:child_process";
38224
- import { chmodSync as chmodSync13, mkdirSync as mkdirSync50, readdirSync as readdirSync15, statSync as statSync22, unlinkSync as unlinkSync30, writeFileSync as writeFileSync49 } from "node:fs";
38245
+ import { chmodSync as chmodSync13, mkdirSync as mkdirSync50, readdirSync as readdirSync15, statSync as statSync22, unlinkSync as unlinkSync30, writeFileSync as writeFileSync51 } from "node:fs";
38225
38246
  import { resolve as resolve12 } from "node:path";
38226
38247
  function captureAgentPane2(opts) {
38227
38248
  const { agentName: agentName3, agentDir, reason } = opts;
@@ -38272,7 +38293,7 @@ function captureAgentPane2(opts) {
38272
38293
  ` + `
38273
38294
  `;
38274
38295
  try {
38275
- writeFileSync49(outPath, Buffer.concat([Buffer.from(header, "utf8"), body]), {
38296
+ writeFileSync51(outPath, Buffer.concat([Buffer.from(header, "utf8"), body]), {
38276
38297
  mode: 384
38277
38298
  });
38278
38299
  } catch (err) {
@@ -39156,7 +39177,7 @@ __export(exports_materialize_bot_token, {
39156
39177
  materializeBotToken: () => materializeBotToken,
39157
39178
  BotTokenMaterializeError: () => BotTokenMaterializeError
39158
39179
  });
39159
- import { existsSync as existsSync57 } from "node:fs";
39180
+ import { existsSync as existsSync59 } from "node:fs";
39160
39181
  function pickConfiguredToken(config, agentName3) {
39161
39182
  if (agentName3) {
39162
39183
  const agent = config.agents?.[agentName3];
@@ -39170,7 +39191,7 @@ function tryDirectVaultRead4(ref, config, passphrase) {
39170
39191
  if (!passphrase)
39171
39192
  return null;
39172
39193
  const vaultPath = resolvePath(config.vault?.path ?? "~/.switchroom/vault.enc");
39173
- if (!existsSync57(vaultPath))
39194
+ if (!existsSync59(vaultPath))
39174
39195
  return null;
39175
39196
  try {
39176
39197
  const secrets = openVault(passphrase, vaultPath);
@@ -39325,18 +39346,18 @@ var import_runner3 = __toESM(require_mod3(), 1);
39325
39346
  import { randomBytes as randomBytes13, createHash as createHash8 } from "crypto";
39326
39347
  import { execFileSync as execFileSync9, execSync as execSync2, spawn as spawn2 } from "child_process";
39327
39348
  import {
39328
- readFileSync as readFileSync61,
39329
- writeFileSync as writeFileSync50,
39349
+ readFileSync as readFileSync62,
39350
+ writeFileSync as writeFileSync52,
39330
39351
  mkdirSync as mkdirSync51,
39331
39352
  readdirSync as readdirSync16,
39332
- rmSync as rmSync8,
39353
+ rmSync as rmSync9,
39333
39354
  statSync as statSync23,
39334
- renameSync as renameSync27,
39355
+ renameSync as renameSync28,
39335
39356
  realpathSync as realpathSync5,
39336
39357
  chmodSync as chmodSync14,
39337
39358
  openSync as openSync15,
39338
39359
  closeSync as closeSync15,
39339
- existsSync as existsSync58,
39360
+ existsSync as existsSync60,
39340
39361
  unlinkSync as unlinkSync31,
39341
39362
  appendFileSync as appendFileSync9
39342
39363
  } from "fs";
@@ -39392,7 +39413,7 @@ function fsyncPathSync(path) {
39392
39413
 
39393
39414
  // gateway/gateway.ts
39394
39415
  import { homedir as homedir20 } from "os";
39395
- import { join as join68, sep as sep4, basename as basename17 } from "path";
39416
+ import { join as join70, sep as sep4, basename as basename17 } from "path";
39396
39417
 
39397
39418
  // plugin-logger.ts
39398
39419
  import { appendFileSync, mkdirSync, renameSync as renameSync2, statSync, existsSync } from "fs";
@@ -42279,6 +42300,225 @@ function resolveReplyOwnerTurnWith(lookups, liveTurn, chatId, args) {
42279
42300
  return { turn: winnerId != null ? byId.get(winnerId) ?? null : null, tier, candidates };
42280
42301
  }
42281
42302
 
42303
+ // gateway/buzz-mirror.ts
42304
+ import { randomUUID } from "crypto";
42305
+
42306
+ // gateway/channel-route.ts
42307
+ var TELEGRAM_ONLY = Object.freeze({ originChannel: "telegram" });
42308
+ var OUTER_LAST_SOURCE = /<channel[^>]*\bsource="([^"]+)"/;
42309
+ var OUTER_OPEN_TAG = /<channel[^>]*>/;
42310
+ function unescapeXmlAttr(s) {
42311
+ return s.replace(/&quot;/g, '"').replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&amp;/g, "&");
42312
+ }
42313
+ function readAttr(openTag, name) {
42314
+ const m = openTag.match(new RegExp(`\\b${name}="([^"]*)"`));
42315
+ if (m == null)
42316
+ return null;
42317
+ const value = unescapeXmlAttr(m[1]);
42318
+ return value.length > 0 ? value : null;
42319
+ }
42320
+ function parseChannelOrigin(rawContent) {
42321
+ if (typeof rawContent !== "string")
42322
+ return TELEGRAM_ONLY;
42323
+ const sourceMatch = rawContent.match(OUTER_LAST_SOURCE);
42324
+ if (sourceMatch == null || sourceMatch[1] !== "buzz")
42325
+ return TELEGRAM_ONLY;
42326
+ const openMatch = rawContent.match(OUTER_OPEN_TAG);
42327
+ if (openMatch == null)
42328
+ return TELEGRAM_ONLY;
42329
+ const openTag = openMatch[0];
42330
+ const channelId = readAttr(openTag, "buzz_channel_id");
42331
+ const eventId = readAttr(openTag, "buzz_event_id");
42332
+ const threadRoot = readAttr(openTag, "buzz_thread_root");
42333
+ if (channelId == null || eventId == null || threadRoot == null) {
42334
+ process.stderr.write("telegram gateway: buzz-origin turn missing coordinate " + `(channel_id=${channelId != null} event_id=${eventId != null} ` + `thread_root=${threadRoot != null}) \u2014 routing as telegram
42335
+ `);
42336
+ return TELEGRAM_ONLY;
42337
+ }
42338
+ return { originChannel: "buzz", buzzCoords: { channelId, eventId, threadRoot } };
42339
+ }
42340
+ function resolveRoute(originChannel, mode, buzzEnabled) {
42341
+ const buzzLive = buzzEnabled && mode !== "off";
42342
+ const primary = originChannel === "buzz" && buzzLive ? "buzz" : "telegram";
42343
+ let mirrors = [];
42344
+ if (buzzLive && mode === "both") {
42345
+ mirrors = originChannel === "telegram" ? ["buzz"] : ["telegram"];
42346
+ }
42347
+ return { primary, mirrors };
42348
+ }
42349
+ function parseConfiguredMirrorMode(raw) {
42350
+ if (raw === undefined)
42351
+ return "both";
42352
+ if (raw === "both")
42353
+ return "both";
42354
+ return "off";
42355
+ }
42356
+ function isBuzzThreadedPublishSafe(input) {
42357
+ return input.ownerEchoed || !input.hasRecentDifferentOriginTurn;
42358
+ }
42359
+ function isBuzzTurnRoutingEnabled(env = process.env) {
42360
+ return env.SWITCHROOM_BUZZ_TURN_ROUTING !== "0";
42361
+ }
42362
+
42363
+ // gateway/buzz-mirror.ts
42364
+ var CORRECTION_DEBOUNCE_MS = 30000;
42365
+ var MAX_TRACKED = 4096;
42366
+
42367
+ class BuzzMirror {
42368
+ cfg;
42369
+ log;
42370
+ sender = null;
42371
+ pending = new Map;
42372
+ pendingOrder = [];
42373
+ msgToBuzz = new Map;
42374
+ msgOrder = [];
42375
+ correctionTimers = new Map;
42376
+ constructor(cfg) {
42377
+ this.cfg = cfg;
42378
+ this.log = cfg.log ?? (() => {});
42379
+ }
42380
+ attachSender(sender) {
42381
+ this.sender = sender;
42382
+ }
42383
+ evict(map, order) {
42384
+ while (order.length > MAX_TRACKED) {
42385
+ const k = order.shift();
42386
+ if (k !== undefined)
42387
+ map.delete(k);
42388
+ }
42389
+ }
42390
+ mirrorReplyDelivered(input) {
42391
+ try {
42392
+ const route = resolveRoute(input.ownerOriginChannel, this.cfg.mode, true);
42393
+ const buzzInRoute = route.primary === "buzz" || route.mirrors.includes("buzz");
42394
+ if (!buzzInRoute)
42395
+ return;
42396
+ let channelId;
42397
+ let replyToEventId;
42398
+ let threadRootId;
42399
+ if (input.ownerOriginChannel === "buzz" && input.ownerBuzzCoords) {
42400
+ if (!isBuzzThreadedPublishSafe({
42401
+ ownerEchoed: input.ownerEchoed,
42402
+ hasRecentDifferentOriginTurn: input.hasRecentDifferentOriginTurn
42403
+ })) {
42404
+ this.log("buzz-mirror: S1 guard blocked a threaded publish on an ambiguous " + "owner binding (un-echoed reply + a recent different-origin turn) " + "\u2014 delivered Telegram-only");
42405
+ return;
42406
+ }
42407
+ channelId = input.ownerBuzzCoords.channelId;
42408
+ replyToEventId = input.ownerBuzzCoords.eventId;
42409
+ threadRootId = input.ownerBuzzCoords.threadRoot;
42410
+ } else {
42411
+ if (!this.cfg.defaultChannelId)
42412
+ return;
42413
+ channelId = this.cfg.defaultChannelId;
42414
+ }
42415
+ this.publish({
42416
+ channelId,
42417
+ replyToEventId,
42418
+ threadRootId,
42419
+ payload: { kind: "message", text: input.scrubbedText }
42420
+ }, input.telegramMessageKeys);
42421
+ } catch (err) {
42422
+ this.log(`buzz-mirror: mirrorReplyDelivered threw (ignored): ${String(err)}`);
42423
+ }
42424
+ }
42425
+ mirrorCorrection(input) {
42426
+ try {
42427
+ const target = this.msgToBuzz.get(input.telegramMessageKey);
42428
+ if (!target)
42429
+ return;
42430
+ const existing = this.correctionTimers.get(input.telegramMessageKey);
42431
+ if (existing)
42432
+ clearTimeout(existing);
42433
+ const timer = setTimeout(() => {
42434
+ this.correctionTimers.delete(input.telegramMessageKey);
42435
+ const t = this.msgToBuzz.get(input.telegramMessageKey);
42436
+ if (!t)
42437
+ return;
42438
+ this.publish({
42439
+ channelId: t.channelId,
42440
+ replyToEventId: t.eventId,
42441
+ threadRootId: t.eventId,
42442
+ payload: {
42443
+ kind: "correction",
42444
+ text: input.scrubbedText,
42445
+ targetEventId: t.eventId
42446
+ }
42447
+ }, []);
42448
+ }, CORRECTION_DEBOUNCE_MS);
42449
+ if (typeof timer.unref === "function") {
42450
+ timer.unref();
42451
+ }
42452
+ this.correctionTimers.set(input.telegramMessageKey, timer);
42453
+ } catch (err) {
42454
+ this.log(`buzz-mirror: mirrorCorrection threw (ignored): ${String(err)}`);
42455
+ }
42456
+ }
42457
+ onPublishResult(msg) {
42458
+ const p = this.pending.get(msg.correlationId);
42459
+ this.pending.delete(msg.correlationId);
42460
+ if (!p)
42461
+ return;
42462
+ if (!msg.ok || !msg.eventId) {
42463
+ this.log(`buzz-mirror: publish failed (correlationId=${msg.correlationId.slice(0, 8)}` + `${msg.error ? ` error=${msg.error}` : ""}) \u2014 Telegram copy already delivered`);
42464
+ return;
42465
+ }
42466
+ for (const key of p.telegramMessageKeys) {
42467
+ this.msgToBuzz.set(key, { eventId: msg.eventId, channelId: p.channelId });
42468
+ this.msgOrder.push(key);
42469
+ }
42470
+ this.evict(this.msgToBuzz, this.msgOrder);
42471
+ }
42472
+ publish(fields, telegramMessageKeys) {
42473
+ if (!this.sender) {
42474
+ this.log("buzz-mirror: no Buzz peer connected \u2014 mirror dropped (Telegram copy delivered)");
42475
+ return;
42476
+ }
42477
+ const correlationId = randomUUID();
42478
+ const msg = {
42479
+ type: "outbound_to_buzz",
42480
+ correlationId,
42481
+ agentName: this.cfg.agentName,
42482
+ ...fields
42483
+ };
42484
+ const sent = this.sender(msg);
42485
+ if (!sent) {
42486
+ this.log("buzz-mirror: Buzz peer send returned false \u2014 mirror dropped (Telegram copy delivered)");
42487
+ return;
42488
+ }
42489
+ this.pending.set(correlationId, {
42490
+ channelId: fields.channelId,
42491
+ telegramMessageKeys
42492
+ });
42493
+ this.pendingOrder.push(correlationId);
42494
+ this.evict(this.pending, this.pendingOrder);
42495
+ }
42496
+ }
42497
+ var singleton = null;
42498
+ function initBuzzMirror(cfg) {
42499
+ singleton = new BuzzMirror(cfg);
42500
+ return singleton;
42501
+ }
42502
+ function getBuzzMirror() {
42503
+ return singleton;
42504
+ }
42505
+ function maybeBootBuzzMirror(sender, env = process.env) {
42506
+ if (env.BUZZ_ENABLED !== "1" && env.BUZZ_ENABLED !== "true")
42507
+ return null;
42508
+ const mode = parseConfiguredMirrorMode(env.BUZZ_MIRROR);
42509
+ if (mode !== "both")
42510
+ return null;
42511
+ const bm = initBuzzMirror({
42512
+ mode,
42513
+ agentName: env.SWITCHROOM_AGENT_NAME?.trim() ?? "",
42514
+ defaultChannelId: env.BUZZ_CHANNEL_IDS?.trim() ?? "",
42515
+ log: (m) => process.stderr.write(`telegram gateway: buzz-mirror \u2014 ${m}
42516
+ `)
42517
+ });
42518
+ bm.attachSender(sender);
42519
+ return bm;
42520
+ }
42521
+
42282
42522
  // gateway/subagent-reply-authority.ts
42283
42523
  var SUB_AGENT_KIND_PREFIX = "sub_agent_";
42284
42524
 
@@ -70710,7 +70950,7 @@ class PostHog extends PostHogBackendClient {
70710
70950
  // analytics-posthog.ts
70711
70951
  import { existsSync as existsSync13, mkdirSync as mkdirSync17, readFileSync as readFileSync16, writeFileSync as writeFileSync14 } from "node:fs";
70712
70952
  import { dirname as dirname11, join as join21 } from "node:path";
70713
- import { randomUUID as randomUUID2 } from "node:crypto";
70953
+ import { randomUUID as randomUUID3 } from "node:crypto";
70714
70954
  var DEFAULT_KEY = "phc_qKY87cKWZm6ZyCtk7LcRd2cU8Sg42u7Ywhui5stYCegd";
70715
70955
  var DEFAULT_HOST = "https://us.i.posthog.com";
70716
70956
  var client = null;
@@ -70745,7 +70985,7 @@ function getDistinctId() {
70745
70985
  }
70746
70986
  }
70747
70987
  } catch {}
70748
- const id = randomUUID2();
70988
+ const id = randomUUID3();
70749
70989
  cachedDistinctId = id;
70750
70990
  try {
70751
70991
  mkdirSync17(dirname11(fallbackPath), { recursive: true });
@@ -70826,7 +71066,7 @@ import { dirname as dirname13, join as join23 } from "node:path";
70826
71066
  // analytics-posthog.ts
70827
71067
  import { existsSync as existsSync14, mkdirSync as mkdirSync18, readFileSync as readFileSync17, writeFileSync as writeFileSync15 } from "node:fs";
70828
71068
  import { dirname as dirname12, join as join22 } from "node:path";
70829
- import { randomUUID as randomUUID3 } from "node:crypto";
71069
+ import { randomUUID as randomUUID4 } from "node:crypto";
70830
71070
  var DEFAULT_KEY2 = "phc_qKY87cKWZm6ZyCtk7LcRd2cU8Sg42u7Ywhui5stYCegd";
70831
71071
  var DEFAULT_HOST2 = "https://us.i.posthog.com";
70832
71072
  var client2 = null;
@@ -70860,7 +71100,7 @@ function getDistinctId2() {
70860
71100
  }
70861
71101
  }
70862
71102
  } catch {}
70863
- const id = randomUUID3();
71103
+ const id = randomUUID4();
70864
71104
  cachedDistinctId2 = id;
70865
71105
  try {
70866
71106
  mkdirSync18(dirname12(fallbackPath), { recursive: true });
@@ -71913,6 +72153,8 @@ function detectModelUnavailable(stderr) {
71913
72153
  "socket hang up",
71914
72154
  "request timed out",
71915
72155
  "connection refused",
72156
+ "connection closed",
72157
+ "mid-response",
71916
72158
  "getaddrinfo"
71917
72159
  ];
71918
72160
  if (networkSignals.some((s) => lower.includes(s))) {
@@ -72143,6 +72385,7 @@ var OPERATOR_EVENT_KINDS = [
72143
72385
  "mcp-dependency-blocked",
72144
72386
  "quota-exhausted",
72145
72387
  "rate-limited",
72388
+ "transport-transient",
72146
72389
  "agent-crashed",
72147
72390
  "agent-restarted-unexpectedly",
72148
72391
  "unknown-4xx",
@@ -72155,12 +72398,12 @@ function classifyClaudeError(raw) {
72155
72398
  try {
72156
72399
  return classifyInner(raw);
72157
72400
  } catch {
72158
- return "unknown-4xx";
72401
+ return "unknown-5xx";
72159
72402
  }
72160
72403
  }
72161
72404
  function classifyInner(raw) {
72162
72405
  if (raw == null)
72163
- return "unknown-4xx";
72406
+ return "unknown-5xx";
72164
72407
  const obj = typeof raw === "object" ? raw : {};
72165
72408
  const errorType = extractString(obj, "error_type") ?? extractString(obj, "type") ?? extractString(getNestedObj(obj, "error"), "type") ?? "";
72166
72409
  const errorCode = extractString(obj, "code") ?? extractString(getNestedObj(obj, "error"), "code") ?? "";
@@ -72205,13 +72448,16 @@ ${message}`;
72205
72448
  if (errorType === "agent-restarted-unexpectedly" || errorCode === "agent-restarted-unexpectedly") {
72206
72449
  return "agent-restarted-unexpectedly";
72207
72450
  }
72451
+ if ((status == null || status >= 500) && (errorType === "server_error" || errorCode === "server_error" || sdkCode === "server_error" || errorType === "api_error" || errorCode === "api_error" || sdkCode === "api_error")) {
72452
+ return "transport-transient";
72453
+ }
72208
72454
  if (status != null) {
72209
72455
  if (status >= 400 && status < 500)
72210
72456
  return "unknown-4xx";
72211
72457
  if (status >= 500 && status < 600)
72212
72458
  return "unknown-5xx";
72213
72459
  }
72214
- return "unknown-4xx";
72460
+ return "unknown-5xx";
72215
72461
  }
72216
72462
  function extractString(obj, key) {
72217
72463
  const v = obj[key];
@@ -73597,7 +73843,7 @@ function defaultAddAccount(label, credentials, opts) {
73597
73843
  init_protocol();
73598
73844
  import * as net3 from "node:net";
73599
73845
  import { homedir as homedir7 } from "node:os";
73600
- import { randomUUID as randomUUID4 } from "node:crypto";
73846
+ import { randomUUID as randomUUID5 } from "node:crypto";
73601
73847
  import { join as join28 } from "node:path";
73602
73848
  var DEFAULT_TIMEOUT_MS3 = 5000;
73603
73849
  function reviveDate2(v) {
@@ -73671,7 +73917,7 @@ class AuthBrokerClient2 {
73671
73917
  async getCredentials(provider, account) {
73672
73918
  const base = {
73673
73919
  v: PROTOCOL_VERSION,
73674
- id: randomUUID4(),
73920
+ id: randomUUID5(),
73675
73921
  op: "get-credentials"
73676
73922
  };
73677
73923
  let req = base;
@@ -73685,7 +73931,7 @@ class AuthBrokerClient2 {
73685
73931
  async listState() {
73686
73932
  const data = await this.send({
73687
73933
  v: PROTOCOL_VERSION,
73688
- id: randomUUID4(),
73934
+ id: randomUUID5(),
73689
73935
  op: "list-state"
73690
73936
  });
73691
73937
  return data;
@@ -73693,7 +73939,7 @@ class AuthBrokerClient2 {
73693
73939
  async listGoogleAccounts() {
73694
73940
  const data = await this.send({
73695
73941
  v: PROTOCOL_VERSION,
73696
- id: randomUUID4(),
73942
+ id: randomUUID5(),
73697
73943
  op: "list-google-accounts"
73698
73944
  });
73699
73945
  return data;
@@ -73701,7 +73947,7 @@ class AuthBrokerClient2 {
73701
73947
  async listMicrosoftAccounts() {
73702
73948
  const data = await this.send({
73703
73949
  v: PROTOCOL_VERSION,
73704
- id: randomUUID4(),
73950
+ id: randomUUID5(),
73705
73951
  op: "list-microsoft-accounts"
73706
73952
  });
73707
73953
  return data;
@@ -73709,7 +73955,7 @@ class AuthBrokerClient2 {
73709
73955
  async probeQuota(accounts, timeoutMs, forceLive) {
73710
73956
  const data = await this.send({
73711
73957
  v: PROTOCOL_VERSION,
73712
- id: randomUUID4(),
73958
+ id: randomUUID5(),
73713
73959
  op: "probe-quota",
73714
73960
  accounts: [...accounts],
73715
73961
  ...timeoutMs !== undefined ? { timeoutMs } : {},
@@ -73727,7 +73973,7 @@ class AuthBrokerClient2 {
73727
73973
  async getExternalSpend(forceLive) {
73728
73974
  const data = await this.send({
73729
73975
  v: PROTOCOL_VERSION,
73730
- id: randomUUID4(),
73976
+ id: randomUUID5(),
73731
73977
  op: "get-external-spend",
73732
73978
  ...forceLive ? { forceLive: true } : {}
73733
73979
  });
@@ -73736,21 +73982,21 @@ class AuthBrokerClient2 {
73736
73982
  async setActive(account) {
73737
73983
  const data = await this.send({
73738
73984
  v: PROTOCOL_VERSION,
73739
- id: randomUUID4(),
73985
+ id: randomUUID5(),
73740
73986
  op: "set-active",
73741
73987
  account
73742
73988
  });
73743
73989
  return data;
73744
73990
  }
73745
73991
  async markExhausted(until) {
73746
- const req = until !== undefined ? { v: PROTOCOL_VERSION, id: randomUUID4(), op: "mark-exhausted", until } : { v: PROTOCOL_VERSION, id: randomUUID4(), op: "mark-exhausted" };
73992
+ const req = until !== undefined ? { v: PROTOCOL_VERSION, id: randomUUID5(), op: "mark-exhausted", until } : { v: PROTOCOL_VERSION, id: randomUUID5(), op: "mark-exhausted" };
73747
73993
  const data = await this.send(req);
73748
73994
  return data;
73749
73995
  }
73750
73996
  async markThrottled(until) {
73751
73997
  const data = await this.send({
73752
73998
  v: PROTOCOL_VERSION,
73753
- id: randomUUID4(),
73999
+ id: randomUUID5(),
73754
74000
  op: "mark-throttled",
73755
74001
  until
73756
74002
  });
@@ -73759,7 +74005,7 @@ class AuthBrokerClient2 {
73759
74005
  async claimNotification(key, windowMs) {
73760
74006
  const data = await this.send({
73761
74007
  v: PROTOCOL_VERSION,
73762
- id: randomUUID4(),
74008
+ id: randomUUID5(),
73763
74009
  op: "claim-notification",
73764
74010
  key,
73765
74011
  windowMs
@@ -73769,7 +74015,7 @@ class AuthBrokerClient2 {
73769
74015
  async refreshAccount(account) {
73770
74016
  const data = await this.send({
73771
74017
  v: PROTOCOL_VERSION,
73772
- id: randomUUID4(),
74018
+ id: randomUUID5(),
73773
74019
  op: "refresh-account",
73774
74020
  account
73775
74021
  });
@@ -73778,7 +74024,7 @@ class AuthBrokerClient2 {
73778
74024
  async addAccount(label, credentials, replace2, provider) {
73779
74025
  const base = {
73780
74026
  v: PROTOCOL_VERSION,
73781
- id: randomUUID4(),
74027
+ id: randomUUID5(),
73782
74028
  op: "add-account",
73783
74029
  label,
73784
74030
  credentials
@@ -73791,7 +74037,7 @@ class AuthBrokerClient2 {
73791
74037
  async rmAccount(label, provider) {
73792
74038
  const base = {
73793
74039
  v: PROTOCOL_VERSION,
73794
- id: randomUUID4(),
74040
+ id: randomUUID5(),
73795
74041
  op: "rm-account",
73796
74042
  label
73797
74043
  };
@@ -73802,7 +74048,7 @@ class AuthBrokerClient2 {
73802
74048
  async setOverride(agent, account) {
73803
74049
  const data = await this.send({
73804
74050
  v: PROTOCOL_VERSION,
73805
- id: randomUUID4(),
74051
+ id: randomUUID5(),
73806
74052
  op: "set-override",
73807
74053
  agent,
73808
74054
  account
@@ -75361,10 +75607,21 @@ function renderOperatorEvent(ev) {
75361
75607
  `),
75362
75608
  keyboard: {
75363
75609
  inline_keyboard: [
75364
- [
75365
- { text: "\uD83D\uDD10 Reauth", callback_data: `op:reauth:${encodeURIComponent(ev.agent)}` },
75366
- { text: "\u274c Dismiss", callback_data: `op:dismiss:${encodeURIComponent(ev.agent)}` }
75367
- ]
75610
+ [{ text: "\u274c Dismiss", callback_data: `op:dismiss:${encodeURIComponent(ev.agent)}` }]
75611
+ ]
75612
+ }
75613
+ };
75614
+ case "transport-transient":
75615
+ return {
75616
+ text: [
75617
+ `\uD83D\uDD0C **Transient transport error** for **${agent}**.`,
75618
+ detail ? `\`${detail}\`` : "",
75619
+ `A mid-response stream to Anthropic dropped. Will retry automatically.`
75620
+ ].filter(Boolean).join(`
75621
+ `),
75622
+ keyboard: {
75623
+ inline_keyboard: [
75624
+ [{ text: "\u274c Dismiss", callback_data: `op:dismiss:${encodeURIComponent(ev.agent)}` }]
75368
75625
  ]
75369
75626
  }
75370
75627
  };
@@ -75762,6 +76019,69 @@ function recordOperatorEvent(event, now = Date.now()) {
75762
76019
  store2.set(event.agent, { event, storedAt: now });
75763
76020
  }
75764
76021
 
76022
+ // gateway/user-failure-notices.ts
76023
+ init_format();
76024
+ var ESCALATION_WINDOW_MS = 10 * 60000;
76025
+ var ESCALATION_THRESHOLD = 3;
76026
+ var transportEscalation = new Map;
76027
+ function noteTransportTransientAndShouldEscalate(agent, now, opts) {
76028
+ const windowMs = opts?.windowMs ?? ESCALATION_WINDOW_MS;
76029
+ const threshold = opts?.threshold ?? ESCALATION_THRESHOLD;
76030
+ const recent = (transportEscalation.get(agent) ?? []).filter((t) => now - t < windowMs);
76031
+ recent.push(now);
76032
+ if (recent.length >= threshold) {
76033
+ transportEscalation.set(agent, []);
76034
+ return true;
76035
+ }
76036
+ transportEscalation.set(agent, recent);
76037
+ return false;
76038
+ }
76039
+ function renderTransportEscalationCard(agent) {
76040
+ const a = escapeMarkdown(agent);
76041
+ return {
76042
+ text: [
76043
+ `\uD83D\uDD0C **Repeated stream failures** reaching Anthropic for **${a}**.`,
76044
+ `3+ mid-response aborts within ~10 min. Turns retry automatically; if this persists, Anthropic's API may be degraded.`
76045
+ ].join(`
76046
+ `),
76047
+ keyboard: {
76048
+ inline_keyboard: [
76049
+ [{ text: "\u274c Dismiss", callback_data: `op:dismiss:${encodeURIComponent(agent)}` }]
76050
+ ]
76051
+ }
76052
+ };
76053
+ }
76054
+ function emitTransportTransientEvent(event, deps) {
76055
+ const now = deps.now();
76056
+ deps.record(event);
76057
+ const escalate = noteTransportTransientAndShouldEscalate(event.agent, now);
76058
+ deps.log(`transport-transient agent=${event.agent} escalate=${escalate} ` + `(no broadcast card; user-notice deferred to turn-end)`);
76059
+ const allowFrom = deps.allowFrom();
76060
+ if (allowFrom.length === 0)
76061
+ return;
76062
+ deps.scheduleUserNotice({
76063
+ chatIds: [...allowFrom],
76064
+ agent: event.agent,
76065
+ kind: event.kind,
76066
+ key: deps.liveTurnKey(),
76067
+ atMs: now
76068
+ });
76069
+ if (escalate) {
76070
+ const card = renderTransportEscalationCard(event.agent);
76071
+ deps.send(allowFrom[0], card.text, card.keyboard);
76072
+ }
76073
+ }
76074
+ function flushDeferredUserNotices(turnDeliveredReply, turnKey3, deps) {
76075
+ const notices = deps.resolveNotices(turnDeliveredReply, turnKey3);
76076
+ if (notices.length === 0)
76077
+ return;
76078
+ for (const notice of notices) {
76079
+ deps.log(`user-notice flush (turn died reply-less) agent=${notice.agent} ` + `kind=${notice.kind} chats=${notice.chatIds.length}`);
76080
+ for (const chatId of notice.chatIds)
76081
+ deps.send(chatId, notice.text);
76082
+ }
76083
+ }
76084
+
75765
76085
  // throttle-tier.ts
75766
76086
  init_card_format();
75767
76087
  init_quota_check();
@@ -75906,9 +76226,6 @@ function classifyKindAndSource(text4) {
75906
76226
  if (claudeKind === "rate-limited") {
75907
76227
  return { kind: "rate_limit", source: "anthropic" };
75908
76228
  }
75909
- if (claudeKind === "unknown-5xx") {
75910
- return { kind: "overload_529", source: "anthropic" };
75911
- }
75912
76229
  return { kind: "unknown", source: "anthropic" };
75913
76230
  }
75914
76231
  function providerById(id) {
@@ -76181,6 +76498,8 @@ function detectModelUnavailable2(stderr) {
76181
76498
  "socket hang up",
76182
76499
  "request timed out",
76183
76500
  "connection refused",
76501
+ "connection closed",
76502
+ "mid-response",
76184
76503
  "getaddrinfo"
76185
76504
  ];
76186
76505
  if (networkSignals.some((s) => lower.includes(s))) {
@@ -78298,6 +78617,146 @@ function resolveChatIdFallback(rawChatId, access, turnSessionChatId, lastKnownCh
78298
78617
  }
78299
78618
  return { chatId: rawChatId, tier: "raw" };
78300
78619
  }
78620
+
78621
+ // gateway/buzz-mirror.ts
78622
+ import { randomUUID as randomUUID6 } from "crypto";
78623
+ var CORRECTION_DEBOUNCE_MS2 = 30000;
78624
+ var MAX_TRACKED2 = 4096;
78625
+
78626
+ class BuzzMirror2 {
78627
+ cfg;
78628
+ log;
78629
+ sender = null;
78630
+ pending = new Map;
78631
+ pendingOrder = [];
78632
+ msgToBuzz = new Map;
78633
+ msgOrder = [];
78634
+ correctionTimers = new Map;
78635
+ constructor(cfg) {
78636
+ this.cfg = cfg;
78637
+ this.log = cfg.log ?? (() => {});
78638
+ }
78639
+ attachSender(sender) {
78640
+ this.sender = sender;
78641
+ }
78642
+ evict(map, order) {
78643
+ while (order.length > MAX_TRACKED2) {
78644
+ const k = order.shift();
78645
+ if (k !== undefined)
78646
+ map.delete(k);
78647
+ }
78648
+ }
78649
+ mirrorReplyDelivered(input) {
78650
+ try {
78651
+ const route = resolveRoute(input.ownerOriginChannel, this.cfg.mode, true);
78652
+ const buzzInRoute = route.primary === "buzz" || route.mirrors.includes("buzz");
78653
+ if (!buzzInRoute)
78654
+ return;
78655
+ let channelId;
78656
+ let replyToEventId;
78657
+ let threadRootId;
78658
+ if (input.ownerOriginChannel === "buzz" && input.ownerBuzzCoords) {
78659
+ if (!isBuzzThreadedPublishSafe({
78660
+ ownerEchoed: input.ownerEchoed,
78661
+ hasRecentDifferentOriginTurn: input.hasRecentDifferentOriginTurn
78662
+ })) {
78663
+ this.log("buzz-mirror: S1 guard blocked a threaded publish on an ambiguous " + "owner binding (un-echoed reply + a recent different-origin turn) " + "\u2014 delivered Telegram-only");
78664
+ return;
78665
+ }
78666
+ channelId = input.ownerBuzzCoords.channelId;
78667
+ replyToEventId = input.ownerBuzzCoords.eventId;
78668
+ threadRootId = input.ownerBuzzCoords.threadRoot;
78669
+ } else {
78670
+ if (!this.cfg.defaultChannelId)
78671
+ return;
78672
+ channelId = this.cfg.defaultChannelId;
78673
+ }
78674
+ this.publish({
78675
+ channelId,
78676
+ replyToEventId,
78677
+ threadRootId,
78678
+ payload: { kind: "message", text: input.scrubbedText }
78679
+ }, input.telegramMessageKeys);
78680
+ } catch (err) {
78681
+ this.log(`buzz-mirror: mirrorReplyDelivered threw (ignored): ${String(err)}`);
78682
+ }
78683
+ }
78684
+ mirrorCorrection(input) {
78685
+ try {
78686
+ const target = this.msgToBuzz.get(input.telegramMessageKey);
78687
+ if (!target)
78688
+ return;
78689
+ const existing = this.correctionTimers.get(input.telegramMessageKey);
78690
+ if (existing)
78691
+ clearTimeout(existing);
78692
+ const timer3 = setTimeout(() => {
78693
+ this.correctionTimers.delete(input.telegramMessageKey);
78694
+ const t = this.msgToBuzz.get(input.telegramMessageKey);
78695
+ if (!t)
78696
+ return;
78697
+ this.publish({
78698
+ channelId: t.channelId,
78699
+ replyToEventId: t.eventId,
78700
+ threadRootId: t.eventId,
78701
+ payload: {
78702
+ kind: "correction",
78703
+ text: input.scrubbedText,
78704
+ targetEventId: t.eventId
78705
+ }
78706
+ }, []);
78707
+ }, CORRECTION_DEBOUNCE_MS2);
78708
+ if (typeof timer3.unref === "function") {
78709
+ timer3.unref();
78710
+ }
78711
+ this.correctionTimers.set(input.telegramMessageKey, timer3);
78712
+ } catch (err) {
78713
+ this.log(`buzz-mirror: mirrorCorrection threw (ignored): ${String(err)}`);
78714
+ }
78715
+ }
78716
+ onPublishResult(msg) {
78717
+ const p = this.pending.get(msg.correlationId);
78718
+ this.pending.delete(msg.correlationId);
78719
+ if (!p)
78720
+ return;
78721
+ if (!msg.ok || !msg.eventId) {
78722
+ this.log(`buzz-mirror: publish failed (correlationId=${msg.correlationId.slice(0, 8)}` + `${msg.error ? ` error=${msg.error}` : ""}) \u2014 Telegram copy already delivered`);
78723
+ return;
78724
+ }
78725
+ for (const key of p.telegramMessageKeys) {
78726
+ this.msgToBuzz.set(key, { eventId: msg.eventId, channelId: p.channelId });
78727
+ this.msgOrder.push(key);
78728
+ }
78729
+ this.evict(this.msgToBuzz, this.msgOrder);
78730
+ }
78731
+ publish(fields, telegramMessageKeys) {
78732
+ if (!this.sender) {
78733
+ this.log("buzz-mirror: no Buzz peer connected \u2014 mirror dropped (Telegram copy delivered)");
78734
+ return;
78735
+ }
78736
+ const correlationId = randomUUID6();
78737
+ const msg = {
78738
+ type: "outbound_to_buzz",
78739
+ correlationId,
78740
+ agentName: this.cfg.agentName,
78741
+ ...fields
78742
+ };
78743
+ const sent = this.sender(msg);
78744
+ if (!sent) {
78745
+ this.log("buzz-mirror: Buzz peer send returned false \u2014 mirror dropped (Telegram copy delivered)");
78746
+ return;
78747
+ }
78748
+ this.pending.set(correlationId, {
78749
+ channelId: fields.channelId,
78750
+ telegramMessageKeys
78751
+ });
78752
+ this.pendingOrder.push(correlationId);
78753
+ this.evict(this.pending, this.pendingOrder);
78754
+ }
78755
+ }
78756
+ var singleton2 = null;
78757
+ function getBuzzMirror2() {
78758
+ return singleton2;
78759
+ }
78301
78760
  // over-ping-safety-net.ts
78302
78761
  function decideOverPing(input) {
78303
78762
  const substantive = input.substantive === true;
@@ -78393,7 +78852,9 @@ var INBOUND_SOURCE_CLASSIFICATION = {
78393
78852
  mental_model_proposal_denied: { decoupledCompletion: false },
78394
78853
  mental_model_proposal_failed: { decoupledCompletion: false },
78395
78854
  webhook: { decoupledCompletion: false },
78396
- linear: { decoupledCompletion: false }
78855
+ linear: { decoupledCompletion: false },
78856
+ buzz: { decoupledCompletion: false },
78857
+ boot_briefing: { decoupledCompletion: false }
78397
78858
  };
78398
78859
  function stampsHandbackMarker(source) {
78399
78860
  if (source == null)
@@ -79003,6 +79464,7 @@ async function sendReply(deps, req) {
79003
79464
  resolveReplyOwnerTurn,
79004
79465
  findTurnByOriginId,
79005
79466
  findTurnByQuotedMessageId,
79467
+ findLatestTurnForChat,
79006
79468
  resolveAnswerThreadWithLog,
79007
79469
  resolveThreadId,
79008
79470
  getLatestInboundMessageId: getLatestInboundMessageId2,
@@ -79736,6 +80198,23 @@ ${url}`;
79736
80198
  if (shouldJournalReplySiteDelivery({ text: rawText, disableNotification: modelDisableNotification })) {
79737
80199
  journalExternalDelivery({ turnNonce: t?.turnId ?? null, text: text4, tgMessageId: sentIds[sentIds.length - 1], replyAlreadyDeliveredThisTurn: true });
79738
80200
  }
80201
+ const buzzMirror = getBuzzMirror2();
80202
+ if (buzzMirror !== null) {
80203
+ const { turn: mOwnerTurn, tier: mOwnerTier } = resolveReplyOwnerTurn(turn, chat_id, args);
80204
+ const ownerOrigin = mOwnerTurn?.originChannel ?? "telegram";
80205
+ const ownerTurnId = mOwnerTurn?.turnId ?? null;
80206
+ const ownerEchoed = mOwnerTier === "origin";
80207
+ const latestEnded = findLatestTurnForChat(chat_id, { endedOnly: true });
80208
+ const hasRecentDifferentOriginTurn = [turn, latestEnded].some((c) => c != null && c.turnId !== ownerTurnId && c.originChannel !== ownerOrigin);
80209
+ buzzMirror.mirrorReplyDelivered({
80210
+ scrubbedText: text4,
80211
+ ownerOriginChannel: ownerOrigin,
80212
+ ownerBuzzCoords: mOwnerTurn?.buzzCoords,
80213
+ ownerEchoed,
80214
+ hasRecentDifferentOriginTurn,
80215
+ telegramMessageKeys: sentIds.map((id) => `${chat_id}:${id}`)
80216
+ });
80217
+ }
79739
80218
  }
79740
80219
  return { content: [{ type: "text", text: result }] };
79741
80220
  }
@@ -81127,6 +81606,8 @@ var QUEUED_CARD_ENABLED = process.env.SWITCHROOM_QUEUED_CARD !== "0";
81127
81606
  var QUEUED_CARD_HTML = "\u23f3 Queued \u2014 waiting for the current task to finish\u2026";
81128
81607
  var QUEUED_CARD_FOLDED_HTML = "\u2705 Folded into the current task.";
81129
81608
  var QUEUED_CARD_EXPIRED_HTML = "\u26a0\ufe0f This queued message timed out before it could start.";
81609
+ var BUZZ_ENABLED = process.env.BUZZ_ENABLED === "1" || process.env.BUZZ_ENABLED === "true";
81610
+ var BUZZ_ORIGIN_STAMP_ACTIVE = BUZZ_ENABLED && isBuzzTurnRoutingEnabled();
81130
81611
  var parkedTurnStarts = [];
81131
81612
  function pruneParkedTurnStarts(now, onDrop) {
81132
81613
  for (let i = parkedTurnStarts.length - 1;i >= 0; i--) {
@@ -81295,6 +81776,7 @@ function beginTurn(deps, ev) {
81295
81776
  startedAt,
81296
81777
  gatewayReceiveAt: startedAt,
81297
81778
  role: deriveTurnRole(ev.rawContent),
81779
+ ...BUZZ_ORIGIN_STAMP_ACTIVE ? parseChannelOrigin(ev.rawContent) : { originChannel: "telegram" },
81298
81780
  ...consumedCrossTurnGate != null ? { crossTurnGate: consumedCrossTurnGate } : {},
81299
81781
  replyCalled: false,
81300
81782
  finalAnswerDelivered: false,
@@ -90529,6 +91011,20 @@ function validateClientMessage(msg) {
90529
91011
  return false;
90530
91012
  return true;
90531
91013
  }
91014
+ case "hello_buzz_peer": {
91015
+ return typeof m.agentName === "string" && AGENT_NAME_RE3.test(m.agentName);
91016
+ }
91017
+ case "buzz_publish_result": {
91018
+ if (typeof m.correlationId !== "string" || m.correlationId.length === 0 || m.correlationId.length > 64)
91019
+ return false;
91020
+ if (typeof m.ok !== "boolean")
91021
+ return false;
91022
+ if (m.eventId !== undefined && (typeof m.eventId !== "string" || m.eventId.length > 128))
91023
+ return false;
91024
+ if (m.error !== undefined && (typeof m.error !== "string" || m.error.length > 500))
91025
+ return false;
91026
+ return true;
91027
+ }
90532
91028
  default:
90533
91029
  return false;
90534
91030
  }
@@ -90557,9 +91053,26 @@ function createIpcServer(options) {
90557
91053
  onRequestConfigFinalize,
90558
91054
  onRolloutStatusPost,
90559
91055
  onRolloutStatusEdit,
91056
+ onBuzzPublishResult,
90560
91057
  log = () => {},
90561
91058
  heartbeatTimeoutMs = 30000
90562
91059
  } = options;
91060
+ let buzzPeerClient = null;
91061
+ const BUZZ_INJECT_RING_MAX = 1024;
91062
+ const buzzInjectSeen = new Set;
91063
+ const buzzInjectOrder = [];
91064
+ const buzzInjectIsDuplicate = (eventId) => {
91065
+ if (buzzInjectSeen.has(eventId))
91066
+ return true;
91067
+ buzzInjectSeen.add(eventId);
91068
+ buzzInjectOrder.push(eventId);
91069
+ if (buzzInjectOrder.length > BUZZ_INJECT_RING_MAX) {
91070
+ const evicted = buzzInjectOrder.shift();
91071
+ if (evicted !== undefined)
91072
+ buzzInjectSeen.delete(evicted);
91073
+ }
91074
+ return false;
91075
+ };
90563
91076
  try {
90564
91077
  renameSync17(socketPath, socketPath + ".bak");
90565
91078
  } catch {}
@@ -90578,6 +91091,8 @@ function createIpcServer(options) {
90578
91091
  if (client3.topicId != null && topicIndex.get(client3.topicId) === client3) {
90579
91092
  topicIndex.delete(client3.topicId);
90580
91093
  }
91094
+ if (buzzPeerClient === client3)
91095
+ buzzPeerClient = null;
90581
91096
  loggedLegacyUpdatePlaceholder.delete(client3.id);
90582
91097
  onClientDisconnected(client3);
90583
91098
  log(`client disconnected: ${client3.id} (agent=${client3.agentName})`);
@@ -90633,10 +91148,19 @@ function createIpcServer(options) {
90633
91148
  if (onPtyPartial)
90634
91149
  onPtyPartial(client3, msg);
90635
91150
  break;
90636
- case "inject_inbound":
91151
+ case "inject_inbound": {
91152
+ const injectMsg = msg;
91153
+ const injMeta = injectMsg.inbound?.meta;
91154
+ if (injMeta && injMeta.source === "buzz" && typeof injMeta.buzz_event_id === "string") {
91155
+ if (buzzInjectIsDuplicate(injMeta.buzz_event_id)) {
91156
+ log(`inject_inbound: dropped duplicate buzz event ${injMeta.buzz_event_id.slice(0, 12)} (hub dedup ring)`);
91157
+ break;
91158
+ }
91159
+ }
90637
91160
  if (onInjectInbound)
90638
- onInjectInbound(client3, msg);
91161
+ onInjectInbound(client3, injectMsg);
90639
91162
  break;
91163
+ }
90640
91164
  case "send_outbound":
90641
91165
  if (onSendOutbound)
90642
91166
  onSendOutbound(client3, msg);
@@ -90780,6 +91304,17 @@ function createIpcServer(options) {
90780
91304
  }
90781
91305
  }
90782
91306
  break;
91307
+ case "hello_buzz_peer":
91308
+ handleHelloBuzzPeer(client3, msg);
91309
+ break;
91310
+ case "buzz_publish_result":
91311
+ if (!client3.isBuzzPeer) {
91312
+ log(`SECURITY: rejecting buzz_publish_result from non-peer connection ` + `(agent=${client3.agentName ?? "anonymous"} id=${client3.id}) \u2014 only the ` + `registered Buzz publish peer may report publish outcomes; dropped`);
91313
+ break;
91314
+ }
91315
+ if (onBuzzPublishResult)
91316
+ onBuzzPublishResult(client3, msg);
91317
+ break;
90783
91318
  case "update_placeholder":
90784
91319
  if (!loggedLegacyUpdatePlaceholder.has(client3.id)) {
90785
91320
  loggedLegacyUpdatePlaceholder.add(client3.id);
@@ -90790,7 +91325,39 @@ function createIpcServer(options) {
90790
91325
  log(`unknown IPC message type from client ${client3.id}: ${msg.type}`);
90791
91326
  }
90792
91327
  }
91328
+ function handleHelloBuzzPeer(client3, msg) {
91329
+ if (client3.agentName !== null || client3.isBuzzPeer) {
91330
+ log(`rejecting hello_buzz_peer: connection already has a role ` + `(agent=${client3.agentName ?? "none"} isBuzzPeer=${client3.isBuzzPeer}) \u2014 close+drop client=${client3.id}`);
91331
+ try {
91332
+ client3.close();
91333
+ } catch {}
91334
+ return;
91335
+ }
91336
+ if (buzzPeerClient && buzzPeerClient !== client3 && buzzPeerClient.isAlive()) {
91337
+ log(`SECURITY: rejecting hello_buzz_peer \u2014 a LIVE Buzz peer is already ` + `connected (live_id=${buzzPeerClient.id} rejected_id=${client3.id}); ` + `refusing displacement, close+drop`);
91338
+ try {
91339
+ client3.close();
91340
+ } catch {}
91341
+ return;
91342
+ }
91343
+ if (buzzPeerClient && buzzPeerClient !== client3) {
91344
+ log(`hello_buzz_peer: replacing prior (dead) buzz peer (prior_id=${buzzPeerClient.id} new_id=${client3.id})`);
91345
+ try {
91346
+ buzzPeerClient.close();
91347
+ } catch {}
91348
+ }
91349
+ client3.isBuzzPeer = true;
91350
+ buzzPeerClient = client3;
91351
+ log(`registered buzz publish peer for agent=${msg.agentName} id=${client3.id}`);
91352
+ }
90793
91353
  function handleRegister(client3, msg) {
91354
+ if (client3.isBuzzPeer) {
91355
+ log(`rejecting register: connection is the Buzz publish peer, not an agent bridge ` + `(close+drop client=${client3.id})`);
91356
+ try {
91357
+ client3.close();
91358
+ } catch {}
91359
+ return;
91360
+ }
90794
91361
  if (msg.agentName === "default") {
90795
91362
  log(`rejecting register: agentName="default" \u2014 anonymous bridges are not allowed (close+drop client=${client3.id})`);
90796
91363
  try {
@@ -90830,6 +91397,7 @@ function createIpcServer(options) {
90830
91397
  id;
90831
91398
  agentName = null;
90832
91399
  topicId = null;
91400
+ isBuzzPeer = false;
90833
91401
  lastHeartbeat = Date.now();
90834
91402
  _socket;
90835
91403
  _closed = false;
@@ -90939,6 +91507,12 @@ function createIpcServer(options) {
90939
91507
  clientCount() {
90940
91508
  return clients.size;
90941
91509
  },
91510
+ sendToBuzzPeer(msg) {
91511
+ if (!buzzPeerClient || !buzzPeerClient.isAlive())
91512
+ return false;
91513
+ buzzPeerClient.send(msg);
91514
+ return true;
91515
+ },
90942
91516
  async close() {
90943
91517
  if (watchdogTimer !== null) {
90944
91518
  clearInterval(watchdogTimer);
@@ -92400,6 +92974,9 @@ function spoolId(msg) {
92400
92974
  if ((msg.meta?.source === "resume_interrupted" || msg.meta?.source === "resume_watchdog_timeout" || msg.meta?.source === "resume_deferred") && typeof msg.meta?.resume_turn_key === "string" && msg.meta.resume_turn_key.length > 0) {
92401
92975
  return `s:resume:${msg.meta.resume_turn_key}`;
92402
92976
  }
92977
+ if (msg.meta?.source === "boot_briefing") {
92978
+ return `s:boot-briefing:${msg.chatId}`;
92979
+ }
92403
92980
  if (msg.meta?.source === "cron" && typeof msg.meta?.replay_fire_ms === "string" && msg.meta.replay_fire_ms.length > 0) {
92404
92981
  const idx = typeof msg.meta?.schedule_index === "string" && msg.meta.schedule_index.length > 0 ? msg.meta.schedule_index : "-";
92405
92982
  return `s:cron-replay:${msg.chatId}:${idx}:${msg.meta.replay_fire_ms}`;
@@ -92572,8 +93149,17 @@ function createInboundSpool(opts) {
92572
93149
  return {
92573
93150
  put(agent, msg) {
92574
93151
  const id = spoolId(msg);
92575
- if (live.has(id))
93152
+ const existing = live.get(id);
93153
+ if (existing != null) {
93154
+ if (msg.meta?.source === "boot_briefing") {
93155
+ existing.agent = agent;
93156
+ existing.msg = msg;
93157
+ appendRecord({ t: "put", id, agent, msg, firstAt: existing.firstAt });
93158
+ maybeCompact();
93159
+ return true;
93160
+ }
92576
93161
  return false;
93162
+ }
92577
93163
  const firstAt = now();
92578
93164
  live.set(id, { agent, msg, firstAt });
92579
93165
  appendRecord({ t: "put", id, agent, msg, firstAt });
@@ -95290,7 +95876,7 @@ import {
95290
95876
  writeSync as writeSync6
95291
95877
  } from "node:fs";
95292
95878
  import { join as join50 } from "node:path";
95293
- import { randomUUID as randomUUID5 } from "node:crypto";
95879
+ import { randomUUID as randomUUID7 } from "node:crypto";
95294
95880
  var PROPOSALS_FILE2 = "skill-proposals.jsonl";
95295
95881
  var REJECTED_FILE2 = "skill-proposals-rejected.jsonl";
95296
95882
  var REJECTION_TTL_MS2 = 90 * 24 * 60 * 60 * 1000;
@@ -95393,7 +95979,7 @@ function enqueueProposal(stateDir, input, opts = {}) {
95393
95979
  const now = opts.now ?? Date.now;
95394
95980
  ensureDir3(stateDir);
95395
95981
  const proposal = {
95396
- id: randomUUID5(),
95982
+ id: randomUUID7(),
95397
95983
  created_at: new Date(now()).toISOString(),
95398
95984
  status: "pending",
95399
95985
  ...input
@@ -96782,7 +97368,7 @@ ${errStr}`) ? "rate-limited" : "quota-exhausted" : classifyClaudeError({ type: e
96782
97368
  const raw = embeddedError ?? obj;
96783
97369
  const kind = classifyClaudeError(embeddedError ?? obj);
96784
97370
  const detail = extractDetailMessage(embeddedError) ?? extractDetailMessage(obj) ?? String(type ?? "");
96785
- const transient = kind === "rate-limited";
97371
+ const transient = kind === "rate-limited" || kind === "transport-transient";
96786
97372
  const retry = extractRetryState(obj);
96787
97373
  const terminal = !transient ? true : retry.retryAttempt != null && retry.maxRetries != null ? retry.retryAttempt >= retry.maxRetries : isErrorLine;
96788
97374
  return { kind, raw, detail, transient, terminal };
@@ -100660,7 +101246,7 @@ function startGatewayHeartbeat(stateDir, intervalMs = GATEWAY_HEARTBEAT_INTERVAL
100660
101246
  }
100661
101247
 
100662
101248
  // gateway/boot-beacon.ts
100663
- import { randomUUID as randomUUID6 } from "node:crypto";
101249
+ import { randomUUID as randomUUID8 } from "node:crypto";
100664
101250
  import {
100665
101251
  closeSync as closeSync14,
100666
101252
  fsyncSync as fsyncSync5,
@@ -100794,7 +101380,7 @@ function writeBootBeaconFile(stateDir, beacon) {
100794
101380
  return false;
100795
101381
  }
100796
101382
  }
100797
- var GATEWAY_BOOT_ID = randomUUID6();
101383
+ var GATEWAY_BOOT_ID = randomUUID8();
100798
101384
  function tickBootBeacon(stateDir, bootId = GATEWAY_BOOT_ID) {
100799
101385
  try {
100800
101386
  return writeBootBeaconFile(stateDir, buildBootBeacon({
@@ -101147,10 +101733,10 @@ function startOutboxSweep(deps) {
101147
101733
  }
101148
101734
 
101149
101735
  // ../src/build-info.ts
101150
- var VERSION2 = "0.19.48";
101151
- var COMMIT_SHA = "22eb634b";
101152
- var COMMIT_DATE = "2026-08-02T05:59:26Z";
101153
- var LATEST_PR = 4207;
101736
+ var VERSION2 = "0.20.0";
101737
+ var COMMIT_SHA = "86eaf06d";
101738
+ var COMMIT_DATE = "2026-08-03T03:02:51Z";
101739
+ var LATEST_PR = 4259;
101154
101740
  var COMMITS_AHEAD_OF_TAG = 0;
101155
101741
 
101156
101742
  // gateway/boot-version.ts
@@ -102533,8 +103119,338 @@ function selectResumeBuilder(endedVia, opts) {
102533
103119
  return kind;
102534
103120
  }
102535
103121
 
103122
+ // gateway/boot-briefing-wiring.ts
103123
+ init_history();
103124
+ import { existsSync as existsSync56, readFileSync as readFileSync59, writeFileSync as writeFileSync48 } from "node:fs";
103125
+ import { join as join68 } from "node:path";
103126
+
103127
+ // gateway/resume-inbound-builder.ts
103128
+ function humanizeElapsed2(ms) {
103129
+ if (!Number.isFinite(ms) || ms < 0)
103130
+ return "an unknown amount of time";
103131
+ const sec = Math.round(ms / 1000);
103132
+ if (sec < 45)
103133
+ return "moments";
103134
+ const min = Math.round(sec / 60);
103135
+ if (min < 60)
103136
+ return `~${min} min`;
103137
+ const hr = Math.round(min / 60);
103138
+ if (hr < 24)
103139
+ return `~${hr}h`;
103140
+ const days = Math.round(hr / 24);
103141
+ return `~${days} day${days === 1 ? "" : "s"}`;
103142
+ }
103143
+ var RESUME_SYNTHETIC_PROMPT_PREFIX2 = "You just restarted.";
103144
+
103145
+ // gateway/boot-briefing-builder.ts
103146
+ var BOOT_BRIEFING_SOURCE = "boot_briefing";
103147
+ var BRIEFING_CHAR_BUDGET = 7000;
103148
+ var BRIEFING_PER_MESSAGE_MAX_CHARS = 400;
103149
+ var BRIEFING_PRIMARY_DEPTH = 15;
103150
+ var BRIEFING_ACTIVE_WINDOW_MS = 48 * 60 * 60 * 1000;
103151
+ var BRIEFING_MAX_SURFACES = 8;
103152
+ var BRIEFING_TTL_MS = 60 * 60 * 1000;
103153
+ var HISTORY_SELFCHECK_CHAT = "__history_selfcheck__";
103154
+ function excludeWindowFromResumeInbound(msg) {
103155
+ if (msg == null)
103156
+ return null;
103157
+ const chatId = msg.meta?.chat_id;
103158
+ const startedAt = Number(msg.meta?.started_at);
103159
+ if (typeof chatId !== "string" || chatId.length === 0)
103160
+ return null;
103161
+ if (!Number.isFinite(startedAt) || startedAt <= 0)
103162
+ return null;
103163
+ const threadRaw = msg.meta?.message_thread_id;
103164
+ const threadNum = threadRaw != null && threadRaw !== "" ? Number(threadRaw) : null;
103165
+ return {
103166
+ chatId,
103167
+ threadId: threadNum != null && Number.isFinite(threadNum) ? threadNum : null,
103168
+ sinceMs: startedAt
103169
+ };
103170
+ }
103171
+ function sameSurface(a, b) {
103172
+ return a.chatId === b.chatId && (a.threadId ?? null) === (b.threadId ?? null);
103173
+ }
103174
+ function collectBriefingSurfaces(db3, opts) {
103175
+ const activeWindowMs = opts.activeWindowMs ?? BRIEFING_ACTIVE_WINDOW_MS;
103176
+ const primaryDepth = opts.primaryDepth ?? BRIEFING_PRIMARY_DEPTH;
103177
+ const maxSurfaces = opts.maxSurfaces ?? BRIEFING_MAX_SURFACES;
103178
+ const cutoffSec = Math.floor((opts.nowMs - activeWindowMs) / 1000);
103179
+ try {
103180
+ const surfaceRows = db3.prepare(`SELECT chat_id, thread_id, MAX(ts) AS last_ts
103181
+ FROM messages
103182
+ WHERE role IN ('user','assistant')
103183
+ AND ts >= ?
103184
+ AND chat_id <> ?
103185
+ GROUP BY chat_id, thread_id
103186
+ ORDER BY last_ts DESC
103187
+ LIMIT ?`).all(cutoffSec, HISTORY_SELFCHECK_CHAT, maxSurfaces);
103188
+ const out = [];
103189
+ for (let i = 0;i < surfaceRows.length; i++) {
103190
+ const s = surfaceRows[i];
103191
+ const depth = i === 0 ? primaryDepth : 1;
103192
+ const threadClause = s.thread_id == null ? "thread_id IS NULL" : "thread_id = ?";
103193
+ const params = [s.chat_id];
103194
+ if (s.thread_id != null)
103195
+ params.push(s.thread_id);
103196
+ params.push(depth);
103197
+ const msgRows = db3.prepare(`SELECT role, user, ts, text
103198
+ FROM messages
103199
+ WHERE chat_id = ? AND ${threadClause}
103200
+ AND role IN ('user','assistant')
103201
+ ORDER BY ts DESC, message_id DESC
103202
+ LIMIT ?`).all(...params);
103203
+ msgRows.reverse();
103204
+ let messages = msgRows.map((r) => ({
103205
+ role: r.role,
103206
+ user: r.user ?? null,
103207
+ ts: r.ts,
103208
+ text: r.text ?? ""
103209
+ }));
103210
+ const ex = opts.exclude;
103211
+ if (ex != null && sameSurface({ chatId: s.chat_id, threadId: s.thread_id ?? null }, ex)) {
103212
+ const sinceSec = Math.floor(ex.sinceMs / 1000);
103213
+ messages = messages.filter((m) => m.ts < sinceSec);
103214
+ }
103215
+ if (messages.length === 0)
103216
+ continue;
103217
+ out.push({
103218
+ chatId: s.chat_id,
103219
+ threadId: s.thread_id ?? null,
103220
+ lastTs: s.last_ts,
103221
+ messages
103222
+ });
103223
+ }
103224
+ return out;
103225
+ } catch {
103226
+ return [];
103227
+ }
103228
+ }
103229
+ function truncateOneLine(s, max) {
103230
+ const t = s.replace(/\s+/g, " ").trim();
103231
+ if (t.length <= max)
103232
+ return t;
103233
+ const points = Array.from(t);
103234
+ if (points.length <= max)
103235
+ return t;
103236
+ return points.slice(0, max - 1).join("").trimEnd() + "\u2026";
103237
+ }
103238
+ function surfaceLabel(s) {
103239
+ return s.threadId != null ? `chat ${s.chatId}, topic ${s.threadId}` : `chat ${s.chatId}`;
103240
+ }
103241
+ function renderMessageLine(m, nowMs3, perMessageMax) {
103242
+ const label = m.role === "user" ? m.user && m.user.trim() ? m.user.trim() : "user" : "you";
103243
+ const age = humanizeElapsed2(Math.max(0, nowMs3 - m.ts * 1000));
103244
+ return `- [${age} ago] ${label}: ${truncateOneLine(m.text, perMessageMax)}`;
103245
+ }
103246
+ function renderBootBriefing(surfaces, opts) {
103247
+ if (surfaces.length === 0)
103248
+ return "";
103249
+ const charBudget = opts.charBudget ?? BRIEFING_CHAR_BUDGET;
103250
+ const perMessageMax = opts.perMessageMax ?? BRIEFING_PER_MESSAGE_MAX_CHARS;
103251
+ const reasonClause = opts.restartReason && opts.restartReason.trim() ? ` The previous session ended via: ${truncateOneLine(opts.restartReason, 120)}.` : "";
103252
+ const header = `${RESUME_SYNTHETIC_PROMPT_PREFIX2} This is an automatic boot briefing assembled ` + `from your durable message history \u2014 context to reorient you, NOT a new user ` + `request.${reasonClause} Read it, then: if nothing in it is unfinished or owed, ` + `do NOT message the user (end the turn with NO_REPLY); if something was clearly ` + `left unfinished or owed, briefly pick it up. The full history is available via ` + `get_recent_messages.`;
103253
+ const primary = surfaces[0];
103254
+ const primaryTitle = `## Active conversation \u2014 ${surfaceLabel(primary)} ` + `(last active ${humanizeElapsed2(Math.max(0, opts.nowMs - primary.lastTs * 1000))} ago)`;
103255
+ const primaryLines = primary.messages.map((m) => renderMessageLine(m, opts.nowMs, perMessageMax));
103256
+ const assemble = (lines, secondaries2) => {
103257
+ const parts = [header, "", primaryTitle, ...lines];
103258
+ if (secondaries2.length > 0) {
103259
+ parts.push("", "## Other recent surfaces (active in the last 48h)", ...secondaries2);
103260
+ }
103261
+ return parts.join(`
103262
+ `);
103263
+ };
103264
+ const kept = [...primaryLines];
103265
+ while (kept.length > 1 && assemble(kept, []).length > charBudget) {
103266
+ kept.shift();
103267
+ }
103268
+ if (assemble(kept, []).length > charBudget) {
103269
+ return assemble(kept, []).slice(0, charBudget);
103270
+ }
103271
+ const secondaries = [];
103272
+ for (const s of surfaces.slice(1)) {
103273
+ const last = s.messages[s.messages.length - 1];
103274
+ const block = `- ${surfaceLabel(s)} \u2014 last active ` + `${humanizeElapsed2(Math.max(0, opts.nowMs - s.lastTs * 1000))} ago:
103275
+ ` + ` ${renderMessageLine(last, opts.nowMs, perMessageMax).slice(2)}`;
103276
+ if (assemble(kept, [...secondaries, block]).length > charBudget)
103277
+ break;
103278
+ secondaries.push(block);
103279
+ }
103280
+ return assemble(kept, secondaries);
103281
+ }
103282
+ function readRestartBreadcrumb(opts) {
103283
+ let reason = null;
103284
+ if (opts.restartReasonPath) {
103285
+ try {
103286
+ const raw = opts.readFile(opts.restartReasonPath);
103287
+ const first = raw.split(`
103288
+ `)[0]?.replace(/\r/g, "").trim();
103289
+ if (first)
103290
+ reason = first;
103291
+ } catch {}
103292
+ }
103293
+ const envVia = opts.env.SWITCHROOM_PENDING_ENDED_VIA;
103294
+ if (typeof envVia === "string" && envVia.trim().length > 0)
103295
+ reason = envVia.trim();
103296
+ return reason;
103297
+ }
103298
+ function decideBootBriefing(opts) {
103299
+ if (opts.briefingMode !== "gateway")
103300
+ return { build: false, reason: "flag-legacy" };
103301
+ if (opts.forceFreshMarker)
103302
+ return { build: false, reason: "force-fresh" };
103303
+ if (opts.resumeMode === "continue" || opts.resumeMode === "auto") {
103304
+ return { build: false, reason: "transcript-replay-possible" };
103305
+ }
103306
+ return { build: true, reason: "ok" };
103307
+ }
103308
+ function buildBootBriefingInbound(args) {
103309
+ const ts = args.nowMs ?? Date.now();
103310
+ const ttlMs = args.ttlMs ?? BRIEFING_TTL_MS;
103311
+ const meta = {
103312
+ source: BOOT_BRIEFING_SOURCE,
103313
+ chat_id: args.chatId,
103314
+ ...args.threadId != null ? { message_thread_id: String(args.threadId) } : {},
103315
+ message_id: String(ts),
103316
+ expiresAt: String(ts + ttlMs)
103317
+ };
103318
+ return {
103319
+ type: "inbound",
103320
+ chatId: args.chatId,
103321
+ ...args.threadId != null ? { threadId: args.threadId } : {},
103322
+ messageId: ts,
103323
+ user: "switchroom",
103324
+ userId: 0,
103325
+ ts,
103326
+ text: args.text,
103327
+ meta
103328
+ };
103329
+ }
103330
+
103331
+ // gateway/boot-briefing-wiring.ts
103332
+ function maybeQueueBootBriefing(opts) {
103333
+ const log = opts.log ?? ((l) => process.stderr.write(l));
103334
+ try {
103335
+ const agentDir = opts.stateDir.endsWith("/telegram") ? opts.stateDir.slice(0, -"/telegram".length) : opts.stateDir;
103336
+ const bootId = opts.env.SWITCHROOM_GATEWAY_BOOT_ID;
103337
+ const genMarkerPath = join68(agentDir, ".boot-briefing-generation");
103338
+ if (bootId) {
103339
+ let prevGen = null;
103340
+ try {
103341
+ prevGen = readFileSync59(genMarkerPath, "utf8").trim();
103342
+ } catch {
103343
+ prevGen = null;
103344
+ }
103345
+ if (prevGen === bootId) {
103346
+ log(`telegram gateway: boot-briefing suppressed (supervisor respawn \u2014 this boot generation already briefed)
103347
+ `);
103348
+ return null;
103349
+ }
103350
+ }
103351
+ const markGeneration = () => {
103352
+ if (!bootId)
103353
+ return;
103354
+ try {
103355
+ writeFileSync48(genMarkerPath, `${bootId}
103356
+ `);
103357
+ } catch {}
103358
+ };
103359
+ const forceFresh = opts.env.SWITCHROOM_FORCE_FRESH === "1" || existsSync56(join68(agentDir, ".force-fresh-session"));
103360
+ const decision = decideBootBriefing({
103361
+ briefingMode: opts.env.SWITCHROOM_SESSION_BRIEFING,
103362
+ resumeMode: opts.env.SWITCHROOM_RESUME_MODE,
103363
+ forceFreshMarker: forceFresh
103364
+ });
103365
+ if (!decision.build) {
103366
+ if (decision.reason !== "flag-legacy") {
103367
+ log(`telegram gateway: boot-briefing suppressed (${decision.reason})
103368
+ `);
103369
+ }
103370
+ return null;
103371
+ }
103372
+ const selfAgent = opts.env.SWITCHROOM_AGENT_NAME ?? "";
103373
+ if (!selfAgent)
103374
+ return null;
103375
+ const db3 = getHistoryDbForBriefing();
103376
+ if (db3 == null) {
103377
+ log(`telegram gateway: boot-briefing skipped \u2014 history DB unavailable
103378
+ `);
103379
+ return null;
103380
+ }
103381
+ const nowMs3 = opts.nowMs ?? Date.now();
103382
+ const surfaces = collectBriefingSurfaces(db3, {
103383
+ nowMs: nowMs3,
103384
+ exclude: excludeWindowFromResumeInbound(opts.resumeMsg)
103385
+ });
103386
+ const restartReason = readRestartBreadcrumb({
103387
+ restartReasonPath: join68(agentDir, ".restart-reason"),
103388
+ env: opts.env,
103389
+ readFile: (p) => readFileSync59(p, "utf8")
103390
+ });
103391
+ const text5 = renderBootBriefing(surfaces, { nowMs: nowMs3, restartReason });
103392
+ if (!text5) {
103393
+ markGeneration();
103394
+ log(`telegram gateway: boot-briefing empty (no recent surfaces) \u2014 nothing queued
103395
+ `);
103396
+ return null;
103397
+ }
103398
+ const primary = surfaces[0];
103399
+ const msg = buildBootBriefingInbound({
103400
+ chatId: primary.chatId,
103401
+ threadId: primary.threadId,
103402
+ text: text5,
103403
+ nowMs: nowMs3
103404
+ });
103405
+ opts.put(selfAgent, msg);
103406
+ markGeneration();
103407
+ log(`telegram gateway: boot-briefing queued chat=${primary.chatId}` + `${primary.threadId != null ? ` thread=${primary.threadId}` : ""} ` + `surfaces=${surfaces.length} chars=${text5.length}
103408
+ `);
103409
+ return msg;
103410
+ } catch (err) {
103411
+ log(`telegram gateway: boot-briefing failed (${err.message}) \u2014 continuing without briefing
103412
+ `);
103413
+ return null;
103414
+ }
103415
+ }
103416
+
103417
+ // gateway/pending-turn-env.ts
103418
+ import { existsSync as existsSync57, renameSync as renameSync26, rmSync as rmSync8, writeFileSync as writeFileSync49 } from "node:fs";
103419
+ import { join as join69 } from "node:path";
103420
+ function writePendingTurnEnv(agentDir, pending, log = (l) => process.stderr.write(l)) {
103421
+ const pendingEnvPath = join69(agentDir, ".pending-turn.env");
103422
+ try {
103423
+ if (pending != null) {
103424
+ const lines = [
103425
+ `SWITCHROOM_PENDING_TURN=true`,
103426
+ `SWITCHROOM_PENDING_TURN_KEY=${pending.turn_key}`,
103427
+ `SWITCHROOM_PENDING_CHAT_ID=${pending.chat_id}`,
103428
+ pending.thread_id != null ? `SWITCHROOM_PENDING_THREAD_ID=${pending.thread_id}` : `SWITCHROOM_PENDING_THREAD_ID=`,
103429
+ pending.last_user_msg_id != null ? `SWITCHROOM_PENDING_USER_MSG_ID=${pending.last_user_msg_id}` : `SWITCHROOM_PENDING_USER_MSG_ID=`,
103430
+ `SWITCHROOM_PENDING_ENDED_VIA=${pending.ended_via ?? "unknown"}`,
103431
+ `SWITCHROOM_PENDING_STARTED_AT=${pending.started_at}`,
103432
+ pending.interrupt_reason != null ? `SWITCHROOM_PENDING_INTERRUPT_REASON=${pending.interrupt_reason}` : `SWITCHROOM_PENDING_INTERRUPT_REASON=`
103433
+ ];
103434
+ const pendingEnvTmp = `${pendingEnvPath}.tmp-${process.pid}`;
103435
+ writeFileSync49(pendingEnvTmp, lines.join(`
103436
+ `) + `
103437
+ `, { mode: 384 });
103438
+ renameSync26(pendingEnvTmp, pendingEnvPath);
103439
+ log(`telegram gateway: pending-turn env written to ${pendingEnvPath} ` + `turnKey=${pending.turn_key} endedVia=${pending.ended_via ?? "open"}
103440
+ `);
103441
+ } else if (existsSync57(pendingEnvPath)) {
103442
+ rmSync8(pendingEnvPath, { force: true });
103443
+ log(`telegram gateway: pending-turn env cleared (clean previous shutdown)
103444
+ `);
103445
+ }
103446
+ } catch (err) {
103447
+ log(`telegram gateway: pending-turn env write failed (${err.message})
103448
+ `);
103449
+ }
103450
+ }
103451
+
102536
103452
  // gateway/bridge-dead-watchdog.ts
102537
- import { readFileSync as readFileSync59, writeFileSync as writeFileSync48, renameSync as renameSync26, unlinkSync as unlinkSync29 } from "node:fs";
103453
+ import { readFileSync as readFileSync60, writeFileSync as writeFileSync50, renameSync as renameSync27, unlinkSync as unlinkSync29 } from "node:fs";
102538
103454
 
102539
103455
  // gateway/cron-session.ts
102540
103456
  var CRON_IDENTITY_SUFFIX2 = "-cron";
@@ -102567,7 +103483,7 @@ function readFreshCrashLogTail(path3, opts = {}) {
102567
103483
  const nowMs3 = opts.nowMs ?? Date.now();
102568
103484
  const freshWindowMs = opts.freshWindowMs ?? CRASH_LOG_FRESH_WINDOW_MS;
102569
103485
  const maxLines = opts.maxLines ?? CRASH_LOG_TAIL_LINES;
102570
- const readFile = opts.readFile ?? ((p) => readFileSync59(p, "utf8"));
103486
+ const readFile = opts.readFile ?? ((p) => readFileSync60(p, "utf8"));
102571
103487
  let raw;
102572
103488
  try {
102573
103489
  raw = readFile(path3);
@@ -102588,13 +103504,13 @@ function readFreshCrashLogTail(path3, opts = {}) {
102588
103504
  }
102589
103505
  function writeBridgeDeadEscalationMarker(path3, marker) {
102590
103506
  const tmp = `${path3}.tmp-${process.pid}-${Date.now()}`;
102591
- writeFileSync48(tmp, JSON.stringify(marker), "utf8");
102592
- renameSync26(tmp, path3);
103507
+ writeFileSync50(tmp, JSON.stringify(marker), "utf8");
103508
+ renameSync27(tmp, path3);
102593
103509
  }
102594
103510
  function consumeBridgeDeadEscalationMarker(path3, nowMs3 = Date.now(), maxAgeMs = ESCALATION_MARKER_MAX_AGE_MS) {
102595
103511
  let marker = null;
102596
103512
  try {
102597
- const parsed = JSON.parse(readFileSync59(path3, "utf8"));
103513
+ const parsed = JSON.parse(readFileSync60(path3, "utf8"));
102598
103514
  if (typeof parsed.ts === "number" && Number.isFinite(parsed.ts) && typeof parsed.reason === "string") {
102599
103515
  const age = nowMs3 - parsed.ts;
102600
103516
  if (age >= 0 && age < maxAgeMs) {
@@ -102747,7 +103663,7 @@ function createBridgeDeadWatchdog(opts) {
102747
103663
  }
102748
103664
 
102749
103665
  // gateway/boot-probes.ts
102750
- import { readFileSync as readFileSync60, readdirSync as readdirSync14, existsSync as existsSync56 } from "fs";
103666
+ import { readFileSync as readFileSync61, readdirSync as readdirSync14, existsSync as existsSync58 } from "fs";
102751
103667
  init_quota_cache();
102752
103668
  init_generation_stamp();
102753
103669
  init_quota_check();
@@ -102756,7 +103672,7 @@ import { promisify as promisify2 } from "util";
102756
103672
  var execFile2 = promisify2(execFileCb2);
102757
103673
  var realProcFs2 = {
102758
103674
  readdir: (p) => readdirSync14(p),
102759
- readFile: (p) => readFileSync60(p, "utf-8")
103675
+ readFile: (p) => readFileSync61(p, "utf-8")
102760
103676
  };
102761
103677
  function findAgentProcessInContainer2(fs4 = realProcFs2) {
102762
103678
  let entries;
@@ -103006,7 +103922,7 @@ if (isGatewayMain) {
103006
103922
  shutdownAnalytics();
103007
103923
  });
103008
103924
  }
103009
- var STATE_DIR = process.env.TELEGRAM_STATE_DIR ?? join68(homedir20(), ".claude", "channels", "telegram");
103925
+ var STATE_DIR = process.env.TELEGRAM_STATE_DIR ?? join70(homedir20(), ".claude", "channels", "telegram");
103010
103926
  var permCardStore = createPermissionCardStore(STATE_DIR);
103011
103927
  var BLOCKED_APPROVALS_DIR = process.env.SWITCHROOM_BLOCKED_APPROVALS_DIR ?? "/state/blocked-approvals";
103012
103928
  var AGENT_NAME = process.env.SWITCHROOM_AGENT_NAME ?? "agent";
@@ -103045,7 +103961,7 @@ function alwaysAllowDrainDeps() {
103045
103961
  return {
103046
103962
  readConfigText: () => {
103047
103963
  const cfgPath = process.env.SWITCHROOM_CONFIG ?? SWITCHROOM_CONFIG ?? findConfigFile2();
103048
- return readFileSync61(cfgPath, "utf8");
103964
+ return readFileSync62(cfgPath, "utf8");
103049
103965
  },
103050
103966
  resolveAllowList: (_configText, agentName3) => {
103051
103967
  const cfg = loadConfig2();
@@ -103106,11 +104022,11 @@ function scheduleAlwaysAllowPersistDrain() {
103106
104022
  }, ALWAYS_ALLOW_DRAIN_INTERVAL_MS);
103107
104023
  timer3.unref?.();
103108
104024
  }
103109
- var ACCESS_FILE = join68(STATE_DIR, "access.json");
103110
- var APPROVED_DIR = join68(STATE_DIR, "approved");
103111
- var ENV_FILE = join68(STATE_DIR, ".env");
103112
- var INBOX_DIR = join68(STATE_DIR, "inbox");
103113
- var PEOPLE_FILE = join68(STATE_DIR, "people.json");
104025
+ var ACCESS_FILE = join70(STATE_DIR, "access.json");
104026
+ var APPROVED_DIR = join70(STATE_DIR, "approved");
104027
+ var ENV_FILE = join70(STATE_DIR, ".env");
104028
+ var INBOX_DIR = join70(STATE_DIR, "inbox");
104029
+ var PEOPLE_FILE = join70(STATE_DIR, "people.json");
103114
104030
  function triggerSelfRestart(targetAgent, reason, delayMs = 300) {
103115
104031
  const isDocker = process.env.SWITCHROOM_RUNTIME === "docker";
103116
104032
  const selfAgent = process.env.SWITCHROOM_AGENT_NAME;
@@ -103175,7 +104091,7 @@ function formatBootVersion() {
103175
104091
  }
103176
104092
  try {
103177
104093
  chmodSync14(ENV_FILE, 384);
103178
- for (const line of readFileSync61(ENV_FILE, "utf8").split(`
104094
+ for (const line of readFileSync62(ENV_FILE, "utf8").split(`
103179
104095
  `)) {
103180
104096
  const m = line.match(/^(\w+)=(.*)$/);
103181
104097
  if (m && process.env[m[1]] === undefined)
@@ -103196,7 +104112,7 @@ var bot;
103196
104112
  var lastGetUpdatesHeartbeatMs = Date.now();
103197
104113
  var GRAMMY_VERSION = (() => {
103198
104114
  try {
103199
- const raw = readFileSync61(new URL("../../node_modules/grammy/package.json", import.meta.url), "utf8");
104115
+ const raw = readFileSync62(new URL("../../node_modules/grammy/package.json", import.meta.url), "utf8");
103200
104116
  return JSON.parse(raw).version ?? "unknown";
103201
104117
  } catch {
103202
104118
  return "unknown";
@@ -103240,7 +104156,7 @@ function assertSendable(f) {
103240
104156
  } catch {
103241
104157
  throw new Error(`refusing to send file \u2014 cannot resolve real path: ${f}`);
103242
104158
  }
103243
- const inbox = join68(stateReal, "inbox");
104159
+ const inbox = join70(stateReal, "inbox");
103244
104160
  if (real.startsWith(stateReal + sep4) && !real.startsWith(inbox + sep4)) {
103245
104161
  throw new Error(`refusing to send channel state: ${f}`);
103246
104162
  }
@@ -103259,7 +104175,7 @@ function assertSendable(f) {
103259
104175
  }
103260
104176
  function readAccessFile() {
103261
104177
  try {
103262
- const raw = readFileSync61(ACCESS_FILE, "utf8");
104178
+ const raw = readFileSync62(ACCESS_FILE, "utf8");
103263
104179
  const parsed = JSON.parse(raw);
103264
104180
  const allowFrom = validateStringArray("allowFrom", parsed.allowFrom ?? []);
103265
104181
  const groups = {};
@@ -103299,7 +104215,7 @@ function readAccessFile() {
103299
104215
  if (err.code === "ENOENT")
103300
104216
  return defaultAccess();
103301
104217
  try {
103302
- renameSync27(ACCESS_FILE, `${ACCESS_FILE}.corrupt-${Date.now()}`);
104218
+ renameSync28(ACCESS_FILE, `${ACCESS_FILE}.corrupt-${Date.now()}`);
103303
104219
  } catch {}
103304
104220
  process.stderr.write(`telegram gateway: access.json is corrupt, moved aside. Starting fresh.
103305
104221
  `);
@@ -103321,7 +104237,7 @@ function loadAccess() {
103321
104237
  }
103322
104238
  function readPeopleFile() {
103323
104239
  try {
103324
- const raw = readFileSync61(PEOPLE_FILE, "utf8");
104240
+ const raw = readFileSync62(PEOPLE_FILE, "utf8");
103325
104241
  const parsed = JSON.parse(raw);
103326
104242
  if (!Array.isArray(parsed.entries))
103327
104243
  return [];
@@ -103345,9 +104261,9 @@ function saveAccess(a) {
103345
104261
  return;
103346
104262
  mkdirSync51(STATE_DIR, { recursive: true, mode: 448 });
103347
104263
  const tmp = ACCESS_FILE + ".tmp";
103348
- writeFileSync50(tmp, JSON.stringify(a, null, 2) + `
104264
+ writeFileSync52(tmp, JSON.stringify(a, null, 2) + `
103349
104265
  `, { mode: 384 });
103350
- renameSync27(tmp, ACCESS_FILE);
104266
+ renameSync28(tmp, ACCESS_FILE);
103351
104267
  }
103352
104268
  function pruneExpired(a) {
103353
104269
  const now = Date.now();
@@ -103365,7 +104281,7 @@ var HISTORY_ENABLED = HISTORY_ACCESS.historyEnabled !== false;
103365
104281
  if (isGatewayMain && HISTORY_ENABLED) {
103366
104282
  try {
103367
104283
  initHistory(STATE_DIR, HISTORY_ACCESS.historyRetentionDays ?? 30);
103368
- process.stderr.write(`telegram gateway: history capture enabled at ${join68(STATE_DIR, "history.db")}
104284
+ process.stderr.write(`telegram gateway: history capture enabled at ${join70(STATE_DIR, "history.db")}
103369
104285
  `);
103370
104286
  } catch (err) {
103371
104287
  process.stderr.write(`telegram gateway: history init failed (${err.message}) \u2014 capture disabled
@@ -103384,12 +104300,12 @@ if (isGatewayMain)
103384
104300
  let markerTurnKey = null;
103385
104301
  let markerAgeMs = null;
103386
104302
  try {
103387
- const markerPath = join68(STATE_DIR, TURN_ACTIVE_MARKER_FILE2);
103388
- if (existsSync58(markerPath)) {
104303
+ const markerPath = join70(STATE_DIR, TURN_ACTIVE_MARKER_FILE2);
104304
+ if (existsSync60(markerPath)) {
103389
104305
  const st = statSync23(markerPath);
103390
104306
  markerAgeMs = Date.now() - st.mtimeMs;
103391
104307
  try {
103392
- const payload = JSON.parse(readFileSync61(markerPath, "utf8"));
104308
+ const payload = JSON.parse(readFileSync62(markerPath, "utf8"));
103393
104309
  if (typeof payload.turnKey === "string" && payload.turnKey.length > 0) {
103394
104310
  markerTurnKey = payload.turnKey;
103395
104311
  }
@@ -103409,10 +104325,10 @@ if (isGatewayMain)
103409
104325
  process.stderr.write(`telegram gateway: turn-registry boot-reaper stamped ${reaped} orphaned turn(s)` + `${timeoutTurnKey ? ` (turnKey=${timeoutTurnKey} as 'timeout', markerAgeMs=${markerAgeMs})` : " as 'restart'"}
103410
104326
  `);
103411
104327
  } else {
103412
- process.stderr.write(`telegram gateway: turn-registry initialized at ${join68(agentDir, "telegram", "registry.db")}
104328
+ process.stderr.write(`telegram gateway: turn-registry initialized at ${join70(agentDir, "telegram", "registry.db")}
103413
104329
  `);
103414
104330
  }
103415
- const bridgeDeadMarker = consumeBridgeDeadEscalationMarker(join68(STATE_DIR, "bridge-dead-escalation.json"));
104331
+ const bridgeDeadMarker = consumeBridgeDeadEscalationMarker(join70(STATE_DIR, "bridge-dead-escalation.json"));
103416
104332
  if (bridgeDeadMarker != null) {
103417
104333
  bridgeDeadPriorStreak = bridgeDeadMarker.count ?? 1;
103418
104334
  process.stderr.write(`telegram gateway: boot: prior restart was a bridge-dead escalation (reason=${bridgeDeadMarker.reason}` + `, consecutive=${bridgeDeadPriorStreak}` + `${bridgeDeadMarker.crashTail ? `, crashTail=${bridgeDeadMarker.crashTail}` : ""})
@@ -103425,7 +104341,7 @@ if (isGatewayMain)
103425
104341
  const pending2 = findLatestTurnIfInterrupted(turnsDb);
103426
104342
  const selfAgent = process.env.SWITCHROOM_AGENT_NAME ?? "";
103427
104343
  if (pending2 != null && selfAgent) {
103428
- const bootResumeMarkerPath = process.env.SWITCHROOM_GATEWAY_CLEAN_SHUTDOWN_MARKER ?? join68(STATE_DIR, "clean-shutdown.json");
104344
+ const bootResumeMarkerPath = process.env.SWITCHROOM_GATEWAY_CLEAN_SHUTDOWN_MARKER ?? join70(STATE_DIR, "clean-shutdown.json");
103429
104345
  const bootResumeCleanMarker = readCleanShutdownMarker(bootResumeMarkerPath);
103430
104346
  const bootResumeForceAlways = process.env.SWITCHROOM_BOOT_RESUME_ALWAYS === "1";
103431
104347
  const bootResumeMode = parseBootResumeMode(process.env.SWITCHROOM_BOOT_RESUME);
@@ -103538,35 +104454,7 @@ if (isGatewayMain)
103538
104454
  `);
103539
104455
  }
103540
104456
  }
103541
- const pendingEnvPath = join68(agentDir, ".pending-turn.env");
103542
- try {
103543
- if (pending2 != null) {
103544
- const lines = [
103545
- `SWITCHROOM_PENDING_TURN=true`,
103546
- `SWITCHROOM_PENDING_TURN_KEY=${pending2.turn_key}`,
103547
- `SWITCHROOM_PENDING_CHAT_ID=${pending2.chat_id}`,
103548
- pending2.thread_id != null ? `SWITCHROOM_PENDING_THREAD_ID=${pending2.thread_id}` : `SWITCHROOM_PENDING_THREAD_ID=`,
103549
- pending2.last_user_msg_id != null ? `SWITCHROOM_PENDING_USER_MSG_ID=${pending2.last_user_msg_id}` : `SWITCHROOM_PENDING_USER_MSG_ID=`,
103550
- `SWITCHROOM_PENDING_ENDED_VIA=${pending2.ended_via ?? "unknown"}`,
103551
- `SWITCHROOM_PENDING_STARTED_AT=${pending2.started_at}`,
103552
- pending2.interrupt_reason != null ? `SWITCHROOM_PENDING_INTERRUPT_REASON=${pending2.interrupt_reason}` : `SWITCHROOM_PENDING_INTERRUPT_REASON=`
103553
- ];
103554
- const pendingEnvTmp = `${pendingEnvPath}.tmp-${process.pid}`;
103555
- writeFileSync50(pendingEnvTmp, lines.join(`
103556
- `) + `
103557
- `, { mode: 384 });
103558
- renameSync27(pendingEnvTmp, pendingEnvPath);
103559
- process.stderr.write(`telegram gateway: pending-turn env written to ${pendingEnvPath} turnKey=${pending2.turn_key} endedVia=${pending2.ended_via ?? "open"}
103560
- `);
103561
- } else if (existsSync58(pendingEnvPath)) {
103562
- rmSync8(pendingEnvPath, { force: true });
103563
- process.stderr.write(`telegram gateway: pending-turn env cleared (clean previous shutdown)
103564
- `);
103565
- }
103566
- } catch (err) {
103567
- process.stderr.write(`telegram gateway: pending-turn env write failed (${err.message})
103568
- `);
103569
- }
104457
+ writePendingTurnEnv(agentDir, pending2);
103570
104458
  } catch (err) {
103571
104459
  process.stderr.write(`telegram gateway: turn-registry init failed (${err.message}) \u2014 turn tracking disabled
103572
104460
  `);
@@ -103666,11 +104554,11 @@ function checkApprovals() {
103666
104554
  return;
103667
104555
  }
103668
104556
  for (const senderId of files) {
103669
- const file = join68(APPROVED_DIR, senderId);
103670
- bot.api.sendMessage(senderId, "Paired! Say hi to Claude.").then(() => rmSync8(file, { force: true }), (err) => {
104557
+ const file = join70(APPROVED_DIR, senderId);
104558
+ bot.api.sendMessage(senderId, "Paired! Say hi to Claude.").then(() => rmSync9(file, { force: true }), (err) => {
103671
104559
  process.stderr.write(`telegram gateway: failed to send approval confirm: ${err}
103672
104560
  `);
103673
- rmSync8(file, { force: true });
104561
+ rmSync9(file, { force: true });
103674
104562
  });
103675
104563
  }
103676
104564
  }
@@ -103844,12 +104732,12 @@ function noteAgentOutputAt(key, ts) {
103844
104732
  lastAgentOutputAt.delete(oldest);
103845
104733
  }
103846
104734
  }
103847
- var OBLIGATION_STORE_PATH = join68(STATE_DIR, "obligations.json");
104735
+ var OBLIGATION_STORE_PATH = join70(STATE_DIR, "obligations.json");
103848
104736
  var obligationStoreFs = {
103849
- readFileSync: (p) => readFileSync61(p, "utf8"),
103850
- writeFileSync: (p, d) => writeFileSync50(p, d),
103851
- renameSync: (a, b) => renameSync27(a, b),
103852
- existsSync: (p) => existsSync58(p),
104737
+ readFileSync: (p) => readFileSync62(p, "utf8"),
104738
+ writeFileSync: (p, d) => writeFileSync52(p, d),
104739
+ renameSync: (a, b) => renameSync28(a, b),
104740
+ existsSync: (p) => existsSync60(p),
103853
104741
  fsyncFileSync: fsyncPathSync,
103854
104742
  fsyncDirSync: fsyncPathSync,
103855
104743
  unlinkSync: unlinkSync31
@@ -104610,7 +105498,7 @@ function emitTurnRecord(turn, endedAt) {
104610
105498
  return;
104611
105499
  }
104612
105500
  },
104613
- rename: (from, to) => renameSync27(from, to)
105501
+ rename: (from, to) => renameSync28(from, to)
104614
105502
  });
104615
105503
  appendFileSync9(turnsPath, rec);
104616
105504
  } catch {}
@@ -105930,6 +106818,10 @@ var inboundCoalescer = createInboundCoalescer({
105930
106818
  function emitGatewayOperatorEvent(event) {
105931
106819
  const { agent, kind } = event;
105932
106820
  event = { ...event, detail: redactOutboundText(event.detail, "operator_event") };
106821
+ if (kind === "transport-transient") {
106822
+ emitTransportTransientEvent(event, userFailureNoticeDeps());
106823
+ return;
106824
+ }
105933
106825
  let throttleEscalation = null;
105934
106826
  let escalationFired = false;
105935
106827
  let rateLimitedCooldownConsulted = false;
@@ -106084,40 +106976,37 @@ function emitGatewayOperatorEvent(event) {
106084
106976
  });
106085
106977
  }
106086
106978
  if (userNoticeChats.length > 0) {
106087
- const liveTurn = currentTurn;
106088
- const noticeKey = liveTurn != null ? statusKey(liveTurn.sessionChatId, liveTurn.sessionThreadId) : undefined;
106089
- pendingUserNoticeGate.schedule({
106090
- chatIds: userNoticeChats,
106091
- text: renderUserFacingFailureNotice(),
106092
- agent,
106093
- kind,
106094
- atMs: Date.now(),
106095
- key: noticeKey
106096
- });
106979
+ const noticeDeps = userFailureNoticeDeps();
106980
+ const noticeKey = noticeDeps.liveTurnKey();
106981
+ noticeDeps.scheduleUserNotice({ chatIds: userNoticeChats, agent, kind, key: noticeKey, atMs: Date.now() });
106097
106982
  process.stderr.write(`telegram gateway: operator-event user-notice deferred to turn-end agent=${agent} kind=${kind} chats=${userNoticeChats.length} topic=${noticeKey ?? "-"}
106098
106983
  `);
106099
106984
  }
106100
106985
  }
106986
+ function userFailureNoticeDeps() {
106987
+ return {
106988
+ now: () => Date.now(),
106989
+ allowFrom: () => loadAccess().allowFrom,
106990
+ liveTurnKey: () => currentTurn != null ? statusKey(currentTurn.sessionChatId, currentTurn.sessionThreadId) : undefined,
106991
+ record: (e) => {
106992
+ try {
106993
+ recordOperatorEvent(e);
106994
+ } catch {}
106995
+ },
106996
+ scheduleUserNotice: (i) => pendingUserNoticeGate.schedule({ ...i, text: renderUserFacingFailureNotice() }),
106997
+ resolveNotices: (delivered, key) => pendingUserNoticeGate.resolveTurnEnd(key, delivered),
106998
+ send: (chat_id, text5, keyboard) => {
106999
+ const thread = topicForRecipient({ recipientChatId: chat_id, resolvedTopic: resolveAgentOutboundTopic({ kind: "compact-watchdog" }), supergroupChatId: resolveAgentSupergroupChatId() });
107000
+ const opts = { ...keyboard ? { reply_markup: keyboard } : {}, ...thread != null ? { message_thread_id: thread } : {} };
107001
+ bot.api.sendRichMessage(chat_id, richMessage2(text5), opts).catch((e) => process.stderr.write(`telegram gateway: user-failure-notice send to ${chat_id} failed: ${e}
107002
+ `));
107003
+ },
107004
+ log: (m) => process.stderr.write(`telegram gateway: ${m}
107005
+ `)
107006
+ };
107007
+ }
106101
107008
  function flushPendingUserFailureNotices(turnDeliveredReply, turnKey3) {
106102
- const notices = pendingUserNoticeGate.resolveTurnEnd(turnKey3, turnDeliveredReply);
106103
- if (notices.length === 0)
106104
- return;
106105
- const noticeTopic = resolveAgentOutboundTopic({ kind: "compact-watchdog" });
106106
- const noticeSupergroup = resolveAgentSupergroupChatId();
106107
- for (const notice of notices) {
106108
- process.stderr.write(`telegram gateway: user-notice flush (turn died reply-less) agent=${notice.agent} kind=${notice.kind} chats=${notice.chatIds.length}
106109
- `);
106110
- for (const chat_id of notice.chatIds) {
106111
- const thread = topicForRecipient({ recipientChatId: chat_id, resolvedTopic: noticeTopic, supergroupChatId: noticeSupergroup });
106112
- const opts = {
106113
- ...thread != null ? { message_thread_id: thread } : {}
106114
- };
106115
- bot.api.sendRichMessage(chat_id, richMessage2(notice.text), opts).catch((e) => {
106116
- process.stderr.write(`telegram gateway: user-notice send to ${chat_id} failed agent=${notice.agent} kind=${notice.kind}: ${e}
106117
- `);
106118
- });
106119
- }
106120
- }
107009
+ flushDeferredUserNotices(turnDeliveredReply, turnKey3, userFailureNoticeDeps());
106121
107010
  }
106122
107011
  function postLegacyBanner(chatId, threadId, ackMessageId, ageSec, site) {
106123
107012
  const text5 = `\uD83C\uDF9B\uFE0F Switchroom restarted \u2014 ready. (took ~${ageSec}s)`;
@@ -106173,28 +107062,28 @@ var PIN_STATUS_WHILE_WORKING = (() => {
106173
107062
  })();
106174
107063
  var statusPinClaims = new Map;
106175
107064
  var statusPinRightsCache = new PinRightsCache2;
106176
- var STATUS_PIN_STORE_PATH = join68(STATE_DIR, "status-pins.json");
107065
+ var STATUS_PIN_STORE_PATH = join70(STATE_DIR, "status-pins.json");
106177
107066
  var statusPinStoreFs = {
106178
- readFileSync: (p) => readFileSync61(p, "utf8"),
106179
- writeFileSync: (p, d) => writeFileSync50(p, d),
106180
- renameSync: (a, b) => renameSync27(a, b),
106181
- existsSync: (p) => existsSync58(p)
107067
+ readFileSync: (p) => readFileSync62(p, "utf8"),
107068
+ writeFileSync: (p, d) => writeFileSync52(p, d),
107069
+ renameSync: (a, b) => renameSync28(a, b),
107070
+ existsSync: (p) => existsSync60(p)
106182
107071
  };
106183
107072
  var statusPinPersistEnabled = !STATIC && PIN_STATUS_WHILE_WORKING;
106184
- var ACTIVITY_CARD_STORE_PATH = join68(STATE_DIR, "activity-cards-pending.json");
107073
+ var ACTIVITY_CARD_STORE_PATH = join70(STATE_DIR, "activity-cards-pending.json");
106185
107074
  var activityCardStoreFs = {
106186
- readFileSync: (p) => readFileSync61(p, "utf8"),
106187
- writeFileSync: (p, d) => writeFileSync50(p, d),
106188
- renameSync: (a, b) => renameSync27(a, b),
106189
- existsSync: (p) => existsSync58(p)
107075
+ readFileSync: (p) => readFileSync62(p, "utf8"),
107076
+ writeFileSync: (p, d) => writeFileSync52(p, d),
107077
+ renameSync: (a, b) => renameSync28(a, b),
107078
+ existsSync: (p) => existsSync60(p)
106190
107079
  };
106191
107080
  var activityCardPersistEnabled = !STATIC;
106192
- var QUEUED_CARD_STORE_PATH = join68(STATE_DIR, "queued-cards-pending.json");
107081
+ var QUEUED_CARD_STORE_PATH = join70(STATE_DIR, "queued-cards-pending.json");
106193
107082
  var queuedCardStoreFs = {
106194
- readFileSync: (p) => readFileSync61(p, "utf8"),
106195
- writeFileSync: (p, d) => writeFileSync50(p, d),
106196
- renameSync: (a, b) => renameSync27(a, b),
106197
- existsSync: (p) => existsSync58(p)
107083
+ readFileSync: (p) => readFileSync62(p, "utf8"),
107084
+ writeFileSync: (p, d) => writeFileSync52(p, d),
107085
+ renameSync: (a, b) => renameSync28(a, b),
107086
+ existsSync: (p) => existsSync60(p)
106198
107087
  };
106199
107088
  var queuedCardPersistEnabled = !STATIC;
106200
107089
  function persistQueuedCard(key, chatId, threadId, messageId) {
@@ -106481,7 +107370,7 @@ async function unpinAllStatusPins() {
106481
107370
  }
106482
107371
  }
106483
107372
  var stalePinSweepEligible = false;
106484
- var STALE_PIN_SWEEP_STORE_PATH = join68(STATE_DIR, "stale-pin-sweep.json");
107373
+ var STALE_PIN_SWEEP_STORE_PATH = join70(STATE_DIR, "stale-pin-sweep.json");
106485
107374
  var stalePinSweeper = createGatewayStalePinSweeper({
106486
107375
  telegram: { handle: () => lockedBot, call: robustApiCall },
106487
107376
  claims: () => statusPinClaims.values(),
@@ -106490,9 +107379,9 @@ var stalePinSweeper = createGatewayStalePinSweeper({
106490
107379
  store: {
106491
107380
  path: STALE_PIN_SWEEP_STORE_PATH,
106492
107381
  fs: {
106493
- readFileSync: (p) => readFileSync61(p, "utf-8"),
107382
+ readFileSync: (p) => readFileSync62(p, "utf-8"),
106494
107383
  writeFileSync: (p, data) => atomicWriteFileSync(p, data, 384),
106495
- existsSync: (p) => existsSync58(p)
107384
+ existsSync: (p) => existsSync60(p)
106496
107385
  }
106497
107386
  },
106498
107387
  allowUnpinAllForumTopic: process.env.SWITCHROOM_PIN_SWEEP_UNPIN_ALL_TOPIC == null ? undefined : process.env.SWITCHROOM_PIN_SWEEP_UNPIN_ALL_TOPIC === "1"
@@ -106522,12 +107411,12 @@ var getPinnedProgressCardMessageId = null;
106522
107411
  var completeProgressCardTurn = null;
106523
107412
  var subagentWatcher = null;
106524
107413
  var workerActivityFeed = null;
106525
- var SOCKET_PATH = process.env.SWITCHROOM_GATEWAY_SOCKET ?? join68(STATE_DIR, "gateway.sock");
107414
+ var SOCKET_PATH = process.env.SWITCHROOM_GATEWAY_SOCKET ?? join70(STATE_DIR, "gateway.sock");
106526
107415
  if (isGatewayMain)
106527
107416
  mkdirSync51(STATE_DIR, { recursive: true, mode: 448 });
106528
- var GATEWAY_PID_PATH = process.env.SWITCHROOM_GATEWAY_PID_FILE ?? join68(STATE_DIR, "gateway.pid.json");
106529
- var GATEWAY_SESSION_MARKER_PATH = process.env.SWITCHROOM_GATEWAY_SESSION_MARKER ?? join68(STATE_DIR, "gateway-session.json");
106530
- var GATEWAY_CLEAN_SHUTDOWN_MARKER_PATH = process.env.SWITCHROOM_GATEWAY_CLEAN_SHUTDOWN_MARKER ?? join68(STATE_DIR, "clean-shutdown.json");
107417
+ var GATEWAY_PID_PATH = process.env.SWITCHROOM_GATEWAY_PID_FILE ?? join70(STATE_DIR, "gateway.pid.json");
107418
+ var GATEWAY_SESSION_MARKER_PATH = process.env.SWITCHROOM_GATEWAY_SESSION_MARKER ?? join70(STATE_DIR, "gateway-session.json");
107419
+ var GATEWAY_CLEAN_SHUTDOWN_MARKER_PATH = process.env.SWITCHROOM_GATEWAY_CLEAN_SHUTDOWN_MARKER ?? join70(STATE_DIR, "clean-shutdown.json");
106531
107420
  var GATEWAY_STARTED_AT_MS = Date.now();
106532
107421
  var BOOT_CARD_ENABLED = process.env.SWITCHROOM_BOOT_CARD !== "false";
106533
107422
  var activeBootCard = null;
@@ -106556,7 +107445,7 @@ function ensureIssuesCard(chatId, threadId) {
106556
107445
  bot: botApi,
106557
107446
  log: (msg) => process.stderr.write(`telegram gateway: ${msg}
106558
107447
  `),
106559
- persistPath: join68(stateDir, "issues-card.json")
107448
+ persistPath: join70(stateDir, "issues-card.json")
106560
107449
  });
106561
107450
  activeIssuesWatcher = startIssuesWatcher({
106562
107451
  stateDir,
@@ -106761,13 +107650,13 @@ if (isGatewayMain)
106761
107650
  var inboundSpool;
106762
107651
  if (isGatewayMain)
106763
107652
  inboundSpool = STATIC ? undefined : createInboundSpool({
106764
- path: join68(STATE_DIR, "inbound-spool.jsonl"),
107653
+ path: join70(STATE_DIR, "inbound-spool.jsonl"),
106765
107654
  fs: {
106766
107655
  appendFileSync: (p, d) => appendFileSync9(p, d),
106767
- readFileSync: (p) => readFileSync61(p, "utf8"),
106768
- writeFileSync: (p, d) => writeFileSync50(p, d),
106769
- renameSync: (a, b) => renameSync27(a, b),
106770
- existsSync: (p) => existsSync58(p),
107656
+ readFileSync: (p) => readFileSync62(p, "utf8"),
107657
+ writeFileSync: (p, d) => writeFileSync52(p, d),
107658
+ renameSync: (a, b) => renameSync28(a, b),
107659
+ existsSync: (p) => existsSync60(p),
106771
107660
  statSizeSync: (p) => statSync23(p).size,
106772
107661
  fsyncFileSync: fsyncPathSync,
106773
107662
  fsyncDirSync: fsyncPathSync
@@ -106831,13 +107720,13 @@ async function maybeRedeliverUndeliveredAnswer() {
106831
107720
  let transcriptText;
106832
107721
  try {
106833
107722
  const projectsDir = getProjectsDirForCwd();
106834
- const path3 = join68(projectsDir, `${sessionId}.jsonl`);
106835
- if (!existsSync58(path3)) {
107723
+ const path3 = join70(projectsDir, `${sessionId}.jsonl`);
107724
+ if (!existsSync60(path3)) {
106836
107725
  process.stderr.write(`telegram gateway: crash-redelivery \u2014 transcript not found for turnKey=${turn.turn_key} session=${sessionId} (${path3}); skipping
106837
107726
  `);
106838
107727
  return;
106839
107728
  }
106840
- transcriptText = readFileSync61(path3, "utf8");
107729
+ transcriptText = readFileSync62(path3, "utf8");
106841
107730
  } catch (err) {
106842
107731
  process.stderr.write(`telegram gateway: crash-redelivery \u2014 transcript read failed turnKey=${turn.turn_key}: ${err.message}
106843
107732
  `);
@@ -106907,6 +107796,14 @@ function obligationSweep() {
106907
107796
  if (isGatewayMain && !STATIC && OBLIGATION_LEDGER_ENABLED) {
106908
107797
  setInterval(obligationSweep, OBLIGATION_SWEEP_MS).unref();
106909
107798
  }
107799
+ if (isGatewayMain && HISTORY_ENABLED) {
107800
+ maybeQueueBootBriefing({
107801
+ env: process.env,
107802
+ stateDir: STATE_DIR,
107803
+ resumeMsg: bootResumeInbound?.msg ?? null,
107804
+ put: (agent, msg) => inboundSpool != null ? inboundSpool.put(agent, msg) : pendingInboundBuffer.push(agent, msg)
107805
+ });
107806
+ }
106910
107807
  if (isGatewayMain && bootResumeInbound != null) {
106911
107808
  if (inboundSpool != null) {
106912
107809
  inboundSpool.put(bootResumeInbound.agent, bootResumeInbound.msg);
@@ -106996,8 +107893,8 @@ var bridgeDeadWatchdog = createBridgeDeadWatchdog({
106996
107893
  isSessionAlive: () => findAgentProcessInContainer2()?.comm === "claude",
106997
107894
  isShuttingDown: () => shuttingDown,
106998
107895
  escalate: (reason) => triggerSelfRestart(process.env.SWITCHROOM_AGENT_NAME ?? "", reason, 1500),
106999
- crashLogPath: join68(STATE_DIR, "bridge-crash.log"),
107000
- markerPath: join68(STATE_DIR, "bridge-dead-escalation.json"),
107896
+ crashLogPath: join70(STATE_DIR, "bridge-crash.log"),
107897
+ markerPath: join70(STATE_DIR, "bridge-dead-escalation.json"),
107001
107898
  log: (line) => process.stderr.write(`${line}
107002
107899
  `),
107003
107900
  priorStreak: bridgeDeadPriorStreak,
@@ -107103,8 +108000,8 @@ if (isGatewayMain)
107103
108000
  probeQuotaViaBroker: (t) => probeQuotaForBootCard(agentSlug, t),
107104
108001
  tmuxSupervisor: process.env.SWITCHROOM_TMUX_SUPERVISOR === "1",
107105
108002
  dockerMode: process.env.SWITCHROOM_RUNTIME === "docker",
107106
- configSnapshotPath: join68(resolvedAgentDirForCard, ".config-snapshot.json"),
107107
- bootCardStatePath: join68(resolvedAgentDirForCard, ".boot-card-msgid.json"),
108003
+ configSnapshotPath: join70(resolvedAgentDirForCard, ".config-snapshot.json"),
108004
+ bootCardStatePath: join70(resolvedAgentDirForCard, ".boot-card-msgid.json"),
107108
108005
  floodStatePath: FLOOD_STATE_PATH,
107109
108006
  ...updateOutcomeLine ? { updateOutcomeLine } : {}
107110
108007
  }, ackMsgId).then((handle) => {
@@ -107753,9 +108650,12 @@ if (isGatewayMain)
107753
108650
  process.stderr.write(`telegram gateway: post_skill_proposal agent=${msg.agentName} chat=${msg.chatId} proposal=${proposal.id} slug=${proposal.skill_slug} new=${proposal.is_new}
107754
108651
  `);
107755
108652
  },
108653
+ onBuzzPublishResult: (_c, m) => getBuzzMirror()?.onPublishResult(m),
107756
108654
  log: (msg) => process.stderr.write(`telegram gateway: ipc \u2014 ${msg}
107757
108655
  `)
107758
108656
  });
108657
+ if (isGatewayMain)
108658
+ maybeBootBuzzMirror((msg) => ipcServer.sendToBuzzPeer(msg));
107759
108659
  if (isGatewayMain)
107760
108660
  (() => {
107761
108661
  try {
@@ -107781,7 +108681,7 @@ if (isGatewayMain)
107781
108681
  const receiverUid = receiverUidRaw ? Number(receiverUidRaw) : NaN;
107782
108682
  if (Number.isInteger(receiverUid))
107783
108683
  allowedUids.push(receiverUid);
107784
- const socketPath = join68(STATE_DIR, "webhook.sock");
108684
+ const socketPath = join70(STATE_DIR, "webhook.sock");
107785
108685
  const webhookInject = (agentName3, inbound) => {
107786
108686
  const msg = inbound;
107787
108687
  const delivered = ipcServer.sendToAgent(agentName3, msg);
@@ -108032,9 +108932,9 @@ function redactOutboundText(text5, site) {
108032
108932
  var VOICE_OUT_DEFAULT_CHUNK_CHARS = 600;
108033
108933
  var VOICE_OUT_HARD_CHUNK_CAP = 4096;
108034
108934
  var voiceOnDemandCache = new VoiceOnDemandCache({
108035
- persistPath: join68(STATE_DIR, "voice-ondemand.json")
108935
+ persistPath: join70(STATE_DIR, "voice-ondemand.json")
108036
108936
  });
108037
- var VOICE_CACHE_DIR = join68(STATE_DIR, "voice-cache");
108937
+ var VOICE_CACHE_DIR = join70(STATE_DIR, "voice-cache");
108038
108938
  var voicePreSynthQueue = new PreSynthQueue({
108039
108939
  runJob: async (job) => {
108040
108940
  const sidecarToken = await materializeSidecarToken2();
@@ -108190,6 +109090,7 @@ function gatewaySendReplyDeps() {
108190
109090
  resolveReplyOwnerTurn,
108191
109091
  findTurnByOriginId,
108192
109092
  findTurnByQuotedMessageId,
109093
+ findLatestTurnForChat,
108193
109094
  resolveAnswerThreadWithLog,
108194
109095
  resolveThreadId,
108195
109096
  getLatestInboundMessageId,
@@ -108441,11 +109342,11 @@ async function executeSendGif(rawArgs) {
108441
109342
  };
108442
109343
  }
108443
109344
  async function publishToTelegraph(text5, shortName, authorName) {
108444
- const accountPath = join68(STATE_DIR, "telegraph-account.json");
109345
+ const accountPath = join70(STATE_DIR, "telegraph-account.json");
108445
109346
  let account = null;
108446
109347
  try {
108447
- if (existsSync58(accountPath)) {
108448
- const raw = readFileSync61(accountPath, "utf-8");
109348
+ if (existsSync60(accountPath)) {
109349
+ const raw = readFileSync62(accountPath, "utf-8");
108449
109350
  const parsed = JSON.parse(raw);
108450
109351
  if (parsed.shortName && parsed.accessToken) {
108451
109352
  account = parsed;
@@ -108465,7 +109366,7 @@ async function publishToTelegraph(text5, shortName, authorName) {
108465
109366
  account = created.value;
108466
109367
  try {
108467
109368
  mkdirSync51(STATE_DIR, { recursive: true, mode: 448 });
108468
- writeFileSync50(accountPath, JSON.stringify(account, null, 2), { mode: 384 });
109369
+ writeFileSync52(accountPath, JSON.stringify(account, null, 2), { mode: 384 });
108469
109370
  } catch (err) {
108470
109371
  process.stderr.write(`telegram gateway: telegraph cache write failed: ${err.message}
108471
109372
  `);
@@ -108581,7 +109482,7 @@ _The secret was NOT saved. The agent can re-request with \`request_secret\`._`,
108581
109482
  }
108582
109483
  function readLiveSwitchroomConfigText() {
108583
109484
  const cfgPath = process.env.SWITCHROOM_CONFIG ?? findConfigFile2();
108584
- return readFileSync61(cfgPath, "utf8");
109485
+ return readFileSync62(cfgPath, "utf8");
108585
109486
  }
108586
109487
  async function executeReact(args) {
108587
109488
  if (!args.chat_id)
@@ -108622,7 +109523,7 @@ async function executeDownloadAttachment(args) {
108622
109523
  });
108623
109524
  mkdirSync51(INBOX_DIR, { recursive: true, mode: 448 });
108624
109525
  assertInsideInbox2(INBOX_DIR, dlPath);
108625
- writeFileSync50(dlPath, buf, { mode: 384 });
109526
+ writeFileSync52(dlPath, buf, { mode: 384 });
108626
109527
  return { content: [{ type: "text", text: dlPath }] };
108627
109528
  }
108628
109529
  async function executeEditMessage(args) {
@@ -108661,6 +109562,7 @@ async function executeEditMessage(args) {
108661
109562
  `);
108662
109563
  }
108663
109564
  }
109565
+ getBuzzMirror()?.mirrorCorrection({ telegramMessageKey: `${String(args.chat_id ?? "")}:${Number(args.message_id)}`, scrubbedText: editRawText });
108664
109566
  return { content: [{ type: "text", text: `edited (id: ${id})` }] };
108665
109567
  }
108666
109568
  async function executeSendTyping(args) {
@@ -109996,14 +110898,14 @@ function restartMarkerPath() {
109996
110898
  const agentDir = resolveAgentDirFromEnv();
109997
110899
  if (!agentDir)
109998
110900
  return null;
109999
- return join68(agentDir, "restart-pending.json");
110901
+ return join70(agentDir, "restart-pending.json");
110000
110902
  }
110001
110903
  function writeRestartMarker(marker) {
110002
110904
  const p = restartMarkerPath();
110003
110905
  if (!p)
110004
110906
  return;
110005
110907
  try {
110006
- writeFileSync50(p, JSON.stringify(marker));
110908
+ writeFileSync52(p, JSON.stringify(marker));
110007
110909
  lastPlannedRestartAt = Date.now();
110008
110910
  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}
110009
110911
  `);
@@ -110022,7 +110924,7 @@ function readRestartMarker() {
110022
110924
  if (!p)
110023
110925
  return null;
110024
110926
  try {
110025
- return JSON.parse(readFileSync61(p, "utf8"));
110927
+ return JSON.parse(readFileSync62(p, "utf8"));
110026
110928
  } catch {
110027
110929
  return null;
110028
110930
  }
@@ -110032,7 +110934,7 @@ function clearRestartMarker() {
110032
110934
  if (!p)
110033
110935
  return;
110034
110936
  try {
110035
- rmSync8(p, { force: true });
110937
+ rmSync9(p, { force: true });
110036
110938
  process.stderr.write(`telegram gateway: restart-marker: cleared path=${p}
110037
110939
  `);
110038
110940
  } catch {}
@@ -110171,7 +111073,7 @@ var _dockerReachable;
110171
111073
  function isDockerReachable() {
110172
111074
  if (_dockerReachable !== undefined)
110173
111075
  return _dockerReachable;
110174
- if (!existsSync58("/var/run/docker.sock")) {
111076
+ if (!existsSync60("/var/run/docker.sock")) {
110175
111077
  _dockerReachable = false;
110176
111078
  return _dockerReachable;
110177
111079
  }
@@ -110188,12 +111090,12 @@ function _resetDockerReachableCache() {
110188
111090
  }
110189
111091
  function spawnSwitchroomDetached(args, onFailure) {
110190
111092
  const fullArgs = SWITCHROOM_CONFIG ? ["--config", SWITCHROOM_CONFIG, ...args] : args;
110191
- const logPath = join68(STATE_DIR, "detached-spawn.log");
111093
+ const logPath = join70(STATE_DIR, "detached-spawn.log");
110192
111094
  let outFd = null;
110193
111095
  try {
110194
111096
  mkdirSync51(STATE_DIR, { recursive: true });
110195
111097
  outFd = openSync15(logPath, "a");
110196
- writeFileSync50(logPath, `
111098
+ writeFileSync52(logPath, `
110197
111099
  [${new Date().toISOString()}] spawn ${SWITCHROOM_CLI} ${fullArgs.join(" ")}
110198
111100
  `, { flag: "a" });
110199
111101
  } catch {}
@@ -110219,7 +111121,7 @@ function spawnSwitchroomDetached(args, onFailure) {
110219
111121
  return;
110220
111122
  let tail = "";
110221
111123
  try {
110222
- const full = readFileSync61(logPath, "utf8");
111124
+ const full = readFileSync62(logPath, "utf8");
110223
111125
  tail = full.split(`
110224
111126
  `).slice(-30).join(`
110225
111127
  `).trim();
@@ -110481,10 +111383,10 @@ ${preBlock(formatSwitchroomOutput(detail))}`, { html: true });
110481
111383
  }
110482
111384
  function readRecentDenialsForAgent(agentName3, windowMs, limit) {
110483
111385
  try {
110484
- const auditPath = join68(homedir20(), ".switchroom", "vault-audit.log");
110485
- if (!existsSync58(auditPath))
111386
+ const auditPath = join70(homedir20(), ".switchroom", "vault-audit.log");
111387
+ if (!existsSync60(auditPath))
110486
111388
  return [];
110487
- const raw = readFileSync61(auditPath, "utf8");
111389
+ const raw = readFileSync62(auditPath, "utf8");
110488
111390
  return recentDenialsFromAuditLog(raw, { agentName: agentName3, windowMs, limit });
110489
111391
  } catch {
110490
111392
  return [];
@@ -110535,7 +111437,7 @@ async function buildAgentMetadata(agentName3) {
110535
111437
  try {
110536
111438
  const agentDir = resolveAgentDirFromEnv();
110537
111439
  if (agentDir) {
110538
- const raw = readFileSync61(join68(agentDir, ".claude", ".claude.json"), "utf8");
111440
+ const raw = readFileSync62(join70(agentDir, ".claude", ".claude.json"), "utf8");
110539
111441
  claudeJson = JSON.parse(raw);
110540
111442
  }
110541
111443
  } catch {}
@@ -110664,7 +111566,7 @@ function buildModelDeps(restartCtx) {
110664
111566
  try {
110665
111567
  const agentDir = resolveAgentDirFromEnv();
110666
111568
  if (agentDir) {
110667
- const local = await fetchQuota2({ claudeConfigDir: join68(agentDir, ".claude") });
111569
+ const local = await fetchQuota2({ claudeConfigDir: join70(agentDir, ".claude") });
110668
111570
  if (local.ok)
110669
111571
  return formatQuotaLine2(local.data);
110670
111572
  }
@@ -110911,9 +111813,9 @@ function effortMenuReplyMarkup(reply) {
110911
111813
  function flushAgentHandoff(agentDir) {
110912
111814
  let removed = 0;
110913
111815
  for (const fname of [".handoff.md", ".handoff-topic"]) {
110914
- const p = join68(agentDir, fname);
111816
+ const p = join70(agentDir, fname);
110915
111817
  try {
110916
- if (existsSync58(p)) {
111818
+ if (existsSync60(p)) {
110917
111819
  unlinkSync31(p);
110918
111820
  removed++;
110919
111821
  }
@@ -110969,7 +111871,7 @@ async function handleNewCommand(ctx) {
110969
111871
  writeRestartMarker({ chat_id: chatId, thread_id: threadId ?? null, ack_message_id: ackId, ts: Date.now() });
110970
111872
  if (agentDir != null) {
110971
111873
  try {
110972
- writeFileSync50(join68(agentDir, ".force-fresh-session"), `${kind} at ${new Date().toISOString()}
111874
+ writeFileSync52(join70(agentDir, ".force-fresh-session"), `${kind} at ${new Date().toISOString()}
110973
111875
  `, "utf8");
110974
111876
  } catch (err) {
110975
111877
  process.stderr.write(`telegram gateway: failed to write force-fresh marker: ${err}
@@ -111082,16 +111984,16 @@ function buildFolderPickerDeps() {
111082
111984
  };
111083
111985
  }
111084
111986
  var lockoutOps = {
111085
- readFileSync: (p, enc) => readFileSync61(p, enc),
111086
- writeFileSync: (p, data, opts) => writeFileSync50(p, data, opts),
111087
- existsSync: (p) => existsSync58(p),
111987
+ readFileSync: (p, enc) => readFileSync62(p, enc),
111988
+ writeFileSync: (p, data, opts) => writeFileSync52(p, data, opts),
111989
+ existsSync: (p) => existsSync60(p),
111088
111990
  mkdirSync: (p, opts) => mkdirSync51(p, opts),
111089
- joinPath: (...parts) => join68(...parts)
111991
+ joinPath: (...parts) => join70(...parts)
111090
111992
  };
111091
111993
  var FLEET_FALLBACK_DEDUP_MS = 30000;
111092
111994
  function isAuthBrokerSocketReachable() {
111093
111995
  try {
111094
- return existsSync58(resolveAuthBrokerSocketPath2());
111996
+ return existsSync60(resolveAuthBrokerSocketPath2());
111095
111997
  } catch {
111096
111998
  return false;
111097
111999
  }
@@ -111346,7 +112248,7 @@ async function runCreditWatch() {
111346
112248
  if (!agentDir)
111347
112249
  return;
111348
112250
  const agentName3 = getMyAgentName();
111349
- const claudeConfigDir = join68(agentDir, ".claude");
112251
+ const claudeConfigDir = join70(agentDir, ".claude");
111350
112252
  const stateDir = STATE_DIR;
111351
112253
  const reason = readClaudeJsonOverage(claudeConfigDir);
111352
112254
  const prev = loadCreditState(stateDir);
@@ -113102,7 +114004,7 @@ Send \`/auth ${parsed.provider} cancel\` to abort.`, { html: true });
113102
114004
  await switchroomReply(ctx, "**/usage:** cannot resolve agent dir.", { html: true });
113103
114005
  return;
113104
114006
  }
113105
- const result = await fetchQuota2({ claudeConfigDir: join68(agentDir, ".claude") });
114007
+ const result = await fetchQuota2({ claudeConfigDir: join70(agentDir, ".claude") });
113106
114008
  if (!result.ok) {
113107
114009
  await switchroomReply(ctx, `**/usage:** ${escapeHtmlForTg2(result.reason)}`, { html: true });
113108
114010
  return;
@@ -113489,7 +114391,7 @@ ${interimLabel}` : interimLabel
113489
114391
  const unifiedDiff = (() => {
113490
114392
  try {
113491
114393
  const cfgPath = process.env.SWITCHROOM_CONFIG ?? SWITCHROOM_CONFIG ?? findConfigFile2();
113492
- const raw = readFileSync61(cfgPath, "utf8");
114394
+ const raw = readFileSync62(cfgPath, "utf8");
113493
114395
  return synthesizeAllowRuleDiff({ agentName: agentName3, rule: chosen.rule, configText: raw });
113494
114396
  } catch (err) {
113495
114397
  process.stderr.write(`telegram gateway: always-allow diff synth failed: ${err.message}
@@ -114358,7 +115260,7 @@ async function startGateway() {
114358
115260
  return;
114359
115261
  }
114360
115262
  })();
114361
- const resolvedAgentDirForBootCard = agentDir ?? join68(homedir20(), ".switchroom", "agents", agentSlug);
115263
+ const resolvedAgentDirForBootCard = agentDir ?? join70(homedir20(), ".switchroom", "agents", agentSlug);
114362
115264
  const handle = await startBootCard(chatId, threadId, botApiForCard, {
114363
115265
  agentName: agentDisplayName,
114364
115266
  agentSlug,
@@ -114372,8 +115274,8 @@ async function startGateway() {
114372
115274
  probeQuotaViaBroker: (t) => probeQuotaForBootCard(agentSlug, t),
114373
115275
  tmuxSupervisor: process.env.SWITCHROOM_TMUX_SUPERVISOR === "1",
114374
115276
  dockerMode: process.env.SWITCHROOM_RUNTIME === "docker",
114375
- configSnapshotPath: join68(resolvedAgentDirForBootCard, ".config-snapshot.json"),
114376
- bootCardStatePath: join68(resolvedAgentDirForBootCard, ".boot-card-msgid.json"),
115277
+ configSnapshotPath: join70(resolvedAgentDirForBootCard, ".config-snapshot.json"),
115278
+ bootCardStatePath: join70(resolvedAgentDirForBootCard, ".boot-card-msgid.json"),
114377
115279
  floodStatePath: FLOOD_STATE_PATH,
114378
115280
  ...updateOutcomeLine ? { updateOutcomeLine } : {}
114379
115281
  }, ackMsgId);
@@ -114406,7 +115308,7 @@ async function startGateway() {
114406
115308
  if (smAgentDir) {
114407
115309
  const resolutionTimeoutMs = resolveSessionModelResolutionTimeoutMs(process.env.SWITCHROOM_SESSION_MODEL_RESOLUTION_TIMEOUT_MS);
114408
115310
  const resolved = await waitForSessionModelResolution({
114409
- barrierExists: () => existsSync58(join68(smAgentDir, ".session-model-resolved")),
115311
+ barrierExists: () => existsSync60(join70(smAgentDir, ".session-model-resolved")),
114410
115312
  timeoutMs: resolutionTimeoutMs
114411
115313
  });
114412
115314
  if (!resolved) {
@@ -114414,10 +115316,10 @@ async function startGateway() {
114414
115316
  process.stderr.write(`telegram gateway: gw /model relaunch UNRESOLVED agent=${getMyAgentName()} target=${target} (barrier timeout after ${resolutionTimeoutMs}ms)
114415
115317
  `);
114416
115318
  } else {
114417
- const activePath = join68(smAgentDir, ".active-session-model");
114418
- if (existsSync58(activePath)) {
115319
+ const activePath = join70(smAgentDir, ".active-session-model");
115320
+ if (existsSync60(activePath)) {
114419
115321
  try {
114420
- const launched = readFileSync61(activePath, "utf8").trim();
115322
+ const launched = readFileSync62(activePath, "utf8").trim();
114421
115323
  const configured = (() => {
114422
115324
  const d = switchroomExecJson(["agent", "list"]);
114423
115325
  const raw = d?.agents?.find((a) => a.name === getMyAgentName())?.model ?? null;
@@ -114450,24 +115352,24 @@ async function startGateway() {
114450
115352
  deliverModelSwitchBootNotice({
114451
115353
  ...modelBootCardDeps,
114452
115354
  confirmation,
114453
- hasSessionModelAlert: existsSync58(join68(smAgentDir, ".session-model-alert"))
115355
+ hasSessionModelAlert: existsSync60(join70(smAgentDir, ".session-model-alert"))
114454
115356
  });
114455
115357
  }
114456
115358
  } catch {}
114457
115359
  }
114458
- const activeEffortPath = join68(smAgentDir, ".active-session-effort");
114459
- if (existsSync58(activeEffortPath)) {
115360
+ const activeEffortPath = join70(smAgentDir, ".active-session-effort");
115361
+ if (existsSync60(activeEffortPath)) {
114460
115362
  try {
114461
- const launchedEffort = readFileSync61(activeEffortPath, "utf8").trim();
115363
+ const launchedEffort = readFileSync62(activeEffortPath, "utf8").trim();
114462
115364
  const configuredEffort = getConfiguredEffortForPersist();
114463
115365
  sessionEffortOverride = launchedEffort.length > 0 && launchedEffort !== configuredEffort ? launchedEffort : null;
114464
115366
  } catch {}
114465
115367
  }
114466
- const alertPath = join68(smAgentDir, ".session-model-alert");
114467
- if (existsSync58(alertPath)) {
115368
+ const alertPath = join70(smAgentDir, ".session-model-alert");
115369
+ if (existsSync60(alertPath)) {
114468
115370
  let alertText = null;
114469
115371
  try {
114470
- alertText = readFileSync61(alertPath, "utf8").trim();
115372
+ alertText = readFileSync62(alertPath, "utf8").trim();
114471
115373
  } catch {
114472
115374
  alertText = null;
114473
115375
  }