switchroom 0.14.37 → 0.14.38

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.
@@ -11101,18 +11101,11 @@ var TelegramChannelSchema = exports_external.object({
11101
11101
  webhook_via_gateway: exports_external.boolean().optional().describe("Route verified webhook events to the agent's in-container gateway " + "over a peercred-gated UDS (<agent>/telegram/webhook.sock) instead " + "of having the host-side web receiver write the agent dir directly. " + "Required under the Docker runtime: the receiver runs as the host " + "operator UID and cannot write the per-agent-UID-owned agent dir " + "(EACCES 500) nor connect the gateway socket. When true the gateway " + "(running as the agent UID) becomes the sole writer of " + "webhook-events.jsonl + dedup/cooldown state and also fires " + "webhook_dispatch. Off by default for back-compat with host-runtime " + "installs. See docs/rfcs/webhook-via-gateway-socket.md."),
11102
11102
  webhook_require_edge: exports_external.boolean().optional().describe("Cloudflare-only edge lock: require the X-Switchroom-Edge header " + "(injected by a Cloudflare Transform Rule on hooks.switchroom.ai) to " + "match the operator's edge secret at ~/.switchroom/webhook-edge-secret " + "before any HMAC verification; reject 403 otherwise. Proves the " + "request entered through our Cloudflare edge — the per-agent HMAC " + "alone can't (it proves body provenance, not network path). Stacks " + "on the GitHub-IP WAF + per-agent HMAC. Fail-closed: when required " + "but the secret file is missing/empty every request is rejected. Off " + "by default. See docs/rfcs/webhook-cloudflare-edge-lock.md."),
11103
11103
  chat_id: exports_external.string().regex(/^-\d+$/, 'supergroup chat_id must be a negative integer as a string (e.g. "-1001234567890")').optional().describe("Per-agent supergroup ID — overrides fleet `telegram.forum_chat_id`. " + "When set, requires `default_topic_id`. Negative integer as string. " + "Forbidden when `dm_only: true`. See docs/rfcs/supergroup-mode.md."),
11104
- default_topic_id: exports_external.number().int().positive().optional().describe("Forum topic ID this agent's automated outbounds default to when " + "no more-specific alias resolves. Required when `chat_id` is set. " + "Telegram's General topic is `id=1` at MTProto but sends omit the " + "field — the outbound wrapper strips `message_thread_id === 1` " + "on send. Forbidden when `dm_only: true`."),
11104
+ default_topic_id: exports_external.number().int().positive().optional().describe("Forum topic ID this agent's automated outbounds default to when " + "no more-specific alias resolves. Defaults to General (topic 1) when " + "`chat_id` is set and this is omitted — set it only to pin a different " + "fallback topic. " + "Telegram's General topic is `id=1` at MTProto but sends omit the " + "field — the outbound wrapper strips `message_thread_id === 1` " + "on send. Forbidden when `dm_only: true`."),
11105
11105
  topic_aliases: exports_external.record(exports_external.string(), exports_external.number().int().positive()).optional().describe("Operator-friendly names for forum topic IDs (e.g. " + "`{ general: 1, planning: 17, cron: 23, admin: 31, alerts: 41 }`). " + "Referenced from per-cron `topic:` fields and the outbound router " + "for autonomous events (boot → alerts, hostd → admin, etc.). " + "Cascades per-key through defaults → profile → agent.")
11106
11106
  }).optional().superRefine((tg, ctx) => {
11107
11107
  if (!tg)
11108
11108
  return;
11109
- if (tg.chat_id != null && tg.default_topic_id == null) {
11110
- ctx.addIssue({
11111
- code: exports_external.ZodIssueCode.custom,
11112
- message: "`channels.telegram.chat_id` requires `default_topic_id` — supergroup-mode agents need a fallback topic for unclassified outbounds.",
11113
- path: ["default_topic_id"]
11114
- });
11115
- }
11116
11109
  if (tg.default_topic_id != null && tg.chat_id == null) {
11117
11110
  ctx.addIssue({
11118
11111
  code: exports_external.ZodIssueCode.custom,
@@ -11127,6 +11120,11 @@ var TelegramChannelSchema = exports_external.object({
11127
11120
  path: ["topic_aliases"]
11128
11121
  });
11129
11122
  }
11123
+ }).transform((tg) => {
11124
+ if (tg && tg.chat_id != null && tg.default_topic_id == null) {
11125
+ return { ...tg, default_topic_id: 1 };
11126
+ }
11127
+ return tg;
11130
11128
  });
11131
11129
  var ChannelsSchema = exports_external.object({
11132
11130
  telegram: TelegramChannelSchema
@@ -11101,18 +11101,11 @@ var TelegramChannelSchema = exports_external.object({
11101
11101
  webhook_via_gateway: exports_external.boolean().optional().describe("Route verified webhook events to the agent's in-container gateway " + "over a peercred-gated UDS (<agent>/telegram/webhook.sock) instead " + "of having the host-side web receiver write the agent dir directly. " + "Required under the Docker runtime: the receiver runs as the host " + "operator UID and cannot write the per-agent-UID-owned agent dir " + "(EACCES 500) nor connect the gateway socket. When true the gateway " + "(running as the agent UID) becomes the sole writer of " + "webhook-events.jsonl + dedup/cooldown state and also fires " + "webhook_dispatch. Off by default for back-compat with host-runtime " + "installs. See docs/rfcs/webhook-via-gateway-socket.md."),
11102
11102
  webhook_require_edge: exports_external.boolean().optional().describe("Cloudflare-only edge lock: require the X-Switchroom-Edge header " + "(injected by a Cloudflare Transform Rule on hooks.switchroom.ai) to " + "match the operator's edge secret at ~/.switchroom/webhook-edge-secret " + "before any HMAC verification; reject 403 otherwise. Proves the " + "request entered through our Cloudflare edge — the per-agent HMAC " + "alone can't (it proves body provenance, not network path). Stacks " + "on the GitHub-IP WAF + per-agent HMAC. Fail-closed: when required " + "but the secret file is missing/empty every request is rejected. Off " + "by default. See docs/rfcs/webhook-cloudflare-edge-lock.md."),
11103
11103
  chat_id: exports_external.string().regex(/^-\d+$/, 'supergroup chat_id must be a negative integer as a string (e.g. "-1001234567890")').optional().describe("Per-agent supergroup ID — overrides fleet `telegram.forum_chat_id`. " + "When set, requires `default_topic_id`. Negative integer as string. " + "Forbidden when `dm_only: true`. See docs/rfcs/supergroup-mode.md."),
11104
- default_topic_id: exports_external.number().int().positive().optional().describe("Forum topic ID this agent's automated outbounds default to when " + "no more-specific alias resolves. Required when `chat_id` is set. " + "Telegram's General topic is `id=1` at MTProto but sends omit the " + "field — the outbound wrapper strips `message_thread_id === 1` " + "on send. Forbidden when `dm_only: true`."),
11104
+ default_topic_id: exports_external.number().int().positive().optional().describe("Forum topic ID this agent's automated outbounds default to when " + "no more-specific alias resolves. Defaults to General (topic 1) when " + "`chat_id` is set and this is omitted — set it only to pin a different " + "fallback topic. " + "Telegram's General topic is `id=1` at MTProto but sends omit the " + "field — the outbound wrapper strips `message_thread_id === 1` " + "on send. Forbidden when `dm_only: true`."),
11105
11105
  topic_aliases: exports_external.record(exports_external.string(), exports_external.number().int().positive()).optional().describe("Operator-friendly names for forum topic IDs (e.g. " + "`{ general: 1, planning: 17, cron: 23, admin: 31, alerts: 41 }`). " + "Referenced from per-cron `topic:` fields and the outbound router " + "for autonomous events (boot → alerts, hostd → admin, etc.). " + "Cascades per-key through defaults → profile → agent.")
11106
11106
  }).optional().superRefine((tg, ctx) => {
11107
11107
  if (!tg)
11108
11108
  return;
11109
- if (tg.chat_id != null && tg.default_topic_id == null) {
11110
- ctx.addIssue({
11111
- code: exports_external.ZodIssueCode.custom,
11112
- message: "`channels.telegram.chat_id` requires `default_topic_id` — supergroup-mode agents need a fallback topic for unclassified outbounds.",
11113
- path: ["default_topic_id"]
11114
- });
11115
- }
11116
11109
  if (tg.default_topic_id != null && tg.chat_id == null) {
11117
11110
  ctx.addIssue({
11118
11111
  code: exports_external.ZodIssueCode.custom,
@@ -11127,6 +11120,11 @@ var TelegramChannelSchema = exports_external.object({
11127
11120
  path: ["topic_aliases"]
11128
11121
  });
11129
11122
  }
11123
+ }).transform((tg) => {
11124
+ if (tg && tg.chat_id != null && tg.default_topic_id == null) {
11125
+ return { ...tg, default_topic_id: 1 };
11126
+ }
11127
+ return tg;
11130
11128
  });
11131
11129
  var ChannelsSchema = exports_external.object({
11132
11130
  telegram: TelegramChannelSchema
@@ -11849,18 +11849,11 @@ var TelegramChannelSchema = exports_external.object({
11849
11849
  webhook_via_gateway: exports_external.boolean().optional().describe("Route verified webhook events to the agent's in-container gateway " + "over a peercred-gated UDS (<agent>/telegram/webhook.sock) instead " + "of having the host-side web receiver write the agent dir directly. " + "Required under the Docker runtime: the receiver runs as the host " + "operator UID and cannot write the per-agent-UID-owned agent dir " + "(EACCES 500) nor connect the gateway socket. When true the gateway " + "(running as the agent UID) becomes the sole writer of " + "webhook-events.jsonl + dedup/cooldown state and also fires " + "webhook_dispatch. Off by default for back-compat with host-runtime " + "installs. See docs/rfcs/webhook-via-gateway-socket.md."),
11850
11850
  webhook_require_edge: exports_external.boolean().optional().describe("Cloudflare-only edge lock: require the X-Switchroom-Edge header " + "(injected by a Cloudflare Transform Rule on hooks.switchroom.ai) to " + "match the operator's edge secret at ~/.switchroom/webhook-edge-secret " + "before any HMAC verification; reject 403 otherwise. Proves the " + "request entered through our Cloudflare edge \u2014 the per-agent HMAC " + "alone can't (it proves body provenance, not network path). Stacks " + "on the GitHub-IP WAF + per-agent HMAC. Fail-closed: when required " + "but the secret file is missing/empty every request is rejected. Off " + "by default. See docs/rfcs/webhook-cloudflare-edge-lock.md."),
11851
11851
  chat_id: exports_external.string().regex(/^-\d+$/, 'supergroup chat_id must be a negative integer as a string (e.g. "-1001234567890")').optional().describe("Per-agent supergroup ID \u2014 overrides fleet `telegram.forum_chat_id`. " + "When set, requires `default_topic_id`. Negative integer as string. " + "Forbidden when `dm_only: true`. See docs/rfcs/supergroup-mode.md."),
11852
- default_topic_id: exports_external.number().int().positive().optional().describe("Forum topic ID this agent's automated outbounds default to when " + "no more-specific alias resolves. Required when `chat_id` is set. " + "Telegram's General topic is `id=1` at MTProto but sends omit the " + "field \u2014 the outbound wrapper strips `message_thread_id === 1` " + "on send. Forbidden when `dm_only: true`."),
11852
+ default_topic_id: exports_external.number().int().positive().optional().describe("Forum topic ID this agent's automated outbounds default to when " + "no more-specific alias resolves. Defaults to General (topic 1) when " + "`chat_id` is set and this is omitted \u2014 set it only to pin a different " + "fallback topic. " + "Telegram's General topic is `id=1` at MTProto but sends omit the " + "field \u2014 the outbound wrapper strips `message_thread_id === 1` " + "on send. Forbidden when `dm_only: true`."),
11853
11853
  topic_aliases: exports_external.record(exports_external.string(), exports_external.number().int().positive()).optional().describe("Operator-friendly names for forum topic IDs (e.g. " + "`{ general: 1, planning: 17, cron: 23, admin: 31, alerts: 41 }`). " + "Referenced from per-cron `topic:` fields and the outbound router " + "for autonomous events (boot \u2192 alerts, hostd \u2192 admin, etc.). " + "Cascades per-key through defaults \u2192 profile \u2192 agent.")
11854
11854
  }).optional().superRefine((tg, ctx) => {
11855
11855
  if (!tg)
11856
11856
  return;
11857
- if (tg.chat_id != null && tg.default_topic_id == null) {
11858
- ctx.addIssue({
11859
- code: exports_external.ZodIssueCode.custom,
11860
- message: "`channels.telegram.chat_id` requires `default_topic_id` \u2014 supergroup-mode agents need a fallback topic for unclassified outbounds.",
11861
- path: ["default_topic_id"]
11862
- });
11863
- }
11864
11857
  if (tg.default_topic_id != null && tg.chat_id == null) {
11865
11858
  ctx.addIssue({
11866
11859
  code: exports_external.ZodIssueCode.custom,
@@ -11875,6 +11868,11 @@ var TelegramChannelSchema = exports_external.object({
11875
11868
  path: ["topic_aliases"]
11876
11869
  });
11877
11870
  }
11871
+ }).transform((tg) => {
11872
+ if (tg && tg.chat_id != null && tg.default_topic_id == null) {
11873
+ return { ...tg, default_topic_id: 1 };
11874
+ }
11875
+ return tg;
11878
11876
  });
11879
11877
  var ChannelsSchema = exports_external.object({
11880
11878
  telegram: TelegramChannelSchema
@@ -13665,18 +13665,11 @@ var init_schema = __esm(() => {
13665
13665
  webhook_via_gateway: exports_external.boolean().optional().describe("Route verified webhook events to the agent's in-container gateway " + "over a peercred-gated UDS (<agent>/telegram/webhook.sock) instead " + "of having the host-side web receiver write the agent dir directly. " + "Required under the Docker runtime: the receiver runs as the host " + "operator UID and cannot write the per-agent-UID-owned agent dir " + "(EACCES 500) nor connect the gateway socket. When true the gateway " + "(running as the agent UID) becomes the sole writer of " + "webhook-events.jsonl + dedup/cooldown state and also fires " + "webhook_dispatch. Off by default for back-compat with host-runtime " + "installs. See docs/rfcs/webhook-via-gateway-socket.md."),
13666
13666
  webhook_require_edge: exports_external.boolean().optional().describe("Cloudflare-only edge lock: require the X-Switchroom-Edge header " + "(injected by a Cloudflare Transform Rule on hooks.switchroom.ai) to " + "match the operator's edge secret at ~/.switchroom/webhook-edge-secret " + "before any HMAC verification; reject 403 otherwise. Proves the " + "request entered through our Cloudflare edge \u2014 the per-agent HMAC " + "alone can't (it proves body provenance, not network path). Stacks " + "on the GitHub-IP WAF + per-agent HMAC. Fail-closed: when required " + "but the secret file is missing/empty every request is rejected. Off " + "by default. See docs/rfcs/webhook-cloudflare-edge-lock.md."),
13667
13667
  chat_id: exports_external.string().regex(/^-\d+$/, 'supergroup chat_id must be a negative integer as a string (e.g. "-1001234567890")').optional().describe("Per-agent supergroup ID \u2014 overrides fleet `telegram.forum_chat_id`. " + "When set, requires `default_topic_id`. Negative integer as string. " + "Forbidden when `dm_only: true`. See docs/rfcs/supergroup-mode.md."),
13668
- default_topic_id: exports_external.number().int().positive().optional().describe("Forum topic ID this agent's automated outbounds default to when " + "no more-specific alias resolves. Required when `chat_id` is set. " + "Telegram's General topic is `id=1` at MTProto but sends omit the " + "field \u2014 the outbound wrapper strips `message_thread_id === 1` " + "on send. Forbidden when `dm_only: true`."),
13668
+ default_topic_id: exports_external.number().int().positive().optional().describe("Forum topic ID this agent's automated outbounds default to when " + "no more-specific alias resolves. Defaults to General (topic 1) when " + "`chat_id` is set and this is omitted \u2014 set it only to pin a different " + "fallback topic. " + "Telegram's General topic is `id=1` at MTProto but sends omit the " + "field \u2014 the outbound wrapper strips `message_thread_id === 1` " + "on send. Forbidden when `dm_only: true`."),
13669
13669
  topic_aliases: exports_external.record(exports_external.string(), exports_external.number().int().positive()).optional().describe("Operator-friendly names for forum topic IDs (e.g. " + "`{ general: 1, planning: 17, cron: 23, admin: 31, alerts: 41 }`). " + "Referenced from per-cron `topic:` fields and the outbound router " + "for autonomous events (boot \u2192 alerts, hostd \u2192 admin, etc.). " + "Cascades per-key through defaults \u2192 profile \u2192 agent.")
13670
13670
  }).optional().superRefine((tg, ctx) => {
13671
13671
  if (!tg)
13672
13672
  return;
13673
- if (tg.chat_id != null && tg.default_topic_id == null) {
13674
- ctx.addIssue({
13675
- code: exports_external.ZodIssueCode.custom,
13676
- message: "`channels.telegram.chat_id` requires `default_topic_id` \u2014 supergroup-mode agents need a fallback topic for unclassified outbounds.",
13677
- path: ["default_topic_id"]
13678
- });
13679
- }
13680
13673
  if (tg.default_topic_id != null && tg.chat_id == null) {
13681
13674
  ctx.addIssue({
13682
13675
  code: exports_external.ZodIssueCode.custom,
@@ -13691,6 +13684,11 @@ var init_schema = __esm(() => {
13691
13684
  path: ["topic_aliases"]
13692
13685
  });
13693
13686
  }
13687
+ }).transform((tg) => {
13688
+ if (tg && tg.chat_id != null && tg.default_topic_id == null) {
13689
+ return { ...tg, default_topic_id: 1 };
13690
+ }
13691
+ return tg;
13694
13692
  });
13695
13693
  ChannelsSchema = exports_external.object({
13696
13694
  telegram: TelegramChannelSchema
@@ -23654,7 +23652,7 @@ var init_docker_fleet = __esm(() => {
23654
23652
 
23655
23653
  // src/agents/lifecycle.ts
23656
23654
  import { execFileSync as execFileSync7, spawn, spawnSync } from "node:child_process";
23657
- import { existsSync as existsSync15, mkdirSync as mkdirSync12, writeFileSync as writeFileSync7, renameSync as renameSync3, readFileSync as readFileSync13 } from "node:fs";
23655
+ import { existsSync as existsSync15, mkdirSync as mkdirSync12, writeFileSync as writeFileSync7, renameSync as renameSync4, readFileSync as readFileSync13 } from "node:fs";
23658
23656
  import { resolve as resolve13, join as join10 } from "node:path";
23659
23657
  import { connect } from "node:net";
23660
23658
  function cleanShutdownMarkerPathForAgent(name) {
@@ -23676,7 +23674,7 @@ function writeRestartReasonMarker(name, reason, opts = {}) {
23676
23674
  const marker = { ts: Date.now(), signal: "SIGTERM", reason };
23677
23675
  const tmp = `${path}.tmp-${process.pid}-${Date.now()}`;
23678
23676
  writeFileSync7(tmp, JSON.stringify(marker), "utf-8");
23679
- renameSync3(tmp, path);
23677
+ renameSync4(tmp, path);
23680
23678
  } catch {}
23681
23679
  }
23682
23680
  function buildCliRestartReason(opts) {
@@ -24154,7 +24152,7 @@ import {
24154
24152
  mkdirSync as mkdirSync14,
24155
24153
  readFileSync as readFileSync17,
24156
24154
  readdirSync as readdirSync9,
24157
- renameSync as renameSync5,
24155
+ renameSync as renameSync6,
24158
24156
  rmSync as rmSync4,
24159
24157
  statSync as statSync11,
24160
24158
  writeFileSync as writeFileSync9
@@ -24296,7 +24294,7 @@ function atomicCopy(src, dest, mode) {
24296
24294
  const tmp = `${dest}.tmp-${process.pid}-${randomBytes2(4).toString("hex")}`;
24297
24295
  try {
24298
24296
  writeFileSync9(tmp, contents, { mode });
24299
- renameSync5(tmp, dest);
24297
+ renameSync6(tmp, dest);
24300
24298
  } catch (err) {
24301
24299
  try {
24302
24300
  rmSync4(tmp);
@@ -25835,7 +25833,7 @@ import {
25835
25833
  mkdirSync as mkdirSync17,
25836
25834
  readFileSync as readFileSync22,
25837
25835
  readdirSync as readdirSync14,
25838
- renameSync as renameSync6,
25836
+ renameSync as renameSync7,
25839
25837
  rmSync as rmSync10,
25840
25838
  statSync as statSync15,
25841
25839
  writeFileSync as writeFileSync14
@@ -25972,7 +25970,7 @@ __export(exports_atomic, {
25972
25970
  atomicWriteFileSync: () => atomicWriteFileSync2
25973
25971
  });
25974
25972
  import { randomBytes as randomBytes3 } from "node:crypto";
25975
- import { closeSync as closeSync5, constants, fsyncSync as fsyncSync3, openSync as openSync5, renameSync as renameSync7, rmSync as rmSync11, writeSync as writeSync3 } from "node:fs";
25973
+ import { closeSync as closeSync5, constants, fsyncSync as fsyncSync3, openSync as openSync5, renameSync as renameSync8, rmSync as rmSync11, writeSync as writeSync3 } from "node:fs";
25976
25974
  function atomicWriteFileSync2(destPath, contents, mode = 384) {
25977
25975
  const tmp = `${destPath}.tmp-${process.pid}-${randomBytes3(4).toString("hex")}`;
25978
25976
  const buf = typeof contents === "string" ? Buffer.from(contents, "utf-8") : contents;
@@ -25983,7 +25981,7 @@ function atomicWriteFileSync2(destPath, contents, mode = 384) {
25983
25981
  fsyncSync3(fd);
25984
25982
  closeSync5(fd);
25985
25983
  fd = null;
25986
- renameSync7(tmp, destPath);
25984
+ renameSync8(tmp, destPath);
25987
25985
  } catch (err) {
25988
25986
  if (fd !== null) {
25989
25987
  try {
@@ -49440,8 +49438,8 @@ var {
49440
49438
  } = import__.default;
49441
49439
 
49442
49440
  // src/build-info.ts
49443
- var VERSION = "0.14.37";
49444
- var COMMIT_SHA = "90d0c420";
49441
+ var VERSION = "0.14.38";
49442
+ var COMMIT_SHA = "1529105b";
49445
49443
 
49446
49444
  // src/cli/agent.ts
49447
49445
  init_source();
@@ -49522,6 +49520,7 @@ import {
49522
49520
  writeFileSync as writeFileSync5,
49523
49521
  appendFileSync,
49524
49522
  readFileSync as readFileSync11,
49523
+ renameSync as renameSync3,
49525
49524
  chmodSync as chmodSync2,
49526
49525
  symlinkSync as symlinkSync2,
49527
49526
  copyFileSync as copyFileSync4,
@@ -51637,6 +51636,7 @@ This file is auto-maintained. Do not edit manually.
51637
51636
  `;
51638
51637
  }, created, skipped, 384);
51639
51638
  writeIfMissing(join8(agentDir, "telegram", "access.json"), () => buildAccessJson2(agentConfig, telegramConfig, topicId, userId), created, skipped, 384);
51639
+ reconcileConfiguredGroup(join8(agentDir, "telegram", "access.json"), agentConfig, telegramConfig);
51640
51640
  if (agentConfig.subagents) {
51641
51641
  const agentsDir2 = join8(agentDir, ".claude", "agents");
51642
51642
  mkdirSync9(agentsDir2, { recursive: true });
@@ -52760,6 +52760,36 @@ function rerenderWithFingerprint(filePath, contentFn, created, skipped, rewritte
52760
52760
  rewrittenWithBackup.push(filePath);
52761
52761
  created.push(filePath);
52762
52762
  }
52763
+ function resolveAgentForumChatId(agentConfig, telegramConfig) {
52764
+ const override = agentConfig.channels?.telegram?.chat_id;
52765
+ if (typeof override === "string" && override.length > 0)
52766
+ return override;
52767
+ return telegramConfig.forum_chat_id ?? "";
52768
+ }
52769
+ function reconcileConfiguredGroup(accessPath, agentConfig, telegramConfig) {
52770
+ if (!existsSync13(accessPath))
52771
+ return;
52772
+ const forumChatId = resolveAgentForumChatId(agentConfig, telegramConfig);
52773
+ const hasRealForumChat = forumChatId !== "" && forumChatId !== "0";
52774
+ if (agentConfig.dm_only || !hasRealForumChat)
52775
+ return;
52776
+ let access;
52777
+ try {
52778
+ access = JSON.parse(readFileSync11(accessPath, "utf-8"));
52779
+ } catch {
52780
+ return;
52781
+ }
52782
+ const groups = access.groups ??= {};
52783
+ if (groups[forumChatId] !== undefined)
52784
+ return;
52785
+ const allowFrom = Array.isArray(access.allowFrom) ? access.allowFrom : [];
52786
+ groups[forumChatId] = { requireMention: false, allowFrom };
52787
+ const tmp = accessPath + ".tmp";
52788
+ writeFileSync5(tmp, JSON.stringify(access, null, 2) + `
52789
+ `, { mode: 384 });
52790
+ renameSync3(tmp, accessPath);
52791
+ console.log(source_default.green(` registered supergroup ${forumChatId} in access.json ` + `(responds to all topics; requireMention=false)`));
52792
+ }
52763
52793
  function buildAccessJson2(agentConfig, telegramConfig, resolvedTopicId, userId) {
52764
52794
  const allowFrom = userId ? [String(userId)] : [];
52765
52795
  if (allowFrom.length === 0) {
@@ -52769,7 +52799,7 @@ function buildAccessJson2(agentConfig, telegramConfig, resolvedTopicId, userId)
52769
52799
  dmPolicy: "allowlist",
52770
52800
  allowFrom
52771
52801
  };
52772
- const forumChatId = telegramConfig.forum_chat_id;
52802
+ const forumChatId = resolveAgentForumChatId(agentConfig, telegramConfig);
52773
52803
  const hasRealForumChat = forumChatId !== "" && forumChatId !== "0";
52774
52804
  if (!agentConfig.dm_only && hasRealForumChat) {
52775
52805
  access.groups = {
@@ -53035,7 +53065,7 @@ import { join as join12 } from "node:path";
53035
53065
  import { execFileSync as execFileSync8 } from "node:child_process";
53036
53066
 
53037
53067
  // src/agents/handoff-summarizer.ts
53038
- import { readFileSync as readFileSync14, writeFileSync as writeFileSync8, renameSync as renameSync4, mkdirSync as mkdirSync13, existsSync as existsSync16, statSync as statSync9, readdirSync as readdirSync8 } from "node:fs";
53068
+ import { readFileSync as readFileSync14, writeFileSync as writeFileSync8, renameSync as renameSync5, mkdirSync as mkdirSync13, existsSync as existsSync16, statSync as statSync9, readdirSync as readdirSync8 } from "node:fs";
53039
53069
  import { join as join11 } from "node:path";
53040
53070
  var DEFAULT_MAX_TURNS = 50;
53041
53071
  var TOPIC_MAX_CHARS = 117;
@@ -53177,8 +53207,8 @@ function writeSidecarsAtomic(agentDir, briefing, topic) {
53177
53207
  const topicTmp = topicPath + ".tmp";
53178
53208
  writeFileSync8(handoffTmp, briefing, "utf-8");
53179
53209
  writeFileSync8(topicTmp, topic, "utf-8");
53180
- renameSync4(handoffTmp, handoffPath);
53181
- renameSync4(topicTmp, topicPath);
53210
+ renameSync5(handoffTmp, handoffPath);
53211
+ renameSync5(topicTmp, topicPath);
53182
53212
  }
53183
53213
  async function buildHandoff(opts) {
53184
53214
  const maxTurns = opts.maxTurns ?? DEFAULT_MAX_TURNS;
@@ -59057,7 +59087,7 @@ import { spawn as spawn4 } from "node:child_process";
59057
59087
  init_compose();
59058
59088
  init_vault();
59059
59089
  import * as net3 from "node:net";
59060
- import { mkdirSync as mkdirSync22, chmodSync as chmodSync7, chownSync as chownSync2, existsSync as existsSync35, readFileSync as readFileSync30, readdirSync as readdirSync16, statSync as statSync19, unlinkSync as unlinkSync8, writeFileSync as writeFileSync19, renameSync as renameSync9 } from "node:fs";
59090
+ import { mkdirSync as mkdirSync22, chmodSync as chmodSync7, chownSync as chownSync2, existsSync as existsSync35, readFileSync as readFileSync30, readdirSync as readdirSync16, statSync as statSync19, unlinkSync as unlinkSync8, writeFileSync as writeFileSync19, renameSync as renameSync10 } from "node:fs";
59061
59091
  import { dirname as dirname6, resolve as resolve26, basename as basename4 } from "node:path";
59062
59092
  import * as os4 from "node:os";
59063
59093
  import * as path3 from "node:path";
@@ -59074,7 +59104,7 @@ import {
59074
59104
  openSync as openSync6,
59075
59105
  closeSync as closeSync6,
59076
59106
  readFileSync as readFileSync28,
59077
- renameSync as renameSync8,
59107
+ renameSync as renameSync9,
59078
59108
  statSync as statSync18,
59079
59109
  symlinkSync as symlinkSync3,
59080
59110
  unlinkSync as unlinkSync7
@@ -59151,7 +59181,7 @@ function runMigration(home2, opts) {
59151
59181
  copyFileSync7(oldPath, tempNew);
59152
59182
  chmodSync4(tempNew, 384);
59153
59183
  fsyncFile(tempNew);
59154
- renameSync8(tempNew, newPath);
59184
+ renameSync9(tempNew, newPath);
59155
59185
  fsyncDir(parent);
59156
59186
  atomicReplaceWithSymlink(oldPath, "vault/vault.enc");
59157
59187
  fsyncDir(switchroomRoot);
@@ -59240,7 +59270,7 @@ function atomicReplaceWithSymlink(target, linkTarget) {
59240
59270
  } catch {}
59241
59271
  }
59242
59272
  symlinkSync3(linkTarget, tmp);
59243
- renameSync8(tmp, target);
59273
+ renameSync9(tmp, target);
59244
59274
  }
59245
59275
  function fsyncFile(path) {
59246
59276
  const fd = openSync6(path, "r+");
@@ -62880,7 +62910,7 @@ class VaultBroker {
62880
62910
  const tokenPath = path3.join(tokenDir, ".vault-token");
62881
62911
  const tmpPath = `${tokenPath}.tmp.${process.pid}`;
62882
62912
  writeFileSync19(tmpPath, mintResult.token, { mode: 384 });
62883
- renameSync9(tmpPath, tokenPath);
62913
+ renameSync10(tmpPath, tokenPath);
62884
62914
  try {
62885
62915
  const uid = allocateAgentUid(agent);
62886
62916
  chownSync2(tokenPath, uid, uid);
@@ -64262,7 +64292,7 @@ import {
64262
64292
  openSync as openSync9,
64263
64293
  readdirSync as readdirSync17,
64264
64294
  readFileSync as readFileSync34,
64265
- renameSync as renameSync10,
64295
+ renameSync as renameSync11,
64266
64296
  statSync as statSync20,
64267
64297
  symlinkSync as symlinkSync4,
64268
64298
  unlinkSync as unlinkSync10,
@@ -64414,7 +64444,7 @@ function backupVault(opts) {
64414
64444
  } catch {}
64415
64445
  throw new Error(`vault backup refused: '${fullPath}' already exists ` + `(sub-second collision with another backup). Retry in 1 second, ` + `or check for a concurrent backup process.`);
64416
64446
  }
64417
- renameSync10(tmpPath, fullPath);
64447
+ renameSync11(tmpPath, fullPath);
64418
64448
  const stat = statSync20(fullPath);
64419
64449
  const sha256 = sha256OfFile(fullPath);
64420
64450
  const row = {
@@ -74106,7 +74136,7 @@ import {
74106
74136
  openSync as openSync11,
74107
74137
  readdirSync as readdirSync21,
74108
74138
  readFileSync as readFileSync50,
74109
- renameSync as renameSync11,
74139
+ renameSync as renameSync12,
74110
74140
  statSync as statSync25,
74111
74141
  unlinkSync as unlinkSync11,
74112
74142
  writeFileSync as writeFileSync26,
@@ -74664,7 +74694,7 @@ function writeAll(stateDir, events) {
74664
74694
  `) + `
74665
74695
  `;
74666
74696
  writeFileSync26(tmp, body, "utf-8");
74667
- renameSync11(tmp, path4);
74697
+ renameSync12(tmp, path4);
74668
74698
  }
74669
74699
  var ORPHAN_TMP_TTL_MS = 60000;
74670
74700
  var TMP_PREFIX = `${ISSUES_FILE}.tmp-`;
@@ -76504,7 +76534,7 @@ import {
76504
76534
  readdirSync as readdirSync23,
76505
76535
  unlinkSync as unlinkSync12,
76506
76536
  existsSync as existsSync64,
76507
- renameSync as renameSync12
76537
+ renameSync as renameSync13
76508
76538
  } from "node:fs";
76509
76539
  import { join as join63, resolve as resolve41 } from "node:path";
76510
76540
  import { homedir as homedir37 } from "node:os";
@@ -76523,7 +76553,7 @@ function writeRecord(record2) {
76523
76553
  const tmp = `${target}.tmp${process.pid}`;
76524
76554
  writeFileSync30(tmp, JSON.stringify(record2, null, 2) + `
76525
76555
  `, { mode: 384 });
76526
- renameSync12(tmp, target);
76556
+ renameSync13(tmp, target);
76527
76557
  }
76528
76558
  function readRecord(id) {
76529
76559
  const path7 = recordPath(id);
@@ -77842,7 +77872,7 @@ async function fetchToken(vaultKey) {
77842
77872
 
77843
77873
  // src/cli/apply.ts
77844
77874
  init_source();
77845
- import { accessSync as accessSync3, chownSync as chownSync4, constants as fsConstants6, copyFileSync as copyFileSync11, existsSync as existsSync72, mkdirSync as mkdirSync40, readFileSync as readFileSync57, readdirSync as readdirSync26, renameSync as renameSync13, writeFileSync as writeFileSync35 } from "node:fs";
77875
+ import { accessSync as accessSync3, chownSync as chownSync4, constants as fsConstants6, copyFileSync as copyFileSync11, existsSync as existsSync72, mkdirSync as mkdirSync40, readFileSync as readFileSync57, readdirSync as readdirSync26, renameSync as renameSync14, writeFileSync as writeFileSync35 } from "node:fs";
77846
77876
  import { mkdir, writeFile } from "node:fs/promises";
77847
77877
  import { spawnSync as childSpawnSync } from "node:child_process";
77848
77878
  import readline from "node:readline";
@@ -78692,7 +78722,7 @@ function writeInstallTypeCache(homeDir = homedir40()) {
78692
78722
  source_paths: ctx.source_paths
78693
78723
  };
78694
78724
  writeFileSync35(tmp, JSON.stringify(payload, null, 2), { mode: 420 });
78695
- renameSync13(tmp, out);
78725
+ renameSync14(tmp, out);
78696
78726
  return out;
78697
78727
  }
78698
78728
  async function runApply(config, options, deps = {}, switchroomConfigPath) {
@@ -79764,7 +79794,7 @@ import {
79764
79794
  openSync as openSync13,
79765
79795
  readdirSync as readdirSync28,
79766
79796
  readFileSync as readFileSync60,
79767
- renameSync as renameSync14,
79797
+ renameSync as renameSync15,
79768
79798
  statSync as statSync28,
79769
79799
  unlinkSync as unlinkSync14,
79770
79800
  writeSync as writeSync8
@@ -79846,7 +79876,7 @@ function writeOverlayEntry(agent, slug, yamlText, opts = {}) {
79846
79876
  } finally {
79847
79877
  closeSync13(fd);
79848
79878
  }
79849
- renameSync14(stagingPath, finalPath);
79879
+ renameSync15(stagingPath, finalPath);
79850
79880
  return finalPath;
79851
79881
  });
79852
79882
  }
@@ -79863,7 +79893,7 @@ function writeSkillsOverlayEntry(agent, slug, yamlText, opts = {}) {
79863
79893
  } finally {
79864
79894
  closeSync13(fd);
79865
79895
  }
79866
- renameSync14(stagingPath, finalPath);
79896
+ renameSync15(stagingPath, finalPath);
79867
79897
  return finalPath;
79868
79898
  });
79869
79899
  }
@@ -80063,7 +80093,7 @@ import {
80063
80093
  openSync as openSync14,
80064
80094
  readdirSync as readdirSync29,
80065
80095
  readFileSync as readFileSync61,
80066
- renameSync as renameSync15,
80096
+ renameSync as renameSync16,
80067
80097
  unlinkSync as unlinkSync15,
80068
80098
  writeFileSync as writeFileSync36,
80069
80099
  writeSync as writeSync9
@@ -80106,7 +80136,7 @@ function stagePendingScheduleEntry(opts) {
80106
80136
  } finally {
80107
80137
  closeSync14(fd);
80108
80138
  }
80109
- renameSync15(yamlTmp, yamlPath);
80139
+ renameSync16(yamlTmp, yamlPath);
80110
80140
  }
80111
80141
  writeFileSync36(metaPath, JSON.stringify(meta, null, 2) + `
80112
80142
  `, { mode: 384 });
@@ -80145,7 +80175,7 @@ function commitPendingScheduleEntry(opts) {
80145
80175
  if (existsSync76(finalPath)) {
80146
80176
  return { committed: false, reason: "slug_collision" };
80147
80177
  }
80148
- renameSync15(match.yamlPath, finalPath);
80178
+ renameSync16(match.yamlPath, finalPath);
80149
80179
  unlinkSync15(match.metaPath);
80150
80180
  return { committed: true, path: finalPath, slug };
80151
80181
  }
@@ -80895,7 +80925,7 @@ import {
80895
80925
  readFileSync as readFileSync63,
80896
80926
  readdirSync as readdirSync30,
80897
80927
  realpathSync as realpathSync7,
80898
- renameSync as renameSync16,
80928
+ renameSync as renameSync17,
80899
80929
  rmSync as rmSync16,
80900
80930
  statSync as statSync29,
80901
80931
  writeFileSync as writeFileSync37
@@ -81350,9 +81380,9 @@ function writePayload(poolDir, name, files) {
81350
81380
  } catch {}
81351
81381
  if (targetExists) {
81352
81382
  oldRename = `${target}.skill-apply-old-${Date.now()}`;
81353
- renameSync16(target, oldRename);
81383
+ renameSync17(target, oldRename);
81354
81384
  }
81355
- renameSync16(staging, target);
81385
+ renameSync17(staging, target);
81356
81386
  if (oldRename) {
81357
81387
  rmSync16(oldRename, { recursive: true, force: true });
81358
81388
  oldRename = null;
@@ -81366,7 +81396,7 @@ function writePayload(poolDir, name, files) {
81366
81396
  if (existsSync79(target)) {
81367
81397
  rmSync16(target, { recursive: true, force: true });
81368
81398
  }
81369
- renameSync16(oldRename, target);
81399
+ renameSync17(oldRename, target);
81370
81400
  } catch {}
81371
81401
  }
81372
81402
  throw err2;
@@ -81446,7 +81476,7 @@ import {
81446
81476
  openSync as openSync16,
81447
81477
  readFileSync as readFileSync64,
81448
81478
  readdirSync as readdirSync31,
81449
- renameSync as renameSync17,
81479
+ renameSync as renameSync18,
81450
81480
  rmSync as rmSync17,
81451
81481
  statSync as statSync30,
81452
81482
  utimesSync,
@@ -81509,7 +81539,7 @@ function mirrorToConfigRepo(agent, name, liveSkillDir) {
81509
81539
  sweepMirrorPriors(configSkillsRoot);
81510
81540
  if (existsSync80(dest)) {
81511
81541
  const trash = join77(configSkillsRoot, `.${name}-trash-${Date.now()}`);
81512
- renameSync17(dest, trash);
81542
+ renameSync18(dest, trash);
81513
81543
  }
81514
81544
  return;
81515
81545
  }
@@ -81533,9 +81563,9 @@ function mirrorToConfigRepo(agent, name, liveSkillDir) {
81533
81563
  walk2(liveSkillDir, staging);
81534
81564
  if (existsSync80(dest)) {
81535
81565
  const prior = join77(configSkillsRoot, `.${name}-prior-${Date.now()}`);
81536
- renameSync17(dest, prior);
81566
+ renameSync18(dest, prior);
81537
81567
  }
81538
- renameSync17(staging, dest);
81568
+ renameSync18(staging, dest);
81539
81569
  } catch (err2) {
81540
81570
  process.stderr.write(source_default.yellow(`warning: mirror to ${dest} failed (${err2.message ?? err2}); ` + `live copy still works, but this skill is not version-controlled until next successful sync.
81541
81571
  `));
@@ -81718,9 +81748,9 @@ function writePersonalSkill(targetDir, files) {
81718
81748
  } catch {}
81719
81749
  if (targetExists) {
81720
81750
  oldRename = `${targetDir}.personal-old-${Date.now()}`;
81721
- renameSync17(targetDir, oldRename);
81751
+ renameSync18(targetDir, oldRename);
81722
81752
  }
81723
- renameSync17(staging, targetDir);
81753
+ renameSync18(staging, targetDir);
81724
81754
  if (oldRename) {
81725
81755
  rmSync17(oldRename, { recursive: true, force: true });
81726
81756
  oldRename = null;
@@ -81734,7 +81764,7 @@ function writePersonalSkill(targetDir, files) {
81734
81764
  if (existsSync80(targetDir)) {
81735
81765
  rmSync17(targetDir, { recursive: true, force: true });
81736
81766
  }
81737
- renameSync17(oldRename, targetDir);
81767
+ renameSync18(oldRename, targetDir);
81738
81768
  } catch {}
81739
81769
  }
81740
81770
  throw err2;
@@ -81972,7 +82002,7 @@ function removePersonalAction(name, opts) {
81972
82002
  mkdirSync45(trashRoot, { recursive: true, mode: 493 });
81973
82003
  const ts = Date.now();
81974
82004
  const trashTarget = join77(trashRoot, `${name}-${ts}`);
81975
- renameSync17(target, trashTarget);
82005
+ renameSync18(target, trashTarget);
81976
82006
  const now = new Date(ts);
81977
82007
  utimesSync(trashTarget, now, now);
81978
82008
  mirrorToConfigRepo(agent, name, null);
@@ -13836,18 +13836,11 @@ var TelegramChannelSchema = exports_external.object({
13836
13836
  webhook_via_gateway: exports_external.boolean().optional().describe("Route verified webhook events to the agent's in-container gateway " + "over a peercred-gated UDS (<agent>/telegram/webhook.sock) instead " + "of having the host-side web receiver write the agent dir directly. " + "Required under the Docker runtime: the receiver runs as the host " + "operator UID and cannot write the per-agent-UID-owned agent dir " + "(EACCES 500) nor connect the gateway socket. When true the gateway " + "(running as the agent UID) becomes the sole writer of " + "webhook-events.jsonl + dedup/cooldown state and also fires " + "webhook_dispatch. Off by default for back-compat with host-runtime " + "installs. See docs/rfcs/webhook-via-gateway-socket.md."),
13837
13837
  webhook_require_edge: exports_external.boolean().optional().describe("Cloudflare-only edge lock: require the X-Switchroom-Edge header " + "(injected by a Cloudflare Transform Rule on hooks.switchroom.ai) to " + "match the operator's edge secret at ~/.switchroom/webhook-edge-secret " + "before any HMAC verification; reject 403 otherwise. Proves the " + "request entered through our Cloudflare edge — the per-agent HMAC " + "alone can't (it proves body provenance, not network path). Stacks " + "on the GitHub-IP WAF + per-agent HMAC. Fail-closed: when required " + "but the secret file is missing/empty every request is rejected. Off " + "by default. See docs/rfcs/webhook-cloudflare-edge-lock.md."),
13838
13838
  chat_id: exports_external.string().regex(/^-\d+$/, 'supergroup chat_id must be a negative integer as a string (e.g. "-1001234567890")').optional().describe("Per-agent supergroup ID — overrides fleet `telegram.forum_chat_id`. " + "When set, requires `default_topic_id`. Negative integer as string. " + "Forbidden when `dm_only: true`. See docs/rfcs/supergroup-mode.md."),
13839
- default_topic_id: exports_external.number().int().positive().optional().describe("Forum topic ID this agent's automated outbounds default to when " + "no more-specific alias resolves. Required when `chat_id` is set. " + "Telegram's General topic is `id=1` at MTProto but sends omit the " + "field — the outbound wrapper strips `message_thread_id === 1` " + "on send. Forbidden when `dm_only: true`."),
13839
+ default_topic_id: exports_external.number().int().positive().optional().describe("Forum topic ID this agent's automated outbounds default to when " + "no more-specific alias resolves. Defaults to General (topic 1) when " + "`chat_id` is set and this is omitted — set it only to pin a different " + "fallback topic. " + "Telegram's General topic is `id=1` at MTProto but sends omit the " + "field — the outbound wrapper strips `message_thread_id === 1` " + "on send. Forbidden when `dm_only: true`."),
13840
13840
  topic_aliases: exports_external.record(exports_external.string(), exports_external.number().int().positive()).optional().describe("Operator-friendly names for forum topic IDs (e.g. " + "`{ general: 1, planning: 17, cron: 23, admin: 31, alerts: 41 }`). " + "Referenced from per-cron `topic:` fields and the outbound router " + "for autonomous events (boot → alerts, hostd → admin, etc.). " + "Cascades per-key through defaults → profile → agent.")
13841
13841
  }).optional().superRefine((tg, ctx) => {
13842
13842
  if (!tg)
13843
13843
  return;
13844
- if (tg.chat_id != null && tg.default_topic_id == null) {
13845
- ctx.addIssue({
13846
- code: exports_external.ZodIssueCode.custom,
13847
- message: "`channels.telegram.chat_id` requires `default_topic_id` — supergroup-mode agents need a fallback topic for unclassified outbounds.",
13848
- path: ["default_topic_id"]
13849
- });
13850
- }
13851
13844
  if (tg.default_topic_id != null && tg.chat_id == null) {
13852
13845
  ctx.addIssue({
13853
13846
  code: exports_external.ZodIssueCode.custom,
@@ -13862,6 +13855,11 @@ var TelegramChannelSchema = exports_external.object({
13862
13855
  path: ["topic_aliases"]
13863
13856
  });
13864
13857
  }
13858
+ }).transform((tg) => {
13859
+ if (tg && tg.chat_id != null && tg.default_topic_id == null) {
13860
+ return { ...tg, default_topic_id: 1 };
13861
+ }
13862
+ return tg;
13865
13863
  });
13866
13864
  var ChannelsSchema = exports_external.object({
13867
13865
  telegram: TelegramChannelSchema
@@ -11422,18 +11422,11 @@ var init_schema = __esm(() => {
11422
11422
  webhook_via_gateway: exports_external.boolean().optional().describe("Route verified webhook events to the agent's in-container gateway " + "over a peercred-gated UDS (<agent>/telegram/webhook.sock) instead " + "of having the host-side web receiver write the agent dir directly. " + "Required under the Docker runtime: the receiver runs as the host " + "operator UID and cannot write the per-agent-UID-owned agent dir " + "(EACCES 500) nor connect the gateway socket. When true the gateway " + "(running as the agent UID) becomes the sole writer of " + "webhook-events.jsonl + dedup/cooldown state and also fires " + "webhook_dispatch. Off by default for back-compat with host-runtime " + "installs. See docs/rfcs/webhook-via-gateway-socket.md."),
11423
11423
  webhook_require_edge: exports_external.boolean().optional().describe("Cloudflare-only edge lock: require the X-Switchroom-Edge header " + "(injected by a Cloudflare Transform Rule on hooks.switchroom.ai) to " + "match the operator's edge secret at ~/.switchroom/webhook-edge-secret " + "before any HMAC verification; reject 403 otherwise. Proves the " + "request entered through our Cloudflare edge — the per-agent HMAC " + "alone can't (it proves body provenance, not network path). Stacks " + "on the GitHub-IP WAF + per-agent HMAC. Fail-closed: when required " + "but the secret file is missing/empty every request is rejected. Off " + "by default. See docs/rfcs/webhook-cloudflare-edge-lock.md."),
11424
11424
  chat_id: exports_external.string().regex(/^-\d+$/, 'supergroup chat_id must be a negative integer as a string (e.g. "-1001234567890")').optional().describe("Per-agent supergroup ID — overrides fleet `telegram.forum_chat_id`. " + "When set, requires `default_topic_id`. Negative integer as string. " + "Forbidden when `dm_only: true`. See docs/rfcs/supergroup-mode.md."),
11425
- default_topic_id: exports_external.number().int().positive().optional().describe("Forum topic ID this agent's automated outbounds default to when " + "no more-specific alias resolves. Required when `chat_id` is set. " + "Telegram's General topic is `id=1` at MTProto but sends omit the " + "field — the outbound wrapper strips `message_thread_id === 1` " + "on send. Forbidden when `dm_only: true`."),
11425
+ default_topic_id: exports_external.number().int().positive().optional().describe("Forum topic ID this agent's automated outbounds default to when " + "no more-specific alias resolves. Defaults to General (topic 1) when " + "`chat_id` is set and this is omitted — set it only to pin a different " + "fallback topic. " + "Telegram's General topic is `id=1` at MTProto but sends omit the " + "field — the outbound wrapper strips `message_thread_id === 1` " + "on send. Forbidden when `dm_only: true`."),
11426
11426
  topic_aliases: exports_external.record(exports_external.string(), exports_external.number().int().positive()).optional().describe("Operator-friendly names for forum topic IDs (e.g. " + "`{ general: 1, planning: 17, cron: 23, admin: 31, alerts: 41 }`). " + "Referenced from per-cron `topic:` fields and the outbound router " + "for autonomous events (boot → alerts, hostd → admin, etc.). " + "Cascades per-key through defaults → profile → agent.")
11427
11427
  }).optional().superRefine((tg, ctx) => {
11428
11428
  if (!tg)
11429
11429
  return;
11430
- if (tg.chat_id != null && tg.default_topic_id == null) {
11431
- ctx.addIssue({
11432
- code: exports_external.ZodIssueCode.custom,
11433
- message: "`channels.telegram.chat_id` requires `default_topic_id` — supergroup-mode agents need a fallback topic for unclassified outbounds.",
11434
- path: ["default_topic_id"]
11435
- });
11436
- }
11437
11430
  if (tg.default_topic_id != null && tg.chat_id == null) {
11438
11431
  ctx.addIssue({
11439
11432
  code: exports_external.ZodIssueCode.custom,
@@ -11448,6 +11441,11 @@ var init_schema = __esm(() => {
11448
11441
  path: ["topic_aliases"]
11449
11442
  });
11450
11443
  }
11444
+ }).transform((tg) => {
11445
+ if (tg && tg.chat_id != null && tg.default_topic_id == null) {
11446
+ return { ...tg, default_topic_id: 1 };
11447
+ }
11448
+ return tg;
11451
11449
  });
11452
11450
  ChannelsSchema = exports_external.object({
11453
11451
  telegram: TelegramChannelSchema
@@ -11422,18 +11422,11 @@ var init_schema = __esm(() => {
11422
11422
  webhook_via_gateway: exports_external.boolean().optional().describe("Route verified webhook events to the agent's in-container gateway " + "over a peercred-gated UDS (<agent>/telegram/webhook.sock) instead " + "of having the host-side web receiver write the agent dir directly. " + "Required under the Docker runtime: the receiver runs as the host " + "operator UID and cannot write the per-agent-UID-owned agent dir " + "(EACCES 500) nor connect the gateway socket. When true the gateway " + "(running as the agent UID) becomes the sole writer of " + "webhook-events.jsonl + dedup/cooldown state and also fires " + "webhook_dispatch. Off by default for back-compat with host-runtime " + "installs. See docs/rfcs/webhook-via-gateway-socket.md."),
11423
11423
  webhook_require_edge: exports_external.boolean().optional().describe("Cloudflare-only edge lock: require the X-Switchroom-Edge header " + "(injected by a Cloudflare Transform Rule on hooks.switchroom.ai) to " + "match the operator's edge secret at ~/.switchroom/webhook-edge-secret " + "before any HMAC verification; reject 403 otherwise. Proves the " + "request entered through our Cloudflare edge — the per-agent HMAC " + "alone can't (it proves body provenance, not network path). Stacks " + "on the GitHub-IP WAF + per-agent HMAC. Fail-closed: when required " + "but the secret file is missing/empty every request is rejected. Off " + "by default. See docs/rfcs/webhook-cloudflare-edge-lock.md."),
11424
11424
  chat_id: exports_external.string().regex(/^-\d+$/, 'supergroup chat_id must be a negative integer as a string (e.g. "-1001234567890")').optional().describe("Per-agent supergroup ID — overrides fleet `telegram.forum_chat_id`. " + "When set, requires `default_topic_id`. Negative integer as string. " + "Forbidden when `dm_only: true`. See docs/rfcs/supergroup-mode.md."),
11425
- default_topic_id: exports_external.number().int().positive().optional().describe("Forum topic ID this agent's automated outbounds default to when " + "no more-specific alias resolves. Required when `chat_id` is set. " + "Telegram's General topic is `id=1` at MTProto but sends omit the " + "field — the outbound wrapper strips `message_thread_id === 1` " + "on send. Forbidden when `dm_only: true`."),
11425
+ default_topic_id: exports_external.number().int().positive().optional().describe("Forum topic ID this agent's automated outbounds default to when " + "no more-specific alias resolves. Defaults to General (topic 1) when " + "`chat_id` is set and this is omitted — set it only to pin a different " + "fallback topic. " + "Telegram's General topic is `id=1` at MTProto but sends omit the " + "field — the outbound wrapper strips `message_thread_id === 1` " + "on send. Forbidden when `dm_only: true`."),
11426
11426
  topic_aliases: exports_external.record(exports_external.string(), exports_external.number().int().positive()).optional().describe("Operator-friendly names for forum topic IDs (e.g. " + "`{ general: 1, planning: 17, cron: 23, admin: 31, alerts: 41 }`). " + "Referenced from per-cron `topic:` fields and the outbound router " + "for autonomous events (boot → alerts, hostd → admin, etc.). " + "Cascades per-key through defaults → profile → agent.")
11427
11427
  }).optional().superRefine((tg, ctx) => {
11428
11428
  if (!tg)
11429
11429
  return;
11430
- if (tg.chat_id != null && tg.default_topic_id == null) {
11431
- ctx.addIssue({
11432
- code: exports_external.ZodIssueCode.custom,
11433
- message: "`channels.telegram.chat_id` requires `default_topic_id` — supergroup-mode agents need a fallback topic for unclassified outbounds.",
11434
- path: ["default_topic_id"]
11435
- });
11436
- }
11437
11430
  if (tg.default_topic_id != null && tg.chat_id == null) {
11438
11431
  ctx.addIssue({
11439
11432
  code: exports_external.ZodIssueCode.custom,
@@ -11448,6 +11441,11 @@ var init_schema = __esm(() => {
11448
11441
  path: ["topic_aliases"]
11449
11442
  });
11450
11443
  }
11444
+ }).transform((tg) => {
11445
+ if (tg && tg.chat_id != null && tg.default_topic_id == null) {
11446
+ return { ...tg, default_topic_id: 1 };
11447
+ }
11448
+ return tg;
11451
11449
  });
11452
11450
  ChannelsSchema = exports_external.object({
11453
11451
  telegram: TelegramChannelSchema
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "switchroom",
3
- "version": "0.14.37",
3
+ "version": "0.14.38",
4
4
  "description": "Run Claude Code 24/7 on your Claude Pro/Max subscription over Telegram. Open-source alternative to OpenClaw and NanoClaw — no API keys.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -23807,18 +23807,11 @@ var init_schema = __esm(() => {
23807
23807
  webhook_via_gateway: exports_external.boolean().optional().describe("Route verified webhook events to the agent's in-container gateway " + "over a peercred-gated UDS (<agent>/telegram/webhook.sock) instead " + "of having the host-side web receiver write the agent dir directly. " + "Required under the Docker runtime: the receiver runs as the host " + "operator UID and cannot write the per-agent-UID-owned agent dir " + "(EACCES 500) nor connect the gateway socket. When true the gateway " + "(running as the agent UID) becomes the sole writer of " + "webhook-events.jsonl + dedup/cooldown state and also fires " + "webhook_dispatch. Off by default for back-compat with host-runtime " + "installs. See docs/rfcs/webhook-via-gateway-socket.md."),
23808
23808
  webhook_require_edge: exports_external.boolean().optional().describe("Cloudflare-only edge lock: require the X-Switchroom-Edge header " + "(injected by a Cloudflare Transform Rule on hooks.switchroom.ai) to " + "match the operator's edge secret at ~/.switchroom/webhook-edge-secret " + "before any HMAC verification; reject 403 otherwise. Proves the " + "request entered through our Cloudflare edge \u2014 the per-agent HMAC " + "alone can't (it proves body provenance, not network path). Stacks " + "on the GitHub-IP WAF + per-agent HMAC. Fail-closed: when required " + "but the secret file is missing/empty every request is rejected. Off " + "by default. See docs/rfcs/webhook-cloudflare-edge-lock.md."),
23809
23809
  chat_id: exports_external.string().regex(/^-\d+$/, 'supergroup chat_id must be a negative integer as a string (e.g. "-1001234567890")').optional().describe("Per-agent supergroup ID \u2014 overrides fleet `telegram.forum_chat_id`. " + "When set, requires `default_topic_id`. Negative integer as string. " + "Forbidden when `dm_only: true`. See docs/rfcs/supergroup-mode.md."),
23810
- default_topic_id: exports_external.number().int().positive().optional().describe("Forum topic ID this agent's automated outbounds default to when " + "no more-specific alias resolves. Required when `chat_id` is set. " + "Telegram's General topic is `id=1` at MTProto but sends omit the " + "field \u2014 the outbound wrapper strips `message_thread_id === 1` " + "on send. Forbidden when `dm_only: true`."),
23810
+ default_topic_id: exports_external.number().int().positive().optional().describe("Forum topic ID this agent's automated outbounds default to when " + "no more-specific alias resolves. Defaults to General (topic 1) when " + "`chat_id` is set and this is omitted \u2014 set it only to pin a different " + "fallback topic. " + "Telegram's General topic is `id=1` at MTProto but sends omit the " + "field \u2014 the outbound wrapper strips `message_thread_id === 1` " + "on send. Forbidden when `dm_only: true`."),
23811
23811
  topic_aliases: exports_external.record(exports_external.string(), exports_external.number().int().positive()).optional().describe("Operator-friendly names for forum topic IDs (e.g. " + "`{ general: 1, planning: 17, cron: 23, admin: 31, alerts: 41 }`). " + "Referenced from per-cron `topic:` fields and the outbound router " + "for autonomous events (boot \u2192 alerts, hostd \u2192 admin, etc.). " + "Cascades per-key through defaults \u2192 profile \u2192 agent.")
23812
23812
  }).optional().superRefine((tg, ctx) => {
23813
23813
  if (!tg)
23814
23814
  return;
23815
- if (tg.chat_id != null && tg.default_topic_id == null) {
23816
- ctx.addIssue({
23817
- code: exports_external.ZodIssueCode.custom,
23818
- message: "`channels.telegram.chat_id` requires `default_topic_id` \u2014 supergroup-mode agents need a fallback topic for unclassified outbounds.",
23819
- path: ["default_topic_id"]
23820
- });
23821
- }
23822
23815
  if (tg.default_topic_id != null && tg.chat_id == null) {
23823
23816
  ctx.addIssue({
23824
23817
  code: exports_external.ZodIssueCode.custom,
@@ -23833,6 +23826,11 @@ var init_schema = __esm(() => {
23833
23826
  path: ["topic_aliases"]
23834
23827
  });
23835
23828
  }
23829
+ }).transform((tg) => {
23830
+ if (tg && tg.chat_id != null && tg.default_topic_id == null) {
23831
+ return { ...tg, default_topic_id: 1 };
23832
+ }
23833
+ return tg;
23836
23834
  });
23837
23835
  ChannelsSchema = exports_external.object({
23838
23836
  telegram: TelegramChannelSchema
@@ -51796,10 +51794,10 @@ function sweepStaleTurnActiveMarker(stateDir, opts) {
51796
51794
  }
51797
51795
 
51798
51796
  // ../src/build-info.ts
51799
- var VERSION = "0.14.37";
51800
- var COMMIT_SHA = "90d0c420";
51801
- var COMMIT_DATE = "2026-06-02T02:10:03Z";
51802
- var LATEST_PR = 2078;
51797
+ var VERSION = "0.14.38";
51798
+ var COMMIT_SHA = "1529105b";
51799
+ var COMMIT_DATE = "2026-06-02T02:25:47Z";
51800
+ var LATEST_PR = 2079;
51803
51801
  var COMMITS_AHEAD_OF_TAG = 0;
51804
51802
 
51805
51803
  // gateway/boot-version.ts