switchroom 0.16.13 → 0.16.14

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.
@@ -51674,8 +51674,8 @@ import { existsSync, readFileSync } from "node:fs";
51674
51674
  import { dirname, join } from "node:path";
51675
51675
 
51676
51676
  // src/build-info.ts
51677
- var VERSION = "0.16.13";
51678
- var COMMIT_SHA = "d4f3ac38";
51677
+ var VERSION = "0.16.14";
51678
+ var COMMIT_SHA = "6daa5e37";
51679
51679
 
51680
51680
  // src/cli/resolve-version.ts
51681
51681
  function readPackageVersion() {
@@ -22587,7 +22587,7 @@ import { existsSync as existsSync6, readFileSync as readFileSync4 } from "node:f
22587
22587
  import { dirname as dirname4, join as join2 } from "node:path";
22588
22588
 
22589
22589
  // src/build-info.ts
22590
- var VERSION = "0.16.13";
22590
+ var VERSION = "0.16.14";
22591
22591
 
22592
22592
  // src/cli/resolve-version.ts
22593
22593
  function readPackageVersion() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "switchroom",
3
- "version": "0.16.13",
3
+ "version": "0.16.14",
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": {
@@ -56064,11 +56064,11 @@ function readTurnActiveMarkerAgeMs(stateDir, now) {
56064
56064
  }
56065
56065
 
56066
56066
  // ../src/build-info.ts
56067
- var VERSION = "0.16.13";
56068
- var COMMIT_SHA = "d4f3ac38";
56069
- var COMMIT_DATE = "2026-06-28T21:45:50+10:00";
56070
- var LATEST_PR = null;
56071
- var COMMITS_AHEAD_OF_TAG = 2;
56067
+ var VERSION = "0.16.14";
56068
+ var COMMIT_SHA = "6daa5e37";
56069
+ var COMMIT_DATE = "2026-06-28T19:14:51Z";
56070
+ var LATEST_PR = 2639;
56071
+ var COMMITS_AHEAD_OF_TAG = 0;
56072
56072
 
56073
56073
  // gateway/boot-version.ts
56074
56074
  function formatRelativeAgo(iso) {
@@ -68285,13 +68285,19 @@ bot.on("callback_query:data", async (ctx) => {
68285
68285
  return;
68286
68286
  }
68287
68287
  await ctx.answerCallbackQuery({ text: "Switching\u2026" }).catch(() => {});
68288
+ let didInterimSrEdit = false;
68289
+ if (data.startsWith(MODEL_CALLBACK_SR)) {
68290
+ const srLabel = escapeHtmlForTg(srFriendlyLabel(data.slice(MODEL_CALLBACK_SR.length)));
68291
+ await ctx.editMessageText(`\u23F3 Switching session to <b>${srLabel}</b>\u2026`, { parse_mode: "HTML", reply_markup: { inline_keyboard: [] } }).catch(() => {});
68292
+ didInterimSrEdit = true;
68293
+ }
68288
68294
  try {
68289
68295
  const prevSessionModel = activeSessionModelOverride;
68290
68296
  const outcome = await handleModelMenuCallback(data, modelDeps);
68291
68297
  if (outcome.selectedModel) {
68292
68298
  activeSessionModelOverride = outcome.selectedModel;
68293
68299
  }
68294
- if (outcome.toastOnly)
68300
+ if (outcome.toastOnly && !didInterimSrEdit)
68295
68301
  return;
68296
68302
  if (outcome.selectedModel && isSrToClaudeTransition(prevSessionModel, outcome.selectedModel)) {
68297
68303
  const agentName3 = getMyAgentName();
@@ -278,6 +278,8 @@ import {
278
278
  isSrToClaudeTransition,
279
279
  MODEL_CALLBACK_PREFIX,
280
280
  MODEL_CALLBACK_HEADER,
281
+ MODEL_CALLBACK_SR,
282
+ srFriendlyLabel,
281
283
  type ModelMenuDeps,
282
284
  type ModelCommandDeps,
283
285
  type ModelMenuReply,
@@ -20849,6 +20851,23 @@ bot.on('callback_query:data', async ctx => {
20849
20851
  return
20850
20852
  }
20851
20853
  await ctx.answerCallbackQuery({ text: 'Switching…' }).catch(() => {})
20854
+ // sr-* inject waits for claude to respond (can take 10-30s). Edit the
20855
+ // menu immediately to show a "working on it" state so the operator isn't
20856
+ // left looking at a stale menu with no feedback. The final edit (✅/❌)
20857
+ // replaces this once the inject returns.
20858
+ // NOTE: this await yields the event loop, so a new inbound turn could
20859
+ // start between here and handleModelMenuCallback's inner isBusy() check.
20860
+ // We track whether we applied the interim edit so we can skip the
20861
+ // toastOnly short-circuit if we did — a toastOnly return after the interim
20862
+ // edit would leave the menu stuck button-less.
20863
+ let didInterimSrEdit = false
20864
+ if (data.startsWith(MODEL_CALLBACK_SR)) {
20865
+ const srLabel = escapeHtmlForTg(srFriendlyLabel(data.slice(MODEL_CALLBACK_SR.length)))
20866
+ await ctx
20867
+ .editMessageText(`⏳ Switching session to <b>${srLabel}</b>…`, { parse_mode: 'HTML', reply_markup: { inline_keyboard: [] } })
20868
+ .catch(() => {})
20869
+ didInterimSrEdit = true
20870
+ }
20852
20871
  try {
20853
20872
  const prevSessionModel = activeSessionModelOverride
20854
20873
  const outcome = await handleModelMenuCallback(data, modelDeps)
@@ -20858,9 +20877,11 @@ bot.on('callback_query:data', async ctx => {
20858
20877
  if (outcome.selectedModel) {
20859
20878
  activeSessionModelOverride = outcome.selectedModel
20860
20879
  }
20861
- // toastOnly: a no-op outcome that should not disturb the menu (defence
20862
- // in depth the isBusy() short-circuit above is the live path).
20863
- if (outcome.toastOnly) return
20880
+ // toastOnly: leave the menu untouched but only if we haven't already
20881
+ // cleared its buttons with the interim sr-* edit. If we have, fall
20882
+ // through to the final edit so the message is recovered (busyReply or
20883
+ // the full menu) rather than left permanently button-less.
20884
+ if (outcome.toastOnly && !didInterimSrEdit) return
20864
20885
 
20865
20886
  // sr-* → Claude transition via the model menu: trigger a graceful restart.
20866
20887
  // Switching FROM an sr-* (LiteLLM/OpenRouter) model BACK to a Claude model
@@ -345,7 +345,7 @@ export function expandSrAlias(arg: string): string {
345
345
  return SR_MODEL_ALIASES[arg.toLowerCase()] ?? arg
346
346
  }
347
347
 
348
- function srFriendlyLabel(srName: string): string {
348
+ export function srFriendlyLabel(srName: string): string {
349
349
  return SR_MODEL_LABELS[srName] ?? srName.replace(/^sr-/, '').replace(/-/g, ' ')
350
350
  }
351
351
 
@@ -140,11 +140,11 @@ describe("uat: /model sr-* LiteLLM routing — section headers + session switch
140
140
  const sc = await spinUp({ agent: AGENT });
141
141
  try {
142
142
  await sc.sendDM("/model");
143
- // 60s — test 2 runs after test 1's restore restart, which takes ~15s.
144
- // If the restart is still finishing when /model lands, it may be queued.
143
+ // 90s — test 2 runs after test 1's restore restart. The model-switch
144
+ // restart can take 30–60s to fully boot; 90s gives comfortable margin.
145
145
  const menu = await sc.expectMessage(/Default \(new sessions\):/i, {
146
146
  from: "bot",
147
- timeout: 60_000,
147
+ timeout: 90_000,
148
148
  });
149
149
  const kb = await sc.driver.getKeyboard(sc.botUserId, menu.messageId);
150
150
  const flat = (kb ?? []).flat();