switchroom 0.14.41 → 0.14.42

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.
@@ -49462,8 +49462,8 @@ var {
49462
49462
  } = import__.default;
49463
49463
 
49464
49464
  // src/build-info.ts
49465
- var VERSION = "0.14.41";
49466
- var COMMIT_SHA = "747ab2f1";
49465
+ var VERSION = "0.14.42";
49466
+ var COMMIT_SHA = "6da4313d";
49467
49467
 
49468
49468
  // src/cli/agent.ts
49469
49469
  init_source();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "switchroom",
3
- "version": "0.14.41",
3
+ "version": "0.14.42",
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": {
@@ -44777,6 +44777,12 @@ function resolveOutboundTopic(config, event) {
44777
44777
  return aliasToId(cfg, ADMIN_ALIAS) ?? cfg.default_topic_id;
44778
44778
  }
44779
44779
  }
44780
+ function topicForRecipient(args) {
44781
+ const { recipientChatId, resolvedTopic, supergroupChatId } = args;
44782
+ if (resolvedTopic == null || supergroupChatId == null)
44783
+ return;
44784
+ return String(recipientChatId) === String(supergroupChatId) ? resolvedTopic : undefined;
44785
+ }
44780
44786
 
44781
44787
  // ../src/agents/perf.ts
44782
44788
  import { existsSync as existsSync18, readFileSync as readFileSync14 } from "node:fs";
@@ -51865,10 +51871,10 @@ function sweepStaleTurnActiveMarker(stateDir, opts) {
51865
51871
  }
51866
51872
 
51867
51873
  // ../src/build-info.ts
51868
- var VERSION = "0.14.41";
51869
- var COMMIT_SHA = "747ab2f1";
51870
- var COMMIT_DATE = "2026-06-02T11:03:39Z";
51871
- var LATEST_PR = 2095;
51874
+ var VERSION = "0.14.42";
51875
+ var COMMIT_SHA = "6da4313d";
51876
+ var COMMIT_DATE = "2026-06-02T22:14:58Z";
51877
+ var LATEST_PR = 2097;
51872
51878
  var COMMITS_AHEAD_OF_TAG = 0;
51873
51879
 
51874
51880
  // gateway/boot-version.ts
@@ -53361,14 +53367,18 @@ function postPermissionResumeMessage(opts) {
53361
53367
  timeoutMinutes: opts.timeoutMinutes
53362
53368
  });
53363
53369
  const turn = currentTurn;
53364
- const targets = turn != null ? [{ chatId: turn.sessionChatId, threadId: turn.sessionThreadId }] : loadAccess().allowFrom.map((chatId) => ({
53365
- chatId,
53366
- threadId: resolveAgentOutboundTopic({
53370
+ const targets = turn != null ? [{ chatId: turn.sessionChatId, threadId: turn.sessionThreadId }] : (() => {
53371
+ const sg = resolveAgentSupergroupChatId();
53372
+ const topic = resolveAgentOutboundTopic({
53367
53373
  kind: "permission",
53368
53374
  turnInitiated: false,
53369
53375
  originThreadId: undefined
53370
- })
53371
- }));
53376
+ });
53377
+ return loadAccess().allowFrom.map((chatId) => ({
53378
+ chatId,
53379
+ threadId: topicForRecipient({ recipientChatId: chatId, resolvedTopic: topic, supergroupChatId: sg })
53380
+ }));
53381
+ })();
53372
53382
  for (const { chatId, threadId } of targets) {
53373
53383
  swallowingApiCall(() => bot.api.sendMessage(chatId, text, {
53374
53384
  parse_mode: "HTML",
@@ -54479,11 +54489,13 @@ var ipcServer = createIpcServer({
54479
54489
  turnInitiated: activeTurn != null,
54480
54490
  originThreadId: activeTurn?.sessionThreadId
54481
54491
  });
54492
+ const permSupergroup = resolveAgentSupergroupChatId();
54482
54493
  for (const chat_id of access.allowFrom) {
54494
+ const permThread = topicForRecipient({ recipientChatId: chat_id, resolvedTopic: permTopic, supergroupChatId: permSupergroup });
54483
54495
  bot.api.sendMessage(chat_id, text, {
54484
54496
  parse_mode: "HTML",
54485
54497
  reply_markup: keyboard,
54486
- ...permTopic != null ? { message_thread_id: permTopic } : {}
54498
+ ...permThread != null ? { message_thread_id: permThread } : {}
54487
54499
  }).catch((e) => {
54488
54500
  process.stderr.write(`telegram gateway: permission_request send to ${chat_id} failed: ${e}
54489
54501
  `);
@@ -54547,9 +54559,13 @@ var ipcServer = createIpcServer({
54547
54559
  if (operator === undefined)
54548
54560
  return null;
54549
54561
  const activeTurn = currentTurn;
54550
- const driveTopic = resolveAgentOutboundTopic({
54551
- kind: "hostd-approval",
54552
- originThreadId: activeTurn?.sessionThreadId
54562
+ const driveTopic = topicForRecipient({
54563
+ recipientChatId: operator,
54564
+ resolvedTopic: resolveAgentOutboundTopic({
54565
+ kind: "hostd-approval",
54566
+ originThreadId: activeTurn?.sessionThreadId
54567
+ }),
54568
+ supergroupChatId: resolveAgentSupergroupChatId()
54553
54569
  });
54554
54570
  return {
54555
54571
  chatId: operator,
@@ -54605,9 +54621,13 @@ var ipcServer = createIpcServer({
54605
54621
  if (operator === undefined)
54606
54622
  return null;
54607
54623
  const activeTurn = currentTurn;
54608
- const ms365Topic = resolveAgentOutboundTopic({
54609
- kind: "hostd-approval",
54610
- originThreadId: activeTurn?.sessionThreadId
54624
+ const ms365Topic = topicForRecipient({
54625
+ recipientChatId: operator,
54626
+ resolvedTopic: resolveAgentOutboundTopic({
54627
+ kind: "hostd-approval",
54628
+ originThreadId: activeTurn?.sessionThreadId
54629
+ }),
54630
+ supergroupChatId: resolveAgentSupergroupChatId()
54611
54631
  });
54612
54632
  return {
54613
54633
  chatId: operator,
@@ -54662,9 +54682,13 @@ var ipcServer = createIpcServer({
54662
54682
  if (operator === undefined)
54663
54683
  return null;
54664
54684
  const activeTurn = currentTurn;
54665
- const cfgTopic = resolveAgentOutboundTopic({
54666
- kind: "hostd-approval",
54667
- originThreadId: activeTurn?.sessionThreadId
54685
+ const cfgTopic = topicForRecipient({
54686
+ recipientChatId: operator,
54687
+ resolvedTopic: resolveAgentOutboundTopic({
54688
+ kind: "hostd-approval",
54689
+ originThreadId: activeTurn?.sessionThreadId
54690
+ }),
54691
+ supergroupChatId: resolveAgentSupergroupChatId()
54668
54692
  });
54669
54693
  return {
54670
54694
  chatId: operator,
@@ -58229,6 +58253,22 @@ function resolveAgentOutboundTopic(event) {
58229
58253
  return;
58230
58254
  }
58231
58255
  }
58256
+ function resolveAgentSupergroupChatId() {
58257
+ const agentName3 = process.env.SWITCHROOM_AGENT_NAME;
58258
+ if (!agentName3)
58259
+ return;
58260
+ try {
58261
+ const cfg = loadConfig2();
58262
+ const rawAgent = cfg.agents?.[agentName3];
58263
+ if (!rawAgent)
58264
+ return;
58265
+ const resolved = resolveAgentConfig2(cfg.defaults, cfg.profiles, rawAgent);
58266
+ const tg = resolved.channels?.telegram;
58267
+ return tg?.chat_id != null ? String(tg.chat_id) : undefined;
58268
+ } catch {
58269
+ return;
58270
+ }
58271
+ }
58232
58272
  function stampUserRestartReason(reason) {
58233
58273
  try {
58234
58274
  writeCleanShutdownMarker(GATEWAY_CLEAN_SHUTDOWN_MARKER_PATH, {
@@ -251,7 +251,7 @@ import { handleInjectCommand } from './inject-handler.js'
251
251
  import { type BannerState } from '../slot-banner.js'
252
252
  import { refreshBanner } from '../slot-banner-driver.js'
253
253
  import { loadConfig as loadSwitchroomConfig, findConfigFile as findSwitchroomConfigFile } from '../../src/config/loader.js'; import { resolveAgentConfig } from '../../src/config/merge.js'
254
- import { resolveOutboundTopic as resolveOutboundTopicHelper, type TopicRouterConfig as _OutboundRouterConfig } from '../../src/telegram/topic-router.js'
254
+ import { resolveOutboundTopic as resolveOutboundTopicHelper, topicForRecipient, type TopicRouterConfig as _OutboundRouterConfig } from '../../src/telegram/topic-router.js'
255
255
  import { readTurnUsages } from '../../src/agents/perf.js'
256
256
  import { decideProactiveCompact, initialCompactState, type CompactState } from './proactive-compact.js'
257
257
  import { nextCompactNotify, idleCompactNotifyState, type CompactNotifyState } from './compact-notify.js'
@@ -2273,14 +2273,20 @@ function postPermissionResumeMessage(opts: {
2273
2273
  const targets: Array<{ chatId: string; threadId: number | undefined }> =
2274
2274
  turn != null
2275
2275
  ? [{ chatId: turn.sessionChatId, threadId: turn.sessionThreadId }]
2276
- : loadAccess().allowFrom.map(chatId => ({
2277
- chatId,
2278
- threadId: resolveAgentOutboundTopic({
2276
+ : (() => {
2277
+ const sg = resolveAgentSupergroupChatId()
2278
+ const topic = resolveAgentOutboundTopic({
2279
2279
  kind: 'permission',
2280
2280
  turnInitiated: false,
2281
2281
  originThreadId: undefined,
2282
- }),
2283
- }))
2282
+ })
2283
+ // allowFrom is normally operator DMs — attach the topic only to a
2284
+ // recipient that owns it (the supergroup), never a DM (marko wedge).
2285
+ return loadAccess().allowFrom.map(chatId => ({
2286
+ chatId,
2287
+ threadId: topicForRecipient({ recipientChatId: chatId, resolvedTopic: topic, supergroupChatId: sg }),
2288
+ }))
2289
+ })()
2284
2290
  for (const { chatId, threadId } of targets) {
2285
2291
  // allow-raw-bot-api: wrapped in swallowingApiCall (retry policy); thread-aware send
2286
2292
  void swallowingApiCall(
@@ -4664,16 +4670,20 @@ const ipcServer: IpcServer = createIpcServer({
4664
4670
  turnInitiated: activeTurn != null,
4665
4671
  originThreadId: activeTurn?.sessionThreadId,
4666
4672
  })
4673
+ const permSupergroup = resolveAgentSupergroupChatId()
4667
4674
  for (const chat_id of access.allowFrom) {
4668
4675
  // parse_mode=HTML pairs with formatPermissionCardBody (#1790)
4669
4676
  // so the <b>/<i> tags render as formatting.
4670
- // PR4b emitter sweep opts now optionally carries
4671
- // message_thread_id when supergroup mode is on.
4677
+ // The resolved topic is valid only in the agent's supergroup — attach
4678
+ // it ONLY when this recipient IS that supergroup. allowFrom DMs get the
4679
+ // card thread-less; attaching a topic to a DM yields 400 "message thread
4680
+ // not found" → card never arrives → auto-deny → wedge (marko 2026-06-02).
4681
+ const permThread = topicForRecipient({ recipientChatId: chat_id, resolvedTopic: permTopic, supergroupChatId: permSupergroup })
4672
4682
  // allow-raw-bot-api: permission-request keyboard fan-out; topic-aware opts
4673
4683
  void bot.api.sendMessage(chat_id, text, {
4674
4684
  parse_mode: 'HTML',
4675
4685
  reply_markup: keyboard,
4676
- ...(permTopic != null ? { message_thread_id: permTopic } : {}),
4686
+ ...(permThread != null ? { message_thread_id: permThread } : {}),
4677
4687
  }).catch(e => {
4678
4688
  process.stderr.write(`telegram gateway: permission_request send to ${chat_id} failed: ${e}\n`)
4679
4689
  })
@@ -4811,9 +4821,15 @@ const ipcServer: IpcServer = createIpcServer({
4811
4821
  // topic. Drive approval cards follow the originating turn
4812
4822
  // (operator-initiated tool call), admin alias fallback.
4813
4823
  const activeTurn = currentTurn
4814
- const driveTopic = resolveAgentOutboundTopic({
4815
- kind: 'hostd-approval',
4816
- originThreadId: activeTurn?.sessionThreadId,
4824
+ // Attach the topic only when `operator` IS the agent's supergroup —
4825
+ // operator DMs have no topics (marko brevo wedge, 2026-06-02).
4826
+ const driveTopic = topicForRecipient({
4827
+ recipientChatId: operator,
4828
+ resolvedTopic: resolveAgentOutboundTopic({
4829
+ kind: 'hostd-approval',
4830
+ originThreadId: activeTurn?.sessionThreadId,
4831
+ }),
4832
+ supergroupChatId: resolveAgentSupergroupChatId(),
4817
4833
  })
4818
4834
  return {
4819
4835
  chatId: operator,
@@ -4884,9 +4900,14 @@ const ipcServer: IpcServer = createIpcServer({
4884
4900
  // alias fallback for background cases. Same shape as hostd /
4885
4901
  // drive approvals below.
4886
4902
  const activeTurn = currentTurn
4887
- const ms365Topic = resolveAgentOutboundTopic({
4888
- kind: 'hostd-approval',
4889
- originThreadId: activeTurn?.sessionThreadId,
4903
+ // Topic valid only in the agent's supergroup — never on the operator DM.
4904
+ const ms365Topic = topicForRecipient({
4905
+ recipientChatId: operator,
4906
+ resolvedTopic: resolveAgentOutboundTopic({
4907
+ kind: 'hostd-approval',
4908
+ originThreadId: activeTurn?.sessionThreadId,
4909
+ }),
4910
+ supergroupChatId: resolveAgentSupergroupChatId(),
4890
4911
  })
4891
4912
  return {
4892
4913
  chatId: operator,
@@ -4963,9 +4984,14 @@ const ipcServer: IpcServer = createIpcServer({
4963
4984
  // returns undefined for non-supergroup agents → behavior
4964
4985
  // unchanged.
4965
4986
  const activeTurn = currentTurn
4966
- const cfgTopic = resolveAgentOutboundTopic({
4967
- kind: 'hostd-approval',
4968
- originThreadId: activeTurn?.sessionThreadId,
4987
+ // Topic valid only in the agent's supergroup — never on the operator DM.
4988
+ const cfgTopic = topicForRecipient({
4989
+ recipientChatId: operator,
4990
+ resolvedTopic: resolveAgentOutboundTopic({
4991
+ kind: 'hostd-approval',
4992
+ originThreadId: activeTurn?.sessionThreadId,
4993
+ }),
4994
+ supergroupChatId: resolveAgentSupergroupChatId(),
4969
4995
  })
4970
4996
  return {
4971
4997
  chatId: operator,
@@ -11067,6 +11093,30 @@ function resolveAgentOutboundTopic(
11067
11093
  }
11068
11094
  }
11069
11095
 
11096
+ /**
11097
+ * The agent's supergroup chat id (`channels.telegram.chat_id`) when it is in
11098
+ * supergroup-owned mode, else undefined. A forum topic id resolved by
11099
+ * {@link resolveAgentOutboundTopic} is valid ONLY in this chat — used by
11100
+ * {@link topicForRecipient} to decide whether an approval/permission card sent
11101
+ * to a given recipient (operator DMs vs the supergroup itself) may carry a
11102
+ * `message_thread_id`. Attaching a topic to a DM is the marko brevo wedge
11103
+ * (2026-06-02): the card fails with "message thread not found" and auto-denies.
11104
+ */
11105
+ function resolveAgentSupergroupChatId(): string | undefined {
11106
+ const agentName = process.env.SWITCHROOM_AGENT_NAME
11107
+ if (!agentName) return undefined
11108
+ try {
11109
+ const cfg = loadSwitchroomConfig()
11110
+ const rawAgent = cfg.agents?.[agentName]
11111
+ if (!rawAgent) return undefined
11112
+ const resolved = resolveAgentConfig(cfg.defaults, cfg.profiles, rawAgent)
11113
+ const tg = resolved.channels?.telegram as { chat_id?: string | number } | undefined
11114
+ return tg?.chat_id != null ? String(tg.chat_id) : undefined
11115
+ } catch {
11116
+ return undefined
11117
+ }
11118
+ }
11119
+
11070
11120
  /**
11071
11121
  * Stamp a user-facing restart reason into the clean-shutdown marker
11072
11122
  * (same file the SIGTERM handler writes to and the next session greeting