switchroom 0.19.47 → 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 +22 -1
  2. package/dist/auth-broker/index.js +26 -2
  3. package/dist/buzz-gateway/index.js +9207 -0
  4. package/dist/cli/notion-write-pretool.mjs +22 -1
  5. package/dist/cli/switchroom.js +91 -9
  6. package/dist/host-control/main.js +27 -3
  7. package/dist/vault/approvals/kernel-server.js +26 -2
  8. package/dist/vault/broker/server.js +26 -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 +1156 -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();
@@ -21673,6 +21673,8 @@ var init_schema = __esm(() => {
21673
21673
  profile: exports_external.string().optional().describe("Memory profile bank this agent's curated memory defaults key off \u2014 " + "the built-in disposition + observations_mission in PROFILE_MEMORY_DEFAULTS. " + "Decouples the memory profile from `extends` (the filesystem persona " + "profile), so an agent on `extends: default` can opt into the `coding` " + "memory bundle via `memory.profile: coding` without inheriting the coding " + "persona. Resolution: memory.profile \u2192 extends \u2192 DEFAULT_PROFILE (see " + "resolveMemoryProfile). Unset \u21d2 byte-identical to keying off `extends`."),
21674
21674
  bank_mission: exports_external.string().optional().describe("Bank-level mission statement used during recall to contextualize " + "results. NOTE: this is an alias for the Hindsight engine's " + "`reflect_mission` field (verified live: switchroom's bank_mission " + "lands in `config.reflect_mission`). Prefer `reflect_mission` going " + "forward; `bank_mission` is retained for back-compat. If both are " + "set, `reflect_mission` wins. Cascade: override."),
21675
21675
  reflect_mission: exports_external.string().optional().describe("Mission/context steering Hindsight Reflect operations (the bank's " + "'who am I / what matters' framing applied during recall). The " + "engine-accurate name for what `bank_mission` sets. Cascade: override."),
21676
+ reflect_budget: exports_external.enum(["low", "mid", "high"]).optional().describe("Thinking/retrieval budget injected into reflect MCP calls when the " + "caller omits budget. Unset \u21d2 shim default (mid). Explicit per-call " + "budget always wins. Higher = better recall on fuzzy queries, more " + "backend latency/compute. stdio-shim transport only (ignored under " + "memory.config.mcp_transport: http). Cascade: override (per-agent " + "wins over default)."),
21677
+ reflect_max_tokens: exports_external.number().int().positive().max(8192).optional().describe("Token cap injected into reflect MCP calls when caller omits " + "max_tokens. Unset \u21d2 shim default 1024. Values much above ~2048 risk " + "exceeding Claude Code's MCP output cap, silently dropping the " + "payload \u2014 raise deliberately. Explicit per-call values always win. " + "stdio-shim transport only. Cascade: override."),
21676
21678
  retain_mission: exports_external.string().optional().describe("Instructions for the fact extraction LLM during retain. Cascade: override."),
21677
21679
  mental_models: exports_external.array(exports_external.object({
21678
21680
  name: exports_external.string().min(1).describe("Stable model name (identity key for idempotent ensure). Two " + "declarations with the same name in one agent are rejected."),
@@ -21792,6 +21794,7 @@ var init_schema = __esm(() => {
21792
21794
  max_turns_in_briefing: exports_external.number().int().positive().optional().describe("Cap on recent user/assistant turn pairs fed to the summarizer."),
21793
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."),
21794
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."),
21795
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."),
21796
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."),
21797
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.")
@@ -21924,8 +21927,24 @@ var init_schema = __esm(() => {
21924
21927
  }
21925
21928
  return tg;
21926
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();
21927
21945
  ChannelsSchema = exports_external.object({
21928
- telegram: TelegramChannelSchema
21946
+ telegram: TelegramChannelSchema,
21947
+ buzz: BuzzChannelSchema.optional()
21929
21948
  }).optional();
21930
21949
  TIMEZONE_REGEX = /^UTC$|^[A-Z][A-Za-z0-9_+-]+(\/[A-Z][A-Za-z0-9_+-]+){1,2}$/;
21931
21950
  ApproverIdSchema = exports_external.union([exports_external.number(), exports_external.string().regex(/^\d+$/)]);
@@ -22164,6 +22183,8 @@ var init_schema = __esm(() => {
22164
22183
  }).optional(),
22165
22184
  bank_mission: exports_external.string().optional(),
22166
22185
  reflect_mission: exports_external.string().optional(),
22186
+ reflect_budget: exports_external.enum(["low", "mid", "high"]).optional(),
22187
+ reflect_max_tokens: exports_external.number().int().positive().max(8192).optional(),
22167
22188
  retain_mission: exports_external.string().optional(),
22168
22189
  observations_mission: exports_external.string().optional(),
22169
22190
  disposition: exports_external.object({
@@ -29754,6 +29775,7 @@ __export(exports_history, {
29754
29775
  hasOutboundDeliveredSince: () => hasOutboundDeliveredSince2,
29755
29776
  getRecentOutboundCount: () => getRecentOutboundCount,
29756
29777
  getLatestInboundMessageId: () => getLatestInboundMessageId2,
29778
+ getHistoryDbForBriefing: () => getHistoryDbForBriefing,
29757
29779
  deliveryTextMatch: () => deliveryTextMatch2,
29758
29780
  deleteFromHistory: () => deleteFromHistory2,
29759
29781
  checkpointWal: () => checkpointWal2,
@@ -29897,6 +29919,9 @@ function verifyHistoryWritable2() {
29897
29919
  } catch {}
29898
29920
  }
29899
29921
  }
29922
+ function getHistoryDbForBriefing() {
29923
+ return db2;
29924
+ }
29900
29925
  function _resetForTests() {
29901
29926
  if (db2 != null) {
29902
29927
  db2.close();
@@ -38217,7 +38242,7 @@ __export(exports_tmux2, {
38217
38242
  captureAgentPane: () => captureAgentPane2
38218
38243
  });
38219
38244
  import { execFileSync as execFileSync8 } from "node:child_process";
38220
- 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";
38221
38246
  import { resolve as resolve12 } from "node:path";
38222
38247
  function captureAgentPane2(opts) {
38223
38248
  const { agentName: agentName3, agentDir, reason } = opts;
@@ -38268,7 +38293,7 @@ function captureAgentPane2(opts) {
38268
38293
  ` + `
38269
38294
  `;
38270
38295
  try {
38271
- writeFileSync49(outPath, Buffer.concat([Buffer.from(header, "utf8"), body]), {
38296
+ writeFileSync51(outPath, Buffer.concat([Buffer.from(header, "utf8"), body]), {
38272
38297
  mode: 384
38273
38298
  });
38274
38299
  } catch (err) {
@@ -39152,7 +39177,7 @@ __export(exports_materialize_bot_token, {
39152
39177
  materializeBotToken: () => materializeBotToken,
39153
39178
  BotTokenMaterializeError: () => BotTokenMaterializeError
39154
39179
  });
39155
- import { existsSync as existsSync57 } from "node:fs";
39180
+ import { existsSync as existsSync59 } from "node:fs";
39156
39181
  function pickConfiguredToken(config, agentName3) {
39157
39182
  if (agentName3) {
39158
39183
  const agent = config.agents?.[agentName3];
@@ -39166,7 +39191,7 @@ function tryDirectVaultRead4(ref, config, passphrase) {
39166
39191
  if (!passphrase)
39167
39192
  return null;
39168
39193
  const vaultPath = resolvePath(config.vault?.path ?? "~/.switchroom/vault.enc");
39169
- if (!existsSync57(vaultPath))
39194
+ if (!existsSync59(vaultPath))
39170
39195
  return null;
39171
39196
  try {
39172
39197
  const secrets = openVault(passphrase, vaultPath);
@@ -39321,18 +39346,18 @@ var import_runner3 = __toESM(require_mod3(), 1);
39321
39346
  import { randomBytes as randomBytes13, createHash as createHash8 } from "crypto";
39322
39347
  import { execFileSync as execFileSync9, execSync as execSync2, spawn as spawn2 } from "child_process";
39323
39348
  import {
39324
- readFileSync as readFileSync61,
39325
- writeFileSync as writeFileSync50,
39349
+ readFileSync as readFileSync62,
39350
+ writeFileSync as writeFileSync52,
39326
39351
  mkdirSync as mkdirSync51,
39327
39352
  readdirSync as readdirSync16,
39328
- rmSync as rmSync8,
39353
+ rmSync as rmSync9,
39329
39354
  statSync as statSync23,
39330
- renameSync as renameSync27,
39355
+ renameSync as renameSync28,
39331
39356
  realpathSync as realpathSync5,
39332
39357
  chmodSync as chmodSync14,
39333
39358
  openSync as openSync15,
39334
39359
  closeSync as closeSync15,
39335
- existsSync as existsSync58,
39360
+ existsSync as existsSync60,
39336
39361
  unlinkSync as unlinkSync31,
39337
39362
  appendFileSync as appendFileSync9
39338
39363
  } from "fs";
@@ -39388,7 +39413,7 @@ function fsyncPathSync(path) {
39388
39413
 
39389
39414
  // gateway/gateway.ts
39390
39415
  import { homedir as homedir20 } from "os";
39391
- 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";
39392
39417
 
39393
39418
  // plugin-logger.ts
39394
39419
  import { appendFileSync, mkdirSync, renameSync as renameSync2, statSync, existsSync } from "fs";
@@ -42275,6 +42300,225 @@ function resolveReplyOwnerTurnWith(lookups, liveTurn, chatId, args) {
42275
42300
  return { turn: winnerId != null ? byId.get(winnerId) ?? null : null, tier, candidates };
42276
42301
  }
42277
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
+
42278
42522
  // gateway/subagent-reply-authority.ts
42279
42523
  var SUB_AGENT_KIND_PREFIX = "sub_agent_";
42280
42524
 
@@ -70706,7 +70950,7 @@ class PostHog extends PostHogBackendClient {
70706
70950
  // analytics-posthog.ts
70707
70951
  import { existsSync as existsSync13, mkdirSync as mkdirSync17, readFileSync as readFileSync16, writeFileSync as writeFileSync14 } from "node:fs";
70708
70952
  import { dirname as dirname11, join as join21 } from "node:path";
70709
- import { randomUUID as randomUUID2 } from "node:crypto";
70953
+ import { randomUUID as randomUUID3 } from "node:crypto";
70710
70954
  var DEFAULT_KEY = "phc_qKY87cKWZm6ZyCtk7LcRd2cU8Sg42u7Ywhui5stYCegd";
70711
70955
  var DEFAULT_HOST = "https://us.i.posthog.com";
70712
70956
  var client = null;
@@ -70741,7 +70985,7 @@ function getDistinctId() {
70741
70985
  }
70742
70986
  }
70743
70987
  } catch {}
70744
- const id = randomUUID2();
70988
+ const id = randomUUID3();
70745
70989
  cachedDistinctId = id;
70746
70990
  try {
70747
70991
  mkdirSync17(dirname11(fallbackPath), { recursive: true });
@@ -70822,7 +71066,7 @@ import { dirname as dirname13, join as join23 } from "node:path";
70822
71066
  // analytics-posthog.ts
70823
71067
  import { existsSync as existsSync14, mkdirSync as mkdirSync18, readFileSync as readFileSync17, writeFileSync as writeFileSync15 } from "node:fs";
70824
71068
  import { dirname as dirname12, join as join22 } from "node:path";
70825
- import { randomUUID as randomUUID3 } from "node:crypto";
71069
+ import { randomUUID as randomUUID4 } from "node:crypto";
70826
71070
  var DEFAULT_KEY2 = "phc_qKY87cKWZm6ZyCtk7LcRd2cU8Sg42u7Ywhui5stYCegd";
70827
71071
  var DEFAULT_HOST2 = "https://us.i.posthog.com";
70828
71072
  var client2 = null;
@@ -70856,7 +71100,7 @@ function getDistinctId2() {
70856
71100
  }
70857
71101
  }
70858
71102
  } catch {}
70859
- const id = randomUUID3();
71103
+ const id = randomUUID4();
70860
71104
  cachedDistinctId2 = id;
70861
71105
  try {
70862
71106
  mkdirSync18(dirname12(fallbackPath), { recursive: true });
@@ -71909,6 +72153,8 @@ function detectModelUnavailable(stderr) {
71909
72153
  "socket hang up",
71910
72154
  "request timed out",
71911
72155
  "connection refused",
72156
+ "connection closed",
72157
+ "mid-response",
71912
72158
  "getaddrinfo"
71913
72159
  ];
71914
72160
  if (networkSignals.some((s) => lower.includes(s))) {
@@ -72139,6 +72385,7 @@ var OPERATOR_EVENT_KINDS = [
72139
72385
  "mcp-dependency-blocked",
72140
72386
  "quota-exhausted",
72141
72387
  "rate-limited",
72388
+ "transport-transient",
72142
72389
  "agent-crashed",
72143
72390
  "agent-restarted-unexpectedly",
72144
72391
  "unknown-4xx",
@@ -72151,12 +72398,12 @@ function classifyClaudeError(raw) {
72151
72398
  try {
72152
72399
  return classifyInner(raw);
72153
72400
  } catch {
72154
- return "unknown-4xx";
72401
+ return "unknown-5xx";
72155
72402
  }
72156
72403
  }
72157
72404
  function classifyInner(raw) {
72158
72405
  if (raw == null)
72159
- return "unknown-4xx";
72406
+ return "unknown-5xx";
72160
72407
  const obj = typeof raw === "object" ? raw : {};
72161
72408
  const errorType = extractString(obj, "error_type") ?? extractString(obj, "type") ?? extractString(getNestedObj(obj, "error"), "type") ?? "";
72162
72409
  const errorCode = extractString(obj, "code") ?? extractString(getNestedObj(obj, "error"), "code") ?? "";
@@ -72201,13 +72448,16 @@ ${message}`;
72201
72448
  if (errorType === "agent-restarted-unexpectedly" || errorCode === "agent-restarted-unexpectedly") {
72202
72449
  return "agent-restarted-unexpectedly";
72203
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
+ }
72204
72454
  if (status != null) {
72205
72455
  if (status >= 400 && status < 500)
72206
72456
  return "unknown-4xx";
72207
72457
  if (status >= 500 && status < 600)
72208
72458
  return "unknown-5xx";
72209
72459
  }
72210
- return "unknown-4xx";
72460
+ return "unknown-5xx";
72211
72461
  }
72212
72462
  function extractString(obj, key) {
72213
72463
  const v = obj[key];
@@ -73593,7 +73843,7 @@ function defaultAddAccount(label, credentials, opts) {
73593
73843
  init_protocol();
73594
73844
  import * as net3 from "node:net";
73595
73845
  import { homedir as homedir7 } from "node:os";
73596
- import { randomUUID as randomUUID4 } from "node:crypto";
73846
+ import { randomUUID as randomUUID5 } from "node:crypto";
73597
73847
  import { join as join28 } from "node:path";
73598
73848
  var DEFAULT_TIMEOUT_MS3 = 5000;
73599
73849
  function reviveDate2(v) {
@@ -73667,7 +73917,7 @@ class AuthBrokerClient2 {
73667
73917
  async getCredentials(provider, account) {
73668
73918
  const base = {
73669
73919
  v: PROTOCOL_VERSION,
73670
- id: randomUUID4(),
73920
+ id: randomUUID5(),
73671
73921
  op: "get-credentials"
73672
73922
  };
73673
73923
  let req = base;
@@ -73681,7 +73931,7 @@ class AuthBrokerClient2 {
73681
73931
  async listState() {
73682
73932
  const data = await this.send({
73683
73933
  v: PROTOCOL_VERSION,
73684
- id: randomUUID4(),
73934
+ id: randomUUID5(),
73685
73935
  op: "list-state"
73686
73936
  });
73687
73937
  return data;
@@ -73689,7 +73939,7 @@ class AuthBrokerClient2 {
73689
73939
  async listGoogleAccounts() {
73690
73940
  const data = await this.send({
73691
73941
  v: PROTOCOL_VERSION,
73692
- id: randomUUID4(),
73942
+ id: randomUUID5(),
73693
73943
  op: "list-google-accounts"
73694
73944
  });
73695
73945
  return data;
@@ -73697,7 +73947,7 @@ class AuthBrokerClient2 {
73697
73947
  async listMicrosoftAccounts() {
73698
73948
  const data = await this.send({
73699
73949
  v: PROTOCOL_VERSION,
73700
- id: randomUUID4(),
73950
+ id: randomUUID5(),
73701
73951
  op: "list-microsoft-accounts"
73702
73952
  });
73703
73953
  return data;
@@ -73705,7 +73955,7 @@ class AuthBrokerClient2 {
73705
73955
  async probeQuota(accounts, timeoutMs, forceLive) {
73706
73956
  const data = await this.send({
73707
73957
  v: PROTOCOL_VERSION,
73708
- id: randomUUID4(),
73958
+ id: randomUUID5(),
73709
73959
  op: "probe-quota",
73710
73960
  accounts: [...accounts],
73711
73961
  ...timeoutMs !== undefined ? { timeoutMs } : {},
@@ -73723,7 +73973,7 @@ class AuthBrokerClient2 {
73723
73973
  async getExternalSpend(forceLive) {
73724
73974
  const data = await this.send({
73725
73975
  v: PROTOCOL_VERSION,
73726
- id: randomUUID4(),
73976
+ id: randomUUID5(),
73727
73977
  op: "get-external-spend",
73728
73978
  ...forceLive ? { forceLive: true } : {}
73729
73979
  });
@@ -73732,21 +73982,21 @@ class AuthBrokerClient2 {
73732
73982
  async setActive(account) {
73733
73983
  const data = await this.send({
73734
73984
  v: PROTOCOL_VERSION,
73735
- id: randomUUID4(),
73985
+ id: randomUUID5(),
73736
73986
  op: "set-active",
73737
73987
  account
73738
73988
  });
73739
73989
  return data;
73740
73990
  }
73741
73991
  async markExhausted(until) {
73742
- 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" };
73743
73993
  const data = await this.send(req);
73744
73994
  return data;
73745
73995
  }
73746
73996
  async markThrottled(until) {
73747
73997
  const data = await this.send({
73748
73998
  v: PROTOCOL_VERSION,
73749
- id: randomUUID4(),
73999
+ id: randomUUID5(),
73750
74000
  op: "mark-throttled",
73751
74001
  until
73752
74002
  });
@@ -73755,7 +74005,7 @@ class AuthBrokerClient2 {
73755
74005
  async claimNotification(key, windowMs) {
73756
74006
  const data = await this.send({
73757
74007
  v: PROTOCOL_VERSION,
73758
- id: randomUUID4(),
74008
+ id: randomUUID5(),
73759
74009
  op: "claim-notification",
73760
74010
  key,
73761
74011
  windowMs
@@ -73765,7 +74015,7 @@ class AuthBrokerClient2 {
73765
74015
  async refreshAccount(account) {
73766
74016
  const data = await this.send({
73767
74017
  v: PROTOCOL_VERSION,
73768
- id: randomUUID4(),
74018
+ id: randomUUID5(),
73769
74019
  op: "refresh-account",
73770
74020
  account
73771
74021
  });
@@ -73774,7 +74024,7 @@ class AuthBrokerClient2 {
73774
74024
  async addAccount(label, credentials, replace2, provider) {
73775
74025
  const base = {
73776
74026
  v: PROTOCOL_VERSION,
73777
- id: randomUUID4(),
74027
+ id: randomUUID5(),
73778
74028
  op: "add-account",
73779
74029
  label,
73780
74030
  credentials
@@ -73787,7 +74037,7 @@ class AuthBrokerClient2 {
73787
74037
  async rmAccount(label, provider) {
73788
74038
  const base = {
73789
74039
  v: PROTOCOL_VERSION,
73790
- id: randomUUID4(),
74040
+ id: randomUUID5(),
73791
74041
  op: "rm-account",
73792
74042
  label
73793
74043
  };
@@ -73798,7 +74048,7 @@ class AuthBrokerClient2 {
73798
74048
  async setOverride(agent, account) {
73799
74049
  const data = await this.send({
73800
74050
  v: PROTOCOL_VERSION,
73801
- id: randomUUID4(),
74051
+ id: randomUUID5(),
73802
74052
  op: "set-override",
73803
74053
  agent,
73804
74054
  account
@@ -75357,10 +75607,21 @@ function renderOperatorEvent(ev) {
75357
75607
  `),
75358
75608
  keyboard: {
75359
75609
  inline_keyboard: [
75360
- [
75361
- { text: "\uD83D\uDD10 Reauth", callback_data: `op:reauth:${encodeURIComponent(ev.agent)}` },
75362
- { text: "\u274c Dismiss", callback_data: `op:dismiss:${encodeURIComponent(ev.agent)}` }
75363
- ]
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)}` }]
75364
75625
  ]
75365
75626
  }
75366
75627
  };
@@ -75758,6 +76019,69 @@ function recordOperatorEvent(event, now = Date.now()) {
75758
76019
  store2.set(event.agent, { event, storedAt: now });
75759
76020
  }
75760
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
+
75761
76085
  // throttle-tier.ts
75762
76086
  init_card_format();
75763
76087
  init_quota_check();
@@ -75902,9 +76226,6 @@ function classifyKindAndSource(text4) {
75902
76226
  if (claudeKind === "rate-limited") {
75903
76227
  return { kind: "rate_limit", source: "anthropic" };
75904
76228
  }
75905
- if (claudeKind === "unknown-5xx") {
75906
- return { kind: "overload_529", source: "anthropic" };
75907
- }
75908
76229
  return { kind: "unknown", source: "anthropic" };
75909
76230
  }
75910
76231
  function providerById(id) {
@@ -76177,6 +76498,8 @@ function detectModelUnavailable2(stderr) {
76177
76498
  "socket hang up",
76178
76499
  "request timed out",
76179
76500
  "connection refused",
76501
+ "connection closed",
76502
+ "mid-response",
76180
76503
  "getaddrinfo"
76181
76504
  ];
76182
76505
  if (networkSignals.some((s) => lower.includes(s))) {
@@ -78294,6 +78617,146 @@ function resolveChatIdFallback(rawChatId, access, turnSessionChatId, lastKnownCh
78294
78617
  }
78295
78618
  return { chatId: rawChatId, tier: "raw" };
78296
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
+ }
78297
78760
  // over-ping-safety-net.ts
78298
78761
  function decideOverPing(input) {
78299
78762
  const substantive = input.substantive === true;
@@ -78389,7 +78852,9 @@ var INBOUND_SOURCE_CLASSIFICATION = {
78389
78852
  mental_model_proposal_denied: { decoupledCompletion: false },
78390
78853
  mental_model_proposal_failed: { decoupledCompletion: false },
78391
78854
  webhook: { decoupledCompletion: false },
78392
- linear: { decoupledCompletion: false }
78855
+ linear: { decoupledCompletion: false },
78856
+ buzz: { decoupledCompletion: false },
78857
+ boot_briefing: { decoupledCompletion: false }
78393
78858
  };
78394
78859
  function stampsHandbackMarker(source) {
78395
78860
  if (source == null)
@@ -78999,6 +79464,7 @@ async function sendReply(deps, req) {
78999
79464
  resolveReplyOwnerTurn,
79000
79465
  findTurnByOriginId,
79001
79466
  findTurnByQuotedMessageId,
79467
+ findLatestTurnForChat,
79002
79468
  resolveAnswerThreadWithLog,
79003
79469
  resolveThreadId,
79004
79470
  getLatestInboundMessageId: getLatestInboundMessageId2,
@@ -79732,6 +80198,23 @@ ${url}`;
79732
80198
  if (shouldJournalReplySiteDelivery({ text: rawText, disableNotification: modelDisableNotification })) {
79733
80199
  journalExternalDelivery({ turnNonce: t?.turnId ?? null, text: text4, tgMessageId: sentIds[sentIds.length - 1], replyAlreadyDeliveredThisTurn: true });
79734
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
+ }
79735
80218
  }
79736
80219
  return { content: [{ type: "text", text: result }] };
79737
80220
  }
@@ -81123,6 +81606,8 @@ var QUEUED_CARD_ENABLED = process.env.SWITCHROOM_QUEUED_CARD !== "0";
81123
81606
  var QUEUED_CARD_HTML = "\u23f3 Queued \u2014 waiting for the current task to finish\u2026";
81124
81607
  var QUEUED_CARD_FOLDED_HTML = "\u2705 Folded into the current task.";
81125
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();
81126
81611
  var parkedTurnStarts = [];
81127
81612
  function pruneParkedTurnStarts(now, onDrop) {
81128
81613
  for (let i = parkedTurnStarts.length - 1;i >= 0; i--) {
@@ -81291,6 +81776,7 @@ function beginTurn(deps, ev) {
81291
81776
  startedAt,
81292
81777
  gatewayReceiveAt: startedAt,
81293
81778
  role: deriveTurnRole(ev.rawContent),
81779
+ ...BUZZ_ORIGIN_STAMP_ACTIVE ? parseChannelOrigin(ev.rawContent) : { originChannel: "telegram" },
81294
81780
  ...consumedCrossTurnGate != null ? { crossTurnGate: consumedCrossTurnGate } : {},
81295
81781
  replyCalled: false,
81296
81782
  finalAnswerDelivered: false,
@@ -86064,6 +86550,9 @@ for (const name of SHARED_FRAGMENTS) {
86064
86550
  }
86065
86551
  }
86066
86552
 
86553
+ // ../src/memory/scaffold-integration.ts
86554
+ init_merge();
86555
+
86067
86556
  // ../src/litellm/timeout-budget.ts
86068
86557
  var LITELLM_ROUTER_MARGIN_S = 10;
86069
86558
  var LITELLM_TIMEOUT_TIERS = {
@@ -90522,6 +91011,20 @@ function validateClientMessage(msg) {
90522
91011
  return false;
90523
91012
  return true;
90524
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
+ }
90525
91028
  default:
90526
91029
  return false;
90527
91030
  }
@@ -90550,9 +91053,26 @@ function createIpcServer(options) {
90550
91053
  onRequestConfigFinalize,
90551
91054
  onRolloutStatusPost,
90552
91055
  onRolloutStatusEdit,
91056
+ onBuzzPublishResult,
90553
91057
  log = () => {},
90554
91058
  heartbeatTimeoutMs = 30000
90555
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
+ };
90556
91076
  try {
90557
91077
  renameSync17(socketPath, socketPath + ".bak");
90558
91078
  } catch {}
@@ -90571,6 +91091,8 @@ function createIpcServer(options) {
90571
91091
  if (client3.topicId != null && topicIndex.get(client3.topicId) === client3) {
90572
91092
  topicIndex.delete(client3.topicId);
90573
91093
  }
91094
+ if (buzzPeerClient === client3)
91095
+ buzzPeerClient = null;
90574
91096
  loggedLegacyUpdatePlaceholder.delete(client3.id);
90575
91097
  onClientDisconnected(client3);
90576
91098
  log(`client disconnected: ${client3.id} (agent=${client3.agentName})`);
@@ -90626,10 +91148,19 @@ function createIpcServer(options) {
90626
91148
  if (onPtyPartial)
90627
91149
  onPtyPartial(client3, msg);
90628
91150
  break;
90629
- 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
+ }
90630
91160
  if (onInjectInbound)
90631
- onInjectInbound(client3, msg);
91161
+ onInjectInbound(client3, injectMsg);
90632
91162
  break;
91163
+ }
90633
91164
  case "send_outbound":
90634
91165
  if (onSendOutbound)
90635
91166
  onSendOutbound(client3, msg);
@@ -90773,6 +91304,17 @@ function createIpcServer(options) {
90773
91304
  }
90774
91305
  }
90775
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;
90776
91318
  case "update_placeholder":
90777
91319
  if (!loggedLegacyUpdatePlaceholder.has(client3.id)) {
90778
91320
  loggedLegacyUpdatePlaceholder.add(client3.id);
@@ -90783,7 +91325,39 @@ function createIpcServer(options) {
90783
91325
  log(`unknown IPC message type from client ${client3.id}: ${msg.type}`);
90784
91326
  }
90785
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
+ }
90786
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
+ }
90787
91361
  if (msg.agentName === "default") {
90788
91362
  log(`rejecting register: agentName="default" \u2014 anonymous bridges are not allowed (close+drop client=${client3.id})`);
90789
91363
  try {
@@ -90823,6 +91397,7 @@ function createIpcServer(options) {
90823
91397
  id;
90824
91398
  agentName = null;
90825
91399
  topicId = null;
91400
+ isBuzzPeer = false;
90826
91401
  lastHeartbeat = Date.now();
90827
91402
  _socket;
90828
91403
  _closed = false;
@@ -90932,6 +91507,12 @@ function createIpcServer(options) {
90932
91507
  clientCount() {
90933
91508
  return clients.size;
90934
91509
  },
91510
+ sendToBuzzPeer(msg) {
91511
+ if (!buzzPeerClient || !buzzPeerClient.isAlive())
91512
+ return false;
91513
+ buzzPeerClient.send(msg);
91514
+ return true;
91515
+ },
90935
91516
  async close() {
90936
91517
  if (watchdogTimer !== null) {
90937
91518
  clearInterval(watchdogTimer);
@@ -92393,6 +92974,9 @@ function spoolId(msg) {
92393
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) {
92394
92975
  return `s:resume:${msg.meta.resume_turn_key}`;
92395
92976
  }
92977
+ if (msg.meta?.source === "boot_briefing") {
92978
+ return `s:boot-briefing:${msg.chatId}`;
92979
+ }
92396
92980
  if (msg.meta?.source === "cron" && typeof msg.meta?.replay_fire_ms === "string" && msg.meta.replay_fire_ms.length > 0) {
92397
92981
  const idx = typeof msg.meta?.schedule_index === "string" && msg.meta.schedule_index.length > 0 ? msg.meta.schedule_index : "-";
92398
92982
  return `s:cron-replay:${msg.chatId}:${idx}:${msg.meta.replay_fire_ms}`;
@@ -92565,8 +93149,17 @@ function createInboundSpool(opts) {
92565
93149
  return {
92566
93150
  put(agent, msg) {
92567
93151
  const id = spoolId(msg);
92568
- 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
+ }
92569
93161
  return false;
93162
+ }
92570
93163
  const firstAt = now();
92571
93164
  live.set(id, { agent, msg, firstAt });
92572
93165
  appendRecord({ t: "put", id, agent, msg, firstAt });
@@ -95283,7 +95876,7 @@ import {
95283
95876
  writeSync as writeSync6
95284
95877
  } from "node:fs";
95285
95878
  import { join as join50 } from "node:path";
95286
- import { randomUUID as randomUUID5 } from "node:crypto";
95879
+ import { randomUUID as randomUUID7 } from "node:crypto";
95287
95880
  var PROPOSALS_FILE2 = "skill-proposals.jsonl";
95288
95881
  var REJECTED_FILE2 = "skill-proposals-rejected.jsonl";
95289
95882
  var REJECTION_TTL_MS2 = 90 * 24 * 60 * 60 * 1000;
@@ -95386,7 +95979,7 @@ function enqueueProposal(stateDir, input, opts = {}) {
95386
95979
  const now = opts.now ?? Date.now;
95387
95980
  ensureDir3(stateDir);
95388
95981
  const proposal = {
95389
- id: randomUUID5(),
95982
+ id: randomUUID7(),
95390
95983
  created_at: new Date(now()).toISOString(),
95391
95984
  status: "pending",
95392
95985
  ...input
@@ -96775,7 +97368,7 @@ ${errStr}`) ? "rate-limited" : "quota-exhausted" : classifyClaudeError({ type: e
96775
97368
  const raw = embeddedError ?? obj;
96776
97369
  const kind = classifyClaudeError(embeddedError ?? obj);
96777
97370
  const detail = extractDetailMessage(embeddedError) ?? extractDetailMessage(obj) ?? String(type ?? "");
96778
- const transient = kind === "rate-limited";
97371
+ const transient = kind === "rate-limited" || kind === "transport-transient";
96779
97372
  const retry = extractRetryState(obj);
96780
97373
  const terminal = !transient ? true : retry.retryAttempt != null && retry.maxRetries != null ? retry.retryAttempt >= retry.maxRetries : isErrorLine;
96781
97374
  return { kind, raw, detail, transient, terminal };
@@ -100653,7 +101246,7 @@ function startGatewayHeartbeat(stateDir, intervalMs = GATEWAY_HEARTBEAT_INTERVAL
100653
101246
  }
100654
101247
 
100655
101248
  // gateway/boot-beacon.ts
100656
- import { randomUUID as randomUUID6 } from "node:crypto";
101249
+ import { randomUUID as randomUUID8 } from "node:crypto";
100657
101250
  import {
100658
101251
  closeSync as closeSync14,
100659
101252
  fsyncSync as fsyncSync5,
@@ -100787,7 +101380,7 @@ function writeBootBeaconFile(stateDir, beacon) {
100787
101380
  return false;
100788
101381
  }
100789
101382
  }
100790
- var GATEWAY_BOOT_ID = randomUUID6();
101383
+ var GATEWAY_BOOT_ID = randomUUID8();
100791
101384
  function tickBootBeacon(stateDir, bootId = GATEWAY_BOOT_ID) {
100792
101385
  try {
100793
101386
  return writeBootBeaconFile(stateDir, buildBootBeacon({
@@ -101140,10 +101733,10 @@ function startOutboxSweep(deps) {
101140
101733
  }
101141
101734
 
101142
101735
  // ../src/build-info.ts
101143
- var VERSION2 = "0.19.47";
101144
- var COMMIT_SHA = "9693493e";
101145
- var COMMIT_DATE = "2026-08-02T01:20:29Z";
101146
- var LATEST_PR = 4201;
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;
101147
101740
  var COMMITS_AHEAD_OF_TAG = 0;
101148
101741
 
101149
101742
  // gateway/boot-version.ts
@@ -102526,8 +103119,338 @@ function selectResumeBuilder(endedVia, opts) {
102526
103119
  return kind;
102527
103120
  }
102528
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
+
102529
103452
  // gateway/bridge-dead-watchdog.ts
102530
- 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";
102531
103454
 
102532
103455
  // gateway/cron-session.ts
102533
103456
  var CRON_IDENTITY_SUFFIX2 = "-cron";
@@ -102560,7 +103483,7 @@ function readFreshCrashLogTail(path3, opts = {}) {
102560
103483
  const nowMs3 = opts.nowMs ?? Date.now();
102561
103484
  const freshWindowMs = opts.freshWindowMs ?? CRASH_LOG_FRESH_WINDOW_MS;
102562
103485
  const maxLines = opts.maxLines ?? CRASH_LOG_TAIL_LINES;
102563
- const readFile = opts.readFile ?? ((p) => readFileSync59(p, "utf8"));
103486
+ const readFile = opts.readFile ?? ((p) => readFileSync60(p, "utf8"));
102564
103487
  let raw;
102565
103488
  try {
102566
103489
  raw = readFile(path3);
@@ -102581,13 +103504,13 @@ function readFreshCrashLogTail(path3, opts = {}) {
102581
103504
  }
102582
103505
  function writeBridgeDeadEscalationMarker(path3, marker) {
102583
103506
  const tmp = `${path3}.tmp-${process.pid}-${Date.now()}`;
102584
- writeFileSync48(tmp, JSON.stringify(marker), "utf8");
102585
- renameSync26(tmp, path3);
103507
+ writeFileSync50(tmp, JSON.stringify(marker), "utf8");
103508
+ renameSync27(tmp, path3);
102586
103509
  }
102587
103510
  function consumeBridgeDeadEscalationMarker(path3, nowMs3 = Date.now(), maxAgeMs = ESCALATION_MARKER_MAX_AGE_MS) {
102588
103511
  let marker = null;
102589
103512
  try {
102590
- const parsed = JSON.parse(readFileSync59(path3, "utf8"));
103513
+ const parsed = JSON.parse(readFileSync60(path3, "utf8"));
102591
103514
  if (typeof parsed.ts === "number" && Number.isFinite(parsed.ts) && typeof parsed.reason === "string") {
102592
103515
  const age = nowMs3 - parsed.ts;
102593
103516
  if (age >= 0 && age < maxAgeMs) {
@@ -102740,7 +103663,7 @@ function createBridgeDeadWatchdog(opts) {
102740
103663
  }
102741
103664
 
102742
103665
  // gateway/boot-probes.ts
102743
- 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";
102744
103667
  init_quota_cache();
102745
103668
  init_generation_stamp();
102746
103669
  init_quota_check();
@@ -102749,7 +103672,7 @@ import { promisify as promisify2 } from "util";
102749
103672
  var execFile2 = promisify2(execFileCb2);
102750
103673
  var realProcFs2 = {
102751
103674
  readdir: (p) => readdirSync14(p),
102752
- readFile: (p) => readFileSync60(p, "utf-8")
103675
+ readFile: (p) => readFileSync61(p, "utf-8")
102753
103676
  };
102754
103677
  function findAgentProcessInContainer2(fs4 = realProcFs2) {
102755
103678
  let entries;
@@ -102999,7 +103922,7 @@ if (isGatewayMain) {
102999
103922
  shutdownAnalytics();
103000
103923
  });
103001
103924
  }
103002
- 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");
103003
103926
  var permCardStore = createPermissionCardStore(STATE_DIR);
103004
103927
  var BLOCKED_APPROVALS_DIR = process.env.SWITCHROOM_BLOCKED_APPROVALS_DIR ?? "/state/blocked-approvals";
103005
103928
  var AGENT_NAME = process.env.SWITCHROOM_AGENT_NAME ?? "agent";
@@ -103038,7 +103961,7 @@ function alwaysAllowDrainDeps() {
103038
103961
  return {
103039
103962
  readConfigText: () => {
103040
103963
  const cfgPath = process.env.SWITCHROOM_CONFIG ?? SWITCHROOM_CONFIG ?? findConfigFile2();
103041
- return readFileSync61(cfgPath, "utf8");
103964
+ return readFileSync62(cfgPath, "utf8");
103042
103965
  },
103043
103966
  resolveAllowList: (_configText, agentName3) => {
103044
103967
  const cfg = loadConfig2();
@@ -103099,11 +104022,11 @@ function scheduleAlwaysAllowPersistDrain() {
103099
104022
  }, ALWAYS_ALLOW_DRAIN_INTERVAL_MS);
103100
104023
  timer3.unref?.();
103101
104024
  }
103102
- var ACCESS_FILE = join68(STATE_DIR, "access.json");
103103
- var APPROVED_DIR = join68(STATE_DIR, "approved");
103104
- var ENV_FILE = join68(STATE_DIR, ".env");
103105
- var INBOX_DIR = join68(STATE_DIR, "inbox");
103106
- 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");
103107
104030
  function triggerSelfRestart(targetAgent, reason, delayMs = 300) {
103108
104031
  const isDocker = process.env.SWITCHROOM_RUNTIME === "docker";
103109
104032
  const selfAgent = process.env.SWITCHROOM_AGENT_NAME;
@@ -103168,7 +104091,7 @@ function formatBootVersion() {
103168
104091
  }
103169
104092
  try {
103170
104093
  chmodSync14(ENV_FILE, 384);
103171
- for (const line of readFileSync61(ENV_FILE, "utf8").split(`
104094
+ for (const line of readFileSync62(ENV_FILE, "utf8").split(`
103172
104095
  `)) {
103173
104096
  const m = line.match(/^(\w+)=(.*)$/);
103174
104097
  if (m && process.env[m[1]] === undefined)
@@ -103189,7 +104112,7 @@ var bot;
103189
104112
  var lastGetUpdatesHeartbeatMs = Date.now();
103190
104113
  var GRAMMY_VERSION = (() => {
103191
104114
  try {
103192
- 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");
103193
104116
  return JSON.parse(raw).version ?? "unknown";
103194
104117
  } catch {
103195
104118
  return "unknown";
@@ -103233,7 +104156,7 @@ function assertSendable(f) {
103233
104156
  } catch {
103234
104157
  throw new Error(`refusing to send file \u2014 cannot resolve real path: ${f}`);
103235
104158
  }
103236
- const inbox = join68(stateReal, "inbox");
104159
+ const inbox = join70(stateReal, "inbox");
103237
104160
  if (real.startsWith(stateReal + sep4) && !real.startsWith(inbox + sep4)) {
103238
104161
  throw new Error(`refusing to send channel state: ${f}`);
103239
104162
  }
@@ -103252,7 +104175,7 @@ function assertSendable(f) {
103252
104175
  }
103253
104176
  function readAccessFile() {
103254
104177
  try {
103255
- const raw = readFileSync61(ACCESS_FILE, "utf8");
104178
+ const raw = readFileSync62(ACCESS_FILE, "utf8");
103256
104179
  const parsed = JSON.parse(raw);
103257
104180
  const allowFrom = validateStringArray("allowFrom", parsed.allowFrom ?? []);
103258
104181
  const groups = {};
@@ -103292,7 +104215,7 @@ function readAccessFile() {
103292
104215
  if (err.code === "ENOENT")
103293
104216
  return defaultAccess();
103294
104217
  try {
103295
- renameSync27(ACCESS_FILE, `${ACCESS_FILE}.corrupt-${Date.now()}`);
104218
+ renameSync28(ACCESS_FILE, `${ACCESS_FILE}.corrupt-${Date.now()}`);
103296
104219
  } catch {}
103297
104220
  process.stderr.write(`telegram gateway: access.json is corrupt, moved aside. Starting fresh.
103298
104221
  `);
@@ -103314,7 +104237,7 @@ function loadAccess() {
103314
104237
  }
103315
104238
  function readPeopleFile() {
103316
104239
  try {
103317
- const raw = readFileSync61(PEOPLE_FILE, "utf8");
104240
+ const raw = readFileSync62(PEOPLE_FILE, "utf8");
103318
104241
  const parsed = JSON.parse(raw);
103319
104242
  if (!Array.isArray(parsed.entries))
103320
104243
  return [];
@@ -103338,9 +104261,9 @@ function saveAccess(a) {
103338
104261
  return;
103339
104262
  mkdirSync51(STATE_DIR, { recursive: true, mode: 448 });
103340
104263
  const tmp = ACCESS_FILE + ".tmp";
103341
- writeFileSync50(tmp, JSON.stringify(a, null, 2) + `
104264
+ writeFileSync52(tmp, JSON.stringify(a, null, 2) + `
103342
104265
  `, { mode: 384 });
103343
- renameSync27(tmp, ACCESS_FILE);
104266
+ renameSync28(tmp, ACCESS_FILE);
103344
104267
  }
103345
104268
  function pruneExpired(a) {
103346
104269
  const now = Date.now();
@@ -103358,7 +104281,7 @@ var HISTORY_ENABLED = HISTORY_ACCESS.historyEnabled !== false;
103358
104281
  if (isGatewayMain && HISTORY_ENABLED) {
103359
104282
  try {
103360
104283
  initHistory(STATE_DIR, HISTORY_ACCESS.historyRetentionDays ?? 30);
103361
- 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")}
103362
104285
  `);
103363
104286
  } catch (err) {
103364
104287
  process.stderr.write(`telegram gateway: history init failed (${err.message}) \u2014 capture disabled
@@ -103377,12 +104300,12 @@ if (isGatewayMain)
103377
104300
  let markerTurnKey = null;
103378
104301
  let markerAgeMs = null;
103379
104302
  try {
103380
- const markerPath = join68(STATE_DIR, TURN_ACTIVE_MARKER_FILE2);
103381
- if (existsSync58(markerPath)) {
104303
+ const markerPath = join70(STATE_DIR, TURN_ACTIVE_MARKER_FILE2);
104304
+ if (existsSync60(markerPath)) {
103382
104305
  const st = statSync23(markerPath);
103383
104306
  markerAgeMs = Date.now() - st.mtimeMs;
103384
104307
  try {
103385
- const payload = JSON.parse(readFileSync61(markerPath, "utf8"));
104308
+ const payload = JSON.parse(readFileSync62(markerPath, "utf8"));
103386
104309
  if (typeof payload.turnKey === "string" && payload.turnKey.length > 0) {
103387
104310
  markerTurnKey = payload.turnKey;
103388
104311
  }
@@ -103402,10 +104325,10 @@ if (isGatewayMain)
103402
104325
  process.stderr.write(`telegram gateway: turn-registry boot-reaper stamped ${reaped} orphaned turn(s)` + `${timeoutTurnKey ? ` (turnKey=${timeoutTurnKey} as 'timeout', markerAgeMs=${markerAgeMs})` : " as 'restart'"}
103403
104326
  `);
103404
104327
  } else {
103405
- 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")}
103406
104329
  `);
103407
104330
  }
103408
- const bridgeDeadMarker = consumeBridgeDeadEscalationMarker(join68(STATE_DIR, "bridge-dead-escalation.json"));
104331
+ const bridgeDeadMarker = consumeBridgeDeadEscalationMarker(join70(STATE_DIR, "bridge-dead-escalation.json"));
103409
104332
  if (bridgeDeadMarker != null) {
103410
104333
  bridgeDeadPriorStreak = bridgeDeadMarker.count ?? 1;
103411
104334
  process.stderr.write(`telegram gateway: boot: prior restart was a bridge-dead escalation (reason=${bridgeDeadMarker.reason}` + `, consecutive=${bridgeDeadPriorStreak}` + `${bridgeDeadMarker.crashTail ? `, crashTail=${bridgeDeadMarker.crashTail}` : ""})
@@ -103418,7 +104341,7 @@ if (isGatewayMain)
103418
104341
  const pending2 = findLatestTurnIfInterrupted(turnsDb);
103419
104342
  const selfAgent = process.env.SWITCHROOM_AGENT_NAME ?? "";
103420
104343
  if (pending2 != null && selfAgent) {
103421
- 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");
103422
104345
  const bootResumeCleanMarker = readCleanShutdownMarker(bootResumeMarkerPath);
103423
104346
  const bootResumeForceAlways = process.env.SWITCHROOM_BOOT_RESUME_ALWAYS === "1";
103424
104347
  const bootResumeMode = parseBootResumeMode(process.env.SWITCHROOM_BOOT_RESUME);
@@ -103531,35 +104454,7 @@ if (isGatewayMain)
103531
104454
  `);
103532
104455
  }
103533
104456
  }
103534
- const pendingEnvPath = join68(agentDir, ".pending-turn.env");
103535
- try {
103536
- if (pending2 != null) {
103537
- const lines = [
103538
- `SWITCHROOM_PENDING_TURN=true`,
103539
- `SWITCHROOM_PENDING_TURN_KEY=${pending2.turn_key}`,
103540
- `SWITCHROOM_PENDING_CHAT_ID=${pending2.chat_id}`,
103541
- pending2.thread_id != null ? `SWITCHROOM_PENDING_THREAD_ID=${pending2.thread_id}` : `SWITCHROOM_PENDING_THREAD_ID=`,
103542
- pending2.last_user_msg_id != null ? `SWITCHROOM_PENDING_USER_MSG_ID=${pending2.last_user_msg_id}` : `SWITCHROOM_PENDING_USER_MSG_ID=`,
103543
- `SWITCHROOM_PENDING_ENDED_VIA=${pending2.ended_via ?? "unknown"}`,
103544
- `SWITCHROOM_PENDING_STARTED_AT=${pending2.started_at}`,
103545
- pending2.interrupt_reason != null ? `SWITCHROOM_PENDING_INTERRUPT_REASON=${pending2.interrupt_reason}` : `SWITCHROOM_PENDING_INTERRUPT_REASON=`
103546
- ];
103547
- const pendingEnvTmp = `${pendingEnvPath}.tmp-${process.pid}`;
103548
- writeFileSync50(pendingEnvTmp, lines.join(`
103549
- `) + `
103550
- `, { mode: 384 });
103551
- renameSync27(pendingEnvTmp, pendingEnvPath);
103552
- process.stderr.write(`telegram gateway: pending-turn env written to ${pendingEnvPath} turnKey=${pending2.turn_key} endedVia=${pending2.ended_via ?? "open"}
103553
- `);
103554
- } else if (existsSync58(pendingEnvPath)) {
103555
- rmSync8(pendingEnvPath, { force: true });
103556
- process.stderr.write(`telegram gateway: pending-turn env cleared (clean previous shutdown)
103557
- `);
103558
- }
103559
- } catch (err) {
103560
- process.stderr.write(`telegram gateway: pending-turn env write failed (${err.message})
103561
- `);
103562
- }
104457
+ writePendingTurnEnv(agentDir, pending2);
103563
104458
  } catch (err) {
103564
104459
  process.stderr.write(`telegram gateway: turn-registry init failed (${err.message}) \u2014 turn tracking disabled
103565
104460
  `);
@@ -103659,11 +104554,11 @@ function checkApprovals() {
103659
104554
  return;
103660
104555
  }
103661
104556
  for (const senderId of files) {
103662
- const file = join68(APPROVED_DIR, senderId);
103663
- 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) => {
103664
104559
  process.stderr.write(`telegram gateway: failed to send approval confirm: ${err}
103665
104560
  `);
103666
- rmSync8(file, { force: true });
104561
+ rmSync9(file, { force: true });
103667
104562
  });
103668
104563
  }
103669
104564
  }
@@ -103837,12 +104732,12 @@ function noteAgentOutputAt(key, ts) {
103837
104732
  lastAgentOutputAt.delete(oldest);
103838
104733
  }
103839
104734
  }
103840
- var OBLIGATION_STORE_PATH = join68(STATE_DIR, "obligations.json");
104735
+ var OBLIGATION_STORE_PATH = join70(STATE_DIR, "obligations.json");
103841
104736
  var obligationStoreFs = {
103842
- readFileSync: (p) => readFileSync61(p, "utf8"),
103843
- writeFileSync: (p, d) => writeFileSync50(p, d),
103844
- renameSync: (a, b) => renameSync27(a, b),
103845
- 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),
103846
104741
  fsyncFileSync: fsyncPathSync,
103847
104742
  fsyncDirSync: fsyncPathSync,
103848
104743
  unlinkSync: unlinkSync31
@@ -104603,7 +105498,7 @@ function emitTurnRecord(turn, endedAt) {
104603
105498
  return;
104604
105499
  }
104605
105500
  },
104606
- rename: (from, to) => renameSync27(from, to)
105501
+ rename: (from, to) => renameSync28(from, to)
104607
105502
  });
104608
105503
  appendFileSync9(turnsPath, rec);
104609
105504
  } catch {}
@@ -105923,6 +106818,10 @@ var inboundCoalescer = createInboundCoalescer({
105923
106818
  function emitGatewayOperatorEvent(event) {
105924
106819
  const { agent, kind } = event;
105925
106820
  event = { ...event, detail: redactOutboundText(event.detail, "operator_event") };
106821
+ if (kind === "transport-transient") {
106822
+ emitTransportTransientEvent(event, userFailureNoticeDeps());
106823
+ return;
106824
+ }
105926
106825
  let throttleEscalation = null;
105927
106826
  let escalationFired = false;
105928
106827
  let rateLimitedCooldownConsulted = false;
@@ -106077,40 +106976,37 @@ function emitGatewayOperatorEvent(event) {
106077
106976
  });
106078
106977
  }
106079
106978
  if (userNoticeChats.length > 0) {
106080
- const liveTurn = currentTurn;
106081
- const noticeKey = liveTurn != null ? statusKey(liveTurn.sessionChatId, liveTurn.sessionThreadId) : undefined;
106082
- pendingUserNoticeGate.schedule({
106083
- chatIds: userNoticeChats,
106084
- text: renderUserFacingFailureNotice(),
106085
- agent,
106086
- kind,
106087
- atMs: Date.now(),
106088
- key: noticeKey
106089
- });
106979
+ const noticeDeps = userFailureNoticeDeps();
106980
+ const noticeKey = noticeDeps.liveTurnKey();
106981
+ noticeDeps.scheduleUserNotice({ chatIds: userNoticeChats, agent, kind, key: noticeKey, atMs: Date.now() });
106090
106982
  process.stderr.write(`telegram gateway: operator-event user-notice deferred to turn-end agent=${agent} kind=${kind} chats=${userNoticeChats.length} topic=${noticeKey ?? "-"}
106091
106983
  `);
106092
106984
  }
106093
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
+ }
106094
107008
  function flushPendingUserFailureNotices(turnDeliveredReply, turnKey3) {
106095
- const notices = pendingUserNoticeGate.resolveTurnEnd(turnKey3, turnDeliveredReply);
106096
- if (notices.length === 0)
106097
- return;
106098
- const noticeTopic = resolveAgentOutboundTopic({ kind: "compact-watchdog" });
106099
- const noticeSupergroup = resolveAgentSupergroupChatId();
106100
- for (const notice of notices) {
106101
- process.stderr.write(`telegram gateway: user-notice flush (turn died reply-less) agent=${notice.agent} kind=${notice.kind} chats=${notice.chatIds.length}
106102
- `);
106103
- for (const chat_id of notice.chatIds) {
106104
- const thread = topicForRecipient({ recipientChatId: chat_id, resolvedTopic: noticeTopic, supergroupChatId: noticeSupergroup });
106105
- const opts = {
106106
- ...thread != null ? { message_thread_id: thread } : {}
106107
- };
106108
- bot.api.sendRichMessage(chat_id, richMessage2(notice.text), opts).catch((e) => {
106109
- process.stderr.write(`telegram gateway: user-notice send to ${chat_id} failed agent=${notice.agent} kind=${notice.kind}: ${e}
106110
- `);
106111
- });
106112
- }
106113
- }
107009
+ flushDeferredUserNotices(turnDeliveredReply, turnKey3, userFailureNoticeDeps());
106114
107010
  }
106115
107011
  function postLegacyBanner(chatId, threadId, ackMessageId, ageSec, site) {
106116
107012
  const text5 = `\uD83C\uDF9B\uFE0F Switchroom restarted \u2014 ready. (took ~${ageSec}s)`;
@@ -106166,28 +107062,28 @@ var PIN_STATUS_WHILE_WORKING = (() => {
106166
107062
  })();
106167
107063
  var statusPinClaims = new Map;
106168
107064
  var statusPinRightsCache = new PinRightsCache2;
106169
- var STATUS_PIN_STORE_PATH = join68(STATE_DIR, "status-pins.json");
107065
+ var STATUS_PIN_STORE_PATH = join70(STATE_DIR, "status-pins.json");
106170
107066
  var statusPinStoreFs = {
106171
- readFileSync: (p) => readFileSync61(p, "utf8"),
106172
- writeFileSync: (p, d) => writeFileSync50(p, d),
106173
- renameSync: (a, b) => renameSync27(a, b),
106174
- 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)
106175
107071
  };
106176
107072
  var statusPinPersistEnabled = !STATIC && PIN_STATUS_WHILE_WORKING;
106177
- 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");
106178
107074
  var activityCardStoreFs = {
106179
- readFileSync: (p) => readFileSync61(p, "utf8"),
106180
- writeFileSync: (p, d) => writeFileSync50(p, d),
106181
- renameSync: (a, b) => renameSync27(a, b),
106182
- 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)
106183
107079
  };
106184
107080
  var activityCardPersistEnabled = !STATIC;
106185
- 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");
106186
107082
  var queuedCardStoreFs = {
106187
- readFileSync: (p) => readFileSync61(p, "utf8"),
106188
- writeFileSync: (p, d) => writeFileSync50(p, d),
106189
- renameSync: (a, b) => renameSync27(a, b),
106190
- 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)
106191
107087
  };
106192
107088
  var queuedCardPersistEnabled = !STATIC;
106193
107089
  function persistQueuedCard(key, chatId, threadId, messageId) {
@@ -106474,7 +107370,7 @@ async function unpinAllStatusPins() {
106474
107370
  }
106475
107371
  }
106476
107372
  var stalePinSweepEligible = false;
106477
- 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");
106478
107374
  var stalePinSweeper = createGatewayStalePinSweeper({
106479
107375
  telegram: { handle: () => lockedBot, call: robustApiCall },
106480
107376
  claims: () => statusPinClaims.values(),
@@ -106483,9 +107379,9 @@ var stalePinSweeper = createGatewayStalePinSweeper({
106483
107379
  store: {
106484
107380
  path: STALE_PIN_SWEEP_STORE_PATH,
106485
107381
  fs: {
106486
- readFileSync: (p) => readFileSync61(p, "utf-8"),
107382
+ readFileSync: (p) => readFileSync62(p, "utf-8"),
106487
107383
  writeFileSync: (p, data) => atomicWriteFileSync(p, data, 384),
106488
- existsSync: (p) => existsSync58(p)
107384
+ existsSync: (p) => existsSync60(p)
106489
107385
  }
106490
107386
  },
106491
107387
  allowUnpinAllForumTopic: process.env.SWITCHROOM_PIN_SWEEP_UNPIN_ALL_TOPIC == null ? undefined : process.env.SWITCHROOM_PIN_SWEEP_UNPIN_ALL_TOPIC === "1"
@@ -106515,12 +107411,12 @@ var getPinnedProgressCardMessageId = null;
106515
107411
  var completeProgressCardTurn = null;
106516
107412
  var subagentWatcher = null;
106517
107413
  var workerActivityFeed = null;
106518
- 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");
106519
107415
  if (isGatewayMain)
106520
107416
  mkdirSync51(STATE_DIR, { recursive: true, mode: 448 });
106521
- var GATEWAY_PID_PATH = process.env.SWITCHROOM_GATEWAY_PID_FILE ?? join68(STATE_DIR, "gateway.pid.json");
106522
- var GATEWAY_SESSION_MARKER_PATH = process.env.SWITCHROOM_GATEWAY_SESSION_MARKER ?? join68(STATE_DIR, "gateway-session.json");
106523
- 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");
106524
107420
  var GATEWAY_STARTED_AT_MS = Date.now();
106525
107421
  var BOOT_CARD_ENABLED = process.env.SWITCHROOM_BOOT_CARD !== "false";
106526
107422
  var activeBootCard = null;
@@ -106549,7 +107445,7 @@ function ensureIssuesCard(chatId, threadId) {
106549
107445
  bot: botApi,
106550
107446
  log: (msg) => process.stderr.write(`telegram gateway: ${msg}
106551
107447
  `),
106552
- persistPath: join68(stateDir, "issues-card.json")
107448
+ persistPath: join70(stateDir, "issues-card.json")
106553
107449
  });
106554
107450
  activeIssuesWatcher = startIssuesWatcher({
106555
107451
  stateDir,
@@ -106754,13 +107650,13 @@ if (isGatewayMain)
106754
107650
  var inboundSpool;
106755
107651
  if (isGatewayMain)
106756
107652
  inboundSpool = STATIC ? undefined : createInboundSpool({
106757
- path: join68(STATE_DIR, "inbound-spool.jsonl"),
107653
+ path: join70(STATE_DIR, "inbound-spool.jsonl"),
106758
107654
  fs: {
106759
107655
  appendFileSync: (p, d) => appendFileSync9(p, d),
106760
- readFileSync: (p) => readFileSync61(p, "utf8"),
106761
- writeFileSync: (p, d) => writeFileSync50(p, d),
106762
- renameSync: (a, b) => renameSync27(a, b),
106763
- 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),
106764
107660
  statSizeSync: (p) => statSync23(p).size,
106765
107661
  fsyncFileSync: fsyncPathSync,
106766
107662
  fsyncDirSync: fsyncPathSync
@@ -106824,13 +107720,13 @@ async function maybeRedeliverUndeliveredAnswer() {
106824
107720
  let transcriptText;
106825
107721
  try {
106826
107722
  const projectsDir = getProjectsDirForCwd();
106827
- const path3 = join68(projectsDir, `${sessionId}.jsonl`);
106828
- if (!existsSync58(path3)) {
107723
+ const path3 = join70(projectsDir, `${sessionId}.jsonl`);
107724
+ if (!existsSync60(path3)) {
106829
107725
  process.stderr.write(`telegram gateway: crash-redelivery \u2014 transcript not found for turnKey=${turn.turn_key} session=${sessionId} (${path3}); skipping
106830
107726
  `);
106831
107727
  return;
106832
107728
  }
106833
- transcriptText = readFileSync61(path3, "utf8");
107729
+ transcriptText = readFileSync62(path3, "utf8");
106834
107730
  } catch (err) {
106835
107731
  process.stderr.write(`telegram gateway: crash-redelivery \u2014 transcript read failed turnKey=${turn.turn_key}: ${err.message}
106836
107732
  `);
@@ -106900,6 +107796,14 @@ function obligationSweep() {
106900
107796
  if (isGatewayMain && !STATIC && OBLIGATION_LEDGER_ENABLED) {
106901
107797
  setInterval(obligationSweep, OBLIGATION_SWEEP_MS).unref();
106902
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
+ }
106903
107807
  if (isGatewayMain && bootResumeInbound != null) {
106904
107808
  if (inboundSpool != null) {
106905
107809
  inboundSpool.put(bootResumeInbound.agent, bootResumeInbound.msg);
@@ -106989,8 +107893,8 @@ var bridgeDeadWatchdog = createBridgeDeadWatchdog({
106989
107893
  isSessionAlive: () => findAgentProcessInContainer2()?.comm === "claude",
106990
107894
  isShuttingDown: () => shuttingDown,
106991
107895
  escalate: (reason) => triggerSelfRestart(process.env.SWITCHROOM_AGENT_NAME ?? "", reason, 1500),
106992
- crashLogPath: join68(STATE_DIR, "bridge-crash.log"),
106993
- 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"),
106994
107898
  log: (line) => process.stderr.write(`${line}
106995
107899
  `),
106996
107900
  priorStreak: bridgeDeadPriorStreak,
@@ -107096,8 +108000,8 @@ if (isGatewayMain)
107096
108000
  probeQuotaViaBroker: (t) => probeQuotaForBootCard(agentSlug, t),
107097
108001
  tmuxSupervisor: process.env.SWITCHROOM_TMUX_SUPERVISOR === "1",
107098
108002
  dockerMode: process.env.SWITCHROOM_RUNTIME === "docker",
107099
- configSnapshotPath: join68(resolvedAgentDirForCard, ".config-snapshot.json"),
107100
- bootCardStatePath: join68(resolvedAgentDirForCard, ".boot-card-msgid.json"),
108003
+ configSnapshotPath: join70(resolvedAgentDirForCard, ".config-snapshot.json"),
108004
+ bootCardStatePath: join70(resolvedAgentDirForCard, ".boot-card-msgid.json"),
107101
108005
  floodStatePath: FLOOD_STATE_PATH,
107102
108006
  ...updateOutcomeLine ? { updateOutcomeLine } : {}
107103
108007
  }, ackMsgId).then((handle) => {
@@ -107746,9 +108650,12 @@ if (isGatewayMain)
107746
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}
107747
108651
  `);
107748
108652
  },
108653
+ onBuzzPublishResult: (_c, m) => getBuzzMirror()?.onPublishResult(m),
107749
108654
  log: (msg) => process.stderr.write(`telegram gateway: ipc \u2014 ${msg}
107750
108655
  `)
107751
108656
  });
108657
+ if (isGatewayMain)
108658
+ maybeBootBuzzMirror((msg) => ipcServer.sendToBuzzPeer(msg));
107752
108659
  if (isGatewayMain)
107753
108660
  (() => {
107754
108661
  try {
@@ -107774,7 +108681,7 @@ if (isGatewayMain)
107774
108681
  const receiverUid = receiverUidRaw ? Number(receiverUidRaw) : NaN;
107775
108682
  if (Number.isInteger(receiverUid))
107776
108683
  allowedUids.push(receiverUid);
107777
- const socketPath = join68(STATE_DIR, "webhook.sock");
108684
+ const socketPath = join70(STATE_DIR, "webhook.sock");
107778
108685
  const webhookInject = (agentName3, inbound) => {
107779
108686
  const msg = inbound;
107780
108687
  const delivered = ipcServer.sendToAgent(agentName3, msg);
@@ -108025,9 +108932,9 @@ function redactOutboundText(text5, site) {
108025
108932
  var VOICE_OUT_DEFAULT_CHUNK_CHARS = 600;
108026
108933
  var VOICE_OUT_HARD_CHUNK_CAP = 4096;
108027
108934
  var voiceOnDemandCache = new VoiceOnDemandCache({
108028
- persistPath: join68(STATE_DIR, "voice-ondemand.json")
108935
+ persistPath: join70(STATE_DIR, "voice-ondemand.json")
108029
108936
  });
108030
- var VOICE_CACHE_DIR = join68(STATE_DIR, "voice-cache");
108937
+ var VOICE_CACHE_DIR = join70(STATE_DIR, "voice-cache");
108031
108938
  var voicePreSynthQueue = new PreSynthQueue({
108032
108939
  runJob: async (job) => {
108033
108940
  const sidecarToken = await materializeSidecarToken2();
@@ -108183,6 +109090,7 @@ function gatewaySendReplyDeps() {
108183
109090
  resolveReplyOwnerTurn,
108184
109091
  findTurnByOriginId,
108185
109092
  findTurnByQuotedMessageId,
109093
+ findLatestTurnForChat,
108186
109094
  resolveAnswerThreadWithLog,
108187
109095
  resolveThreadId,
108188
109096
  getLatestInboundMessageId,
@@ -108434,11 +109342,11 @@ async function executeSendGif(rawArgs) {
108434
109342
  };
108435
109343
  }
108436
109344
  async function publishToTelegraph(text5, shortName, authorName) {
108437
- const accountPath = join68(STATE_DIR, "telegraph-account.json");
109345
+ const accountPath = join70(STATE_DIR, "telegraph-account.json");
108438
109346
  let account = null;
108439
109347
  try {
108440
- if (existsSync58(accountPath)) {
108441
- const raw = readFileSync61(accountPath, "utf-8");
109348
+ if (existsSync60(accountPath)) {
109349
+ const raw = readFileSync62(accountPath, "utf-8");
108442
109350
  const parsed = JSON.parse(raw);
108443
109351
  if (parsed.shortName && parsed.accessToken) {
108444
109352
  account = parsed;
@@ -108458,7 +109366,7 @@ async function publishToTelegraph(text5, shortName, authorName) {
108458
109366
  account = created.value;
108459
109367
  try {
108460
109368
  mkdirSync51(STATE_DIR, { recursive: true, mode: 448 });
108461
- writeFileSync50(accountPath, JSON.stringify(account, null, 2), { mode: 384 });
109369
+ writeFileSync52(accountPath, JSON.stringify(account, null, 2), { mode: 384 });
108462
109370
  } catch (err) {
108463
109371
  process.stderr.write(`telegram gateway: telegraph cache write failed: ${err.message}
108464
109372
  `);
@@ -108574,7 +109482,7 @@ _The secret was NOT saved. The agent can re-request with \`request_secret\`._`,
108574
109482
  }
108575
109483
  function readLiveSwitchroomConfigText() {
108576
109484
  const cfgPath = process.env.SWITCHROOM_CONFIG ?? findConfigFile2();
108577
- return readFileSync61(cfgPath, "utf8");
109485
+ return readFileSync62(cfgPath, "utf8");
108578
109486
  }
108579
109487
  async function executeReact(args) {
108580
109488
  if (!args.chat_id)
@@ -108615,7 +109523,7 @@ async function executeDownloadAttachment(args) {
108615
109523
  });
108616
109524
  mkdirSync51(INBOX_DIR, { recursive: true, mode: 448 });
108617
109525
  assertInsideInbox2(INBOX_DIR, dlPath);
108618
- writeFileSync50(dlPath, buf, { mode: 384 });
109526
+ writeFileSync52(dlPath, buf, { mode: 384 });
108619
109527
  return { content: [{ type: "text", text: dlPath }] };
108620
109528
  }
108621
109529
  async function executeEditMessage(args) {
@@ -108654,6 +109562,7 @@ async function executeEditMessage(args) {
108654
109562
  `);
108655
109563
  }
108656
109564
  }
109565
+ getBuzzMirror()?.mirrorCorrection({ telegramMessageKey: `${String(args.chat_id ?? "")}:${Number(args.message_id)}`, scrubbedText: editRawText });
108657
109566
  return { content: [{ type: "text", text: `edited (id: ${id})` }] };
108658
109567
  }
108659
109568
  async function executeSendTyping(args) {
@@ -109989,14 +110898,14 @@ function restartMarkerPath() {
109989
110898
  const agentDir = resolveAgentDirFromEnv();
109990
110899
  if (!agentDir)
109991
110900
  return null;
109992
- return join68(agentDir, "restart-pending.json");
110901
+ return join70(agentDir, "restart-pending.json");
109993
110902
  }
109994
110903
  function writeRestartMarker(marker) {
109995
110904
  const p = restartMarkerPath();
109996
110905
  if (!p)
109997
110906
  return;
109998
110907
  try {
109999
- writeFileSync50(p, JSON.stringify(marker));
110908
+ writeFileSync52(p, JSON.stringify(marker));
110000
110909
  lastPlannedRestartAt = Date.now();
110001
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}
110002
110911
  `);
@@ -110015,7 +110924,7 @@ function readRestartMarker() {
110015
110924
  if (!p)
110016
110925
  return null;
110017
110926
  try {
110018
- return JSON.parse(readFileSync61(p, "utf8"));
110927
+ return JSON.parse(readFileSync62(p, "utf8"));
110019
110928
  } catch {
110020
110929
  return null;
110021
110930
  }
@@ -110025,7 +110934,7 @@ function clearRestartMarker() {
110025
110934
  if (!p)
110026
110935
  return;
110027
110936
  try {
110028
- rmSync8(p, { force: true });
110937
+ rmSync9(p, { force: true });
110029
110938
  process.stderr.write(`telegram gateway: restart-marker: cleared path=${p}
110030
110939
  `);
110031
110940
  } catch {}
@@ -110164,7 +111073,7 @@ var _dockerReachable;
110164
111073
  function isDockerReachable() {
110165
111074
  if (_dockerReachable !== undefined)
110166
111075
  return _dockerReachable;
110167
- if (!existsSync58("/var/run/docker.sock")) {
111076
+ if (!existsSync60("/var/run/docker.sock")) {
110168
111077
  _dockerReachable = false;
110169
111078
  return _dockerReachable;
110170
111079
  }
@@ -110181,12 +111090,12 @@ function _resetDockerReachableCache() {
110181
111090
  }
110182
111091
  function spawnSwitchroomDetached(args, onFailure) {
110183
111092
  const fullArgs = SWITCHROOM_CONFIG ? ["--config", SWITCHROOM_CONFIG, ...args] : args;
110184
- const logPath = join68(STATE_DIR, "detached-spawn.log");
111093
+ const logPath = join70(STATE_DIR, "detached-spawn.log");
110185
111094
  let outFd = null;
110186
111095
  try {
110187
111096
  mkdirSync51(STATE_DIR, { recursive: true });
110188
111097
  outFd = openSync15(logPath, "a");
110189
- writeFileSync50(logPath, `
111098
+ writeFileSync52(logPath, `
110190
111099
  [${new Date().toISOString()}] spawn ${SWITCHROOM_CLI} ${fullArgs.join(" ")}
110191
111100
  `, { flag: "a" });
110192
111101
  } catch {}
@@ -110212,7 +111121,7 @@ function spawnSwitchroomDetached(args, onFailure) {
110212
111121
  return;
110213
111122
  let tail = "";
110214
111123
  try {
110215
- const full = readFileSync61(logPath, "utf8");
111124
+ const full = readFileSync62(logPath, "utf8");
110216
111125
  tail = full.split(`
110217
111126
  `).slice(-30).join(`
110218
111127
  `).trim();
@@ -110474,10 +111383,10 @@ ${preBlock(formatSwitchroomOutput(detail))}`, { html: true });
110474
111383
  }
110475
111384
  function readRecentDenialsForAgent(agentName3, windowMs, limit) {
110476
111385
  try {
110477
- const auditPath = join68(homedir20(), ".switchroom", "vault-audit.log");
110478
- if (!existsSync58(auditPath))
111386
+ const auditPath = join70(homedir20(), ".switchroom", "vault-audit.log");
111387
+ if (!existsSync60(auditPath))
110479
111388
  return [];
110480
- const raw = readFileSync61(auditPath, "utf8");
111389
+ const raw = readFileSync62(auditPath, "utf8");
110481
111390
  return recentDenialsFromAuditLog(raw, { agentName: agentName3, windowMs, limit });
110482
111391
  } catch {
110483
111392
  return [];
@@ -110528,7 +111437,7 @@ async function buildAgentMetadata(agentName3) {
110528
111437
  try {
110529
111438
  const agentDir = resolveAgentDirFromEnv();
110530
111439
  if (agentDir) {
110531
- const raw = readFileSync61(join68(agentDir, ".claude", ".claude.json"), "utf8");
111440
+ const raw = readFileSync62(join70(agentDir, ".claude", ".claude.json"), "utf8");
110532
111441
  claudeJson = JSON.parse(raw);
110533
111442
  }
110534
111443
  } catch {}
@@ -110657,7 +111566,7 @@ function buildModelDeps(restartCtx) {
110657
111566
  try {
110658
111567
  const agentDir = resolveAgentDirFromEnv();
110659
111568
  if (agentDir) {
110660
- const local = await fetchQuota2({ claudeConfigDir: join68(agentDir, ".claude") });
111569
+ const local = await fetchQuota2({ claudeConfigDir: join70(agentDir, ".claude") });
110661
111570
  if (local.ok)
110662
111571
  return formatQuotaLine2(local.data);
110663
111572
  }
@@ -110904,9 +111813,9 @@ function effortMenuReplyMarkup(reply) {
110904
111813
  function flushAgentHandoff(agentDir) {
110905
111814
  let removed = 0;
110906
111815
  for (const fname of [".handoff.md", ".handoff-topic"]) {
110907
- const p = join68(agentDir, fname);
111816
+ const p = join70(agentDir, fname);
110908
111817
  try {
110909
- if (existsSync58(p)) {
111818
+ if (existsSync60(p)) {
110910
111819
  unlinkSync31(p);
110911
111820
  removed++;
110912
111821
  }
@@ -110962,7 +111871,7 @@ async function handleNewCommand(ctx) {
110962
111871
  writeRestartMarker({ chat_id: chatId, thread_id: threadId ?? null, ack_message_id: ackId, ts: Date.now() });
110963
111872
  if (agentDir != null) {
110964
111873
  try {
110965
- writeFileSync50(join68(agentDir, ".force-fresh-session"), `${kind} at ${new Date().toISOString()}
111874
+ writeFileSync52(join70(agentDir, ".force-fresh-session"), `${kind} at ${new Date().toISOString()}
110966
111875
  `, "utf8");
110967
111876
  } catch (err) {
110968
111877
  process.stderr.write(`telegram gateway: failed to write force-fresh marker: ${err}
@@ -111075,16 +111984,16 @@ function buildFolderPickerDeps() {
111075
111984
  };
111076
111985
  }
111077
111986
  var lockoutOps = {
111078
- readFileSync: (p, enc) => readFileSync61(p, enc),
111079
- writeFileSync: (p, data, opts) => writeFileSync50(p, data, opts),
111080
- 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),
111081
111990
  mkdirSync: (p, opts) => mkdirSync51(p, opts),
111082
- joinPath: (...parts) => join68(...parts)
111991
+ joinPath: (...parts) => join70(...parts)
111083
111992
  };
111084
111993
  var FLEET_FALLBACK_DEDUP_MS = 30000;
111085
111994
  function isAuthBrokerSocketReachable() {
111086
111995
  try {
111087
- return existsSync58(resolveAuthBrokerSocketPath2());
111996
+ return existsSync60(resolveAuthBrokerSocketPath2());
111088
111997
  } catch {
111089
111998
  return false;
111090
111999
  }
@@ -111339,7 +112248,7 @@ async function runCreditWatch() {
111339
112248
  if (!agentDir)
111340
112249
  return;
111341
112250
  const agentName3 = getMyAgentName();
111342
- const claudeConfigDir = join68(agentDir, ".claude");
112251
+ const claudeConfigDir = join70(agentDir, ".claude");
111343
112252
  const stateDir = STATE_DIR;
111344
112253
  const reason = readClaudeJsonOverage(claudeConfigDir);
111345
112254
  const prev = loadCreditState(stateDir);
@@ -113095,7 +114004,7 @@ Send \`/auth ${parsed.provider} cancel\` to abort.`, { html: true });
113095
114004
  await switchroomReply(ctx, "**/usage:** cannot resolve agent dir.", { html: true });
113096
114005
  return;
113097
114006
  }
113098
- const result = await fetchQuota2({ claudeConfigDir: join68(agentDir, ".claude") });
114007
+ const result = await fetchQuota2({ claudeConfigDir: join70(agentDir, ".claude") });
113099
114008
  if (!result.ok) {
113100
114009
  await switchroomReply(ctx, `**/usage:** ${escapeHtmlForTg2(result.reason)}`, { html: true });
113101
114010
  return;
@@ -113482,7 +114391,7 @@ ${interimLabel}` : interimLabel
113482
114391
  const unifiedDiff = (() => {
113483
114392
  try {
113484
114393
  const cfgPath = process.env.SWITCHROOM_CONFIG ?? SWITCHROOM_CONFIG ?? findConfigFile2();
113485
- const raw = readFileSync61(cfgPath, "utf8");
114394
+ const raw = readFileSync62(cfgPath, "utf8");
113486
114395
  return synthesizeAllowRuleDiff({ agentName: agentName3, rule: chosen.rule, configText: raw });
113487
114396
  } catch (err) {
113488
114397
  process.stderr.write(`telegram gateway: always-allow diff synth failed: ${err.message}
@@ -114351,7 +115260,7 @@ async function startGateway() {
114351
115260
  return;
114352
115261
  }
114353
115262
  })();
114354
- const resolvedAgentDirForBootCard = agentDir ?? join68(homedir20(), ".switchroom", "agents", agentSlug);
115263
+ const resolvedAgentDirForBootCard = agentDir ?? join70(homedir20(), ".switchroom", "agents", agentSlug);
114355
115264
  const handle = await startBootCard(chatId, threadId, botApiForCard, {
114356
115265
  agentName: agentDisplayName,
114357
115266
  agentSlug,
@@ -114365,8 +115274,8 @@ async function startGateway() {
114365
115274
  probeQuotaViaBroker: (t) => probeQuotaForBootCard(agentSlug, t),
114366
115275
  tmuxSupervisor: process.env.SWITCHROOM_TMUX_SUPERVISOR === "1",
114367
115276
  dockerMode: process.env.SWITCHROOM_RUNTIME === "docker",
114368
- configSnapshotPath: join68(resolvedAgentDirForBootCard, ".config-snapshot.json"),
114369
- bootCardStatePath: join68(resolvedAgentDirForBootCard, ".boot-card-msgid.json"),
115277
+ configSnapshotPath: join70(resolvedAgentDirForBootCard, ".config-snapshot.json"),
115278
+ bootCardStatePath: join70(resolvedAgentDirForBootCard, ".boot-card-msgid.json"),
114370
115279
  floodStatePath: FLOOD_STATE_PATH,
114371
115280
  ...updateOutcomeLine ? { updateOutcomeLine } : {}
114372
115281
  }, ackMsgId);
@@ -114399,7 +115308,7 @@ async function startGateway() {
114399
115308
  if (smAgentDir) {
114400
115309
  const resolutionTimeoutMs = resolveSessionModelResolutionTimeoutMs(process.env.SWITCHROOM_SESSION_MODEL_RESOLUTION_TIMEOUT_MS);
114401
115310
  const resolved = await waitForSessionModelResolution({
114402
- barrierExists: () => existsSync58(join68(smAgentDir, ".session-model-resolved")),
115311
+ barrierExists: () => existsSync60(join70(smAgentDir, ".session-model-resolved")),
114403
115312
  timeoutMs: resolutionTimeoutMs
114404
115313
  });
114405
115314
  if (!resolved) {
@@ -114407,10 +115316,10 @@ async function startGateway() {
114407
115316
  process.stderr.write(`telegram gateway: gw /model relaunch UNRESOLVED agent=${getMyAgentName()} target=${target} (barrier timeout after ${resolutionTimeoutMs}ms)
114408
115317
  `);
114409
115318
  } else {
114410
- const activePath = join68(smAgentDir, ".active-session-model");
114411
- if (existsSync58(activePath)) {
115319
+ const activePath = join70(smAgentDir, ".active-session-model");
115320
+ if (existsSync60(activePath)) {
114412
115321
  try {
114413
- const launched = readFileSync61(activePath, "utf8").trim();
115322
+ const launched = readFileSync62(activePath, "utf8").trim();
114414
115323
  const configured = (() => {
114415
115324
  const d = switchroomExecJson(["agent", "list"]);
114416
115325
  const raw = d?.agents?.find((a) => a.name === getMyAgentName())?.model ?? null;
@@ -114443,24 +115352,24 @@ async function startGateway() {
114443
115352
  deliverModelSwitchBootNotice({
114444
115353
  ...modelBootCardDeps,
114445
115354
  confirmation,
114446
- hasSessionModelAlert: existsSync58(join68(smAgentDir, ".session-model-alert"))
115355
+ hasSessionModelAlert: existsSync60(join70(smAgentDir, ".session-model-alert"))
114447
115356
  });
114448
115357
  }
114449
115358
  } catch {}
114450
115359
  }
114451
- const activeEffortPath = join68(smAgentDir, ".active-session-effort");
114452
- if (existsSync58(activeEffortPath)) {
115360
+ const activeEffortPath = join70(smAgentDir, ".active-session-effort");
115361
+ if (existsSync60(activeEffortPath)) {
114453
115362
  try {
114454
- const launchedEffort = readFileSync61(activeEffortPath, "utf8").trim();
115363
+ const launchedEffort = readFileSync62(activeEffortPath, "utf8").trim();
114455
115364
  const configuredEffort = getConfiguredEffortForPersist();
114456
115365
  sessionEffortOverride = launchedEffort.length > 0 && launchedEffort !== configuredEffort ? launchedEffort : null;
114457
115366
  } catch {}
114458
115367
  }
114459
- const alertPath = join68(smAgentDir, ".session-model-alert");
114460
- if (existsSync58(alertPath)) {
115368
+ const alertPath = join70(smAgentDir, ".session-model-alert");
115369
+ if (existsSync60(alertPath)) {
114461
115370
  let alertText = null;
114462
115371
  try {
114463
- alertText = readFileSync61(alertPath, "utf8").trim();
115372
+ alertText = readFileSync62(alertPath, "utf8").trim();
114464
115373
  } catch {
114465
115374
  alertText = null;
114466
115375
  }