switchroom 0.18.28 → 0.18.29

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.
@@ -20610,63 +20610,6 @@ for (const name of SHARED_FRAGMENTS) {
20610
20610
  }
20611
20611
  }
20612
20612
 
20613
- // src/agents/pane-lock.ts
20614
- var tails = new Map;
20615
-
20616
- // src/agents/inject.ts
20617
- var INJECT_COMMANDS = new Map([
20618
- ["/cost", { description: "Show session cost", expectsOutput: true, dialog: true, argsAllowed: false }],
20619
- ["/status", { description: "Show session status", expectsOutput: true, dialog: true, argsAllowed: false }],
20620
- ["/usage", { description: "Show plan quota", expectsOutput: true, dialog: true, argsAllowed: false }],
20621
- ["/hooks", { description: "List configured hooks", expectsOutput: true, dialog: true, argsAllowed: false }],
20622
- ["/memory", { description: "Open memory picker", expectsOutput: true, dialog: true, argsAllowed: false }],
20623
- ["/help", { description: "Show help / command discovery", expectsOutput: true, dialog: true, argsAllowed: false }],
20624
- ["/context", { description: "Show context window usage", expectsOutput: true, argsAllowed: false }],
20625
- ["/release-notes", { description: "Show release-notes version list", expectsOutput: true, dialog: true, argsAllowed: false }],
20626
- ["/model", { description: "Open model picker", expectsOutput: true, argsAllowed: true }],
20627
- [
20628
- "/clear",
20629
- {
20630
- description: "Clear session screen",
20631
- expectsOutput: false,
20632
- silentNote: "context cleared — fresh slate",
20633
- argsAllowed: false
20634
- }
20635
- ],
20636
- [
20637
- "/compact",
20638
- {
20639
- description: "Compact conversation history",
20640
- expectsOutput: false,
20641
- silentNote: "compaction runs silently",
20642
- argsAllowed: false
20643
- }
20644
- ]
20645
- ]);
20646
- var INJECT_ALLOWLIST = new Set(INJECT_COMMANDS.keys());
20647
- var INJECT_BLOCKED = new Map([
20648
- ["/login", { reason: "would mutate auth state" }],
20649
- ["/logout", { reason: "would terminate the agent's auth session" }],
20650
- ["/exit", { reason: "would kill the agent process" }],
20651
- ["/quit", { reason: "would kill the agent process" }],
20652
- ["/upgrade", { reason: "mutates the Claude Code installation" }],
20653
- ["/init", { reason: "generates/overwrites CLAUDE.md and runs a model turn" }],
20654
- ["/mcp", { reason: "opens an interactive MCP server management dialog" }],
20655
- ["/permissions", { reason: "opens an interactive permissions editor that mutates tool policy" }],
20656
- ["/install-github-app", { reason: "runs a network/OAuth install flow" }],
20657
- ["/add-dir", { reason: "mutates the session's working-directory set" }],
20658
- ["/terminal-setup", { reason: "mutates terminal keybinding configuration" }],
20659
- ["/privacy-settings", { reason: "opens an interactive privacy-settings dialog" }],
20660
- ["/bug", { reason: "submits a bug report over the network" }],
20661
- [
20662
- "/effort",
20663
- {
20664
- reason: "leaves a blocking confirmation modal open and wedges the pane; use the /effort command (it drives the modal), not raw inject"
20665
- }
20666
- ]
20667
- ]);
20668
- var INJECT_BLOCKLIST = new Set(INJECT_BLOCKED.keys());
20669
-
20670
20613
  // src/setup/hindsight.ts
20671
20614
  var HINDSIGHT_DEFAULT_API_PORT = 18888;
20672
20615
  var HINDSIGHT_DEFAULT_MCP_URL = `http://127.0.0.1:${HINDSIGHT_DEFAULT_API_PORT}/mcp/`;
@@ -26605,7 +26548,7 @@ import { existsSync as existsSync9, readFileSync as readFileSync7 } from "node:f
26605
26548
  import { dirname as dirname4, join as join7 } from "node:path";
26606
26549
 
26607
26550
  // src/build-info.ts
26608
- var VERSION = "0.18.28";
26551
+ var VERSION = "0.18.29";
26609
26552
 
26610
26553
  // src/cli/resolve-version.ts
26611
26554
  function readPackageVersion() {
@@ -18990,63 +18990,6 @@ for (const name of SHARED_FRAGMENTS) {
18990
18990
  }
18991
18991
  }
18992
18992
 
18993
- // src/agents/pane-lock.ts
18994
- var tails = new Map;
18995
-
18996
- // src/agents/inject.ts
18997
- var INJECT_COMMANDS = new Map([
18998
- ["/cost", { description: "Show session cost", expectsOutput: true, dialog: true, argsAllowed: false }],
18999
- ["/status", { description: "Show session status", expectsOutput: true, dialog: true, argsAllowed: false }],
19000
- ["/usage", { description: "Show plan quota", expectsOutput: true, dialog: true, argsAllowed: false }],
19001
- ["/hooks", { description: "List configured hooks", expectsOutput: true, dialog: true, argsAllowed: false }],
19002
- ["/memory", { description: "Open memory picker", expectsOutput: true, dialog: true, argsAllowed: false }],
19003
- ["/help", { description: "Show help / command discovery", expectsOutput: true, dialog: true, argsAllowed: false }],
19004
- ["/context", { description: "Show context window usage", expectsOutput: true, argsAllowed: false }],
19005
- ["/release-notes", { description: "Show release-notes version list", expectsOutput: true, dialog: true, argsAllowed: false }],
19006
- ["/model", { description: "Open model picker", expectsOutput: true, argsAllowed: true }],
19007
- [
19008
- "/clear",
19009
- {
19010
- description: "Clear session screen",
19011
- expectsOutput: false,
19012
- silentNote: "context cleared — fresh slate",
19013
- argsAllowed: false
19014
- }
19015
- ],
19016
- [
19017
- "/compact",
19018
- {
19019
- description: "Compact conversation history",
19020
- expectsOutput: false,
19021
- silentNote: "compaction runs silently",
19022
- argsAllowed: false
19023
- }
19024
- ]
19025
- ]);
19026
- var INJECT_ALLOWLIST = new Set(INJECT_COMMANDS.keys());
19027
- var INJECT_BLOCKED = new Map([
19028
- ["/login", { reason: "would mutate auth state" }],
19029
- ["/logout", { reason: "would terminate the agent's auth session" }],
19030
- ["/exit", { reason: "would kill the agent process" }],
19031
- ["/quit", { reason: "would kill the agent process" }],
19032
- ["/upgrade", { reason: "mutates the Claude Code installation" }],
19033
- ["/init", { reason: "generates/overwrites CLAUDE.md and runs a model turn" }],
19034
- ["/mcp", { reason: "opens an interactive MCP server management dialog" }],
19035
- ["/permissions", { reason: "opens an interactive permissions editor that mutates tool policy" }],
19036
- ["/install-github-app", { reason: "runs a network/OAuth install flow" }],
19037
- ["/add-dir", { reason: "mutates the session's working-directory set" }],
19038
- ["/terminal-setup", { reason: "mutates terminal keybinding configuration" }],
19039
- ["/privacy-settings", { reason: "opens an interactive privacy-settings dialog" }],
19040
- ["/bug", { reason: "submits a bug report over the network" }],
19041
- [
19042
- "/effort",
19043
- {
19044
- reason: "leaves a blocking confirmation modal open and wedges the pane; use the /effort command (it drives the modal), not raw inject"
19045
- }
19046
- ]
19047
- ]);
19048
- var INJECT_BLOCKLIST = new Set(INJECT_BLOCKED.keys());
19049
-
19050
18993
  // src/setup/hindsight.ts
19051
18994
  var HINDSIGHT_DEFAULT_API_PORT = 18888;
19052
18995
  var HINDSIGHT_DEFAULT_MCP_URL = `http://127.0.0.1:${HINDSIGHT_DEFAULT_API_PORT}/mcp/`;
@@ -18698,63 +18698,6 @@ for (const name of SHARED_FRAGMENTS) {
18698
18698
  }
18699
18699
  }
18700
18700
 
18701
- // src/agents/pane-lock.ts
18702
- var tails = new Map;
18703
-
18704
- // src/agents/inject.ts
18705
- var INJECT_COMMANDS = new Map([
18706
- ["/cost", { description: "Show session cost", expectsOutput: true, dialog: true, argsAllowed: false }],
18707
- ["/status", { description: "Show session status", expectsOutput: true, dialog: true, argsAllowed: false }],
18708
- ["/usage", { description: "Show plan quota", expectsOutput: true, dialog: true, argsAllowed: false }],
18709
- ["/hooks", { description: "List configured hooks", expectsOutput: true, dialog: true, argsAllowed: false }],
18710
- ["/memory", { description: "Open memory picker", expectsOutput: true, dialog: true, argsAllowed: false }],
18711
- ["/help", { description: "Show help / command discovery", expectsOutput: true, dialog: true, argsAllowed: false }],
18712
- ["/context", { description: "Show context window usage", expectsOutput: true, argsAllowed: false }],
18713
- ["/release-notes", { description: "Show release-notes version list", expectsOutput: true, dialog: true, argsAllowed: false }],
18714
- ["/model", { description: "Open model picker", expectsOutput: true, argsAllowed: true }],
18715
- [
18716
- "/clear",
18717
- {
18718
- description: "Clear session screen",
18719
- expectsOutput: false,
18720
- silentNote: "context cleared — fresh slate",
18721
- argsAllowed: false
18722
- }
18723
- ],
18724
- [
18725
- "/compact",
18726
- {
18727
- description: "Compact conversation history",
18728
- expectsOutput: false,
18729
- silentNote: "compaction runs silently",
18730
- argsAllowed: false
18731
- }
18732
- ]
18733
- ]);
18734
- var INJECT_ALLOWLIST = new Set(INJECT_COMMANDS.keys());
18735
- var INJECT_BLOCKED = new Map([
18736
- ["/login", { reason: "would mutate auth state" }],
18737
- ["/logout", { reason: "would terminate the agent's auth session" }],
18738
- ["/exit", { reason: "would kill the agent process" }],
18739
- ["/quit", { reason: "would kill the agent process" }],
18740
- ["/upgrade", { reason: "mutates the Claude Code installation" }],
18741
- ["/init", { reason: "generates/overwrites CLAUDE.md and runs a model turn" }],
18742
- ["/mcp", { reason: "opens an interactive MCP server management dialog" }],
18743
- ["/permissions", { reason: "opens an interactive permissions editor that mutates tool policy" }],
18744
- ["/install-github-app", { reason: "runs a network/OAuth install flow" }],
18745
- ["/add-dir", { reason: "mutates the session's working-directory set" }],
18746
- ["/terminal-setup", { reason: "mutates terminal keybinding configuration" }],
18747
- ["/privacy-settings", { reason: "opens an interactive privacy-settings dialog" }],
18748
- ["/bug", { reason: "submits a bug report over the network" }],
18749
- [
18750
- "/effort",
18751
- {
18752
- reason: "leaves a blocking confirmation modal open and wedges the pane; use the /effort command (it drives the modal), not raw inject"
18753
- }
18754
- ]
18755
- ]);
18756
- var INJECT_BLOCKLIST = new Set(INJECT_BLOCKED.keys());
18757
-
18758
18701
  // src/setup/hindsight.ts
18759
18702
  var HINDSIGHT_DEFAULT_API_PORT = 18888;
18760
18703
  var HINDSIGHT_DEFAULT_MCP_URL = `http://127.0.0.1:${HINDSIGHT_DEFAULT_API_PORT}/mcp/`;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "switchroom",
3
3
  "//version": "NOT the release version — source of truth is the git tag, resolved by scripts/build.mjs:resolveVersion() (see CLAUDE.md > Standard release process). This field is stale by design and only the Layer-4 dev/non-tag fallback for build.mjs + src/cli/resolve-version.ts; do NOT bump it expecting a release to pick it up. npm-pack tarball naming needs a real version — do that as an UNCOMMITTED pack-time bump (see release step 6), never a committed one.",
4
- "version": "0.18.28",
4
+ "version": "0.18.29",
5
5
  "description": "Run Claude Code 24/7 on your Claude Pro/Max subscription over Telegram. Open-source alternative to OpenClaw and NanoClaw — no API keys.",
6
6
  "type": "module",
7
7
  "bin": {
@@ -1148,18 +1148,19 @@ fi
1148
1148
 
1149
1149
  # --- Session model resolution (consume-once .session-model carrier) ---
1150
1150
  #
1151
- # Contract: reference/rfcs/session-model-stickiness.md §0.1 (rev 4, operator
1152
- # decision 2026-07-12SESSION-SCOPED, superseding rev 3 keep-by-default). A
1153
- # `/model` override lasts only for the current session:
1151
+ # Contract: reference/rfcs/session-model-stickiness.md §0.05 (rev 5, operator
1152
+ # decision 2026-07-16DETERMINISTIC SWITCH, superseding rev 4 §0.1's "live
1153
+ # Claude switches write no carrier"). A `/model` override lasts only for the
1154
+ # current session:
1154
1155
  #
1155
- # - Claude switches apply LIVE in-session (claude's native picker) and write
1156
- # NO carrier the explicit `claude --model {{{modelQ}}}` this script execs
1157
- # reverts them for free on the next boot.
1158
- # - sr-* / sr→Claude switches and a queued /model persisted at shutdown need
1159
- # a relaunch to take effect, so the gateway writes `{{agentDir}}/.session-model`
1160
- # (one-line JSON) IMMEDIATELY before that relaunch. This block APPLIES the
1161
- # carrier on the single boot that reads it and then DELETES it (consume-once).
1162
- # Any SUBSEQUENT restart — deploy, `/restart`, `/new`, watchdog recovery,
1156
+ # - EVERY `/model` switch Claude→Claude, Claude→sr-*, sr-*→Claude, the
1157
+ # Fable/alias button, and the picker SELECT writes `{{agentDir}}/.session-model`
1158
+ # (one-line JSON) IMMEDIATELY before the relaunch that applies it. The
1159
+ # inject-into-tmux + terminal-scrape path is RETIRED: it silently no-op'd
1160
+ # then optimistically lied to /status. This block APPLIES the carrier on the
1161
+ # single boot that reads it and then DELETES it (consume-once), and the
1162
+ # `exec claude --model <token>` below cannot silently no-op.
1163
+ # - Any SUBSEQUENT restart — deploy, `/restart`, `/new`, watchdog recovery,
1163
1164
  # crash, raw `docker restart`, host reboot — finds no carrier and boots the
1164
1165
  # configured default. That deletion is the marker distinguishing the
1165
1166
  # model-apply relaunch from every later restart.
@@ -1230,12 +1231,16 @@ if [ -f "{{agentDir}}/.session-model" ]; then
1230
1231
  # announce once.
1231
1232
  echo "session-model: configured default changed ('$_sm_cfg' → '$_EFFECTIVE_MODEL') — dropping session override '$_sm_model'" >&2
1232
1233
  printf 'The configured default model changed (`%s` → `%s`), so your session override to `%s` was not applied — the agent booted on the new configured default. Re-issue /model %s if you still want it.\n' "$_sm_cfg" "$_EFFECTIVE_MODEL" "$_sm_model" "$_sm_model" > "{{agentDir}}/.session-model-alert" 2>/dev/null || true
1233
- elif [ "${_sm_model#sr-}" != "$_sm_model" ] && [ -z "$_LITELLM_OK" ]; then
1234
- # sr-* + LiteLLM unreachable at the apply-boot: booting the override would
1235
- # 4xx against Anthropic, and consume-once means we can't retain it for a
1236
- # later relaunch. Boot the configured default and tell the operator to
1237
- # re-issue once the proxy is back.
1238
- echo "session-model: LiteLLM proxy unreachable at boot NOT applying sr-* override '$_sm_model'; booting configured default '$_EFFECTIVE_MODEL' (re-issue /model when the proxy is back)" >&2
1234
+ elif { [ "${_sm_model#sr-}" != "$_sm_model" ] || [ "$_sm_model" = fable ] || [ "$_sm_model" = claude-fable-5 ]; } && [ -z "$_LITELLM_OK" ]; then
1235
+ # Proxy-only override + LiteLLM unreachable at the apply-boot: booting the
1236
+ # override would 4xx (sr-* against Anthropic; `fable`/`claude-fable-5` is a
1237
+ # retired codename direct-to-Anthropic and ONLY resolves via the LiteLLM
1238
+ # router see the repoint case below). Consume-once means we can't retain it
1239
+ # for a later relaunch, so boot the configured default and tell the operator
1240
+ # to re-issue once the proxy is back. (Rev 5: Fable is now reachable via the
1241
+ # carrier relaunch — the old live-inject path never launched it — so it needs
1242
+ # the same down-guard as sr-*.)
1243
+ echo "session-model: LiteLLM proxy unreachable at boot — NOT applying proxy-only override '$_sm_model'; booting configured default '$_EFFECTIVE_MODEL' (re-issue /model when the proxy is back)" >&2
1239
1244
  printf 'LiteLLM proxy was unreachable at boot, so the session model override `%s` was not applied — the agent booted on its configured default `%s`. Re-issue /model %s once the proxy is reachable.\n' "$_sm_model" "$_EFFECTIVE_MODEL" "$_sm_model" > "{{agentDir}}/.session-model-alert" 2>/dev/null || true
1240
1245
  else
1241
1246
  # Apply the carrier for THIS session. No boot alert — the gateway already
@@ -1312,11 +1317,24 @@ printf '%s\n' "$_EFFECTIVE_EFFORT" > "{{agentDir}}/.active-session-effort" 2>/de
1312
1317
  # non-Claude default at the router root) and run degraded until litellm returns —
1313
1318
  # there is NO Claude default to fall back to for a persistent sr-* agent, and
1314
1319
  # that (loud-warned, non-fatal) degraded state is the correct behavior.
1320
+ #
1321
+ # Rev 5 (F2): `fable`/`claude-fable-5` ALSO needs the router root, not the
1322
+ # passthrough. The `fable` alias resolves in the CLI to `claude-fable-5`
1323
+ # (ANTHROPIC_DEFAULT_FABLE_MODEL above), a RETIRED codename that 4xxs
1324
+ # direct-to-Anthropic; it ONLY resolves via the LiteLLM router (maps
1325
+ # fable/claude-fable-5 -> anthropic/claude-fable-5, forwarding the OAuth). The
1326
+ # old Fable path was a live inject that silently no-op'd, so Fable never actually
1327
+ # launched via `--model`; routing the Fable button through the carrier relaunch
1328
+ # newly exposes this, so the repoint case must include it or a fable carrier
1329
+ # 4xxs even with LiteLLM UP. Verified against the live litellm config + router
1330
+ # /v1/models (both `fable` and `claude-fable-5` served). Other Claude sessions
1331
+ # (opus/sonnet/haiku/default) keep the passthrough — it dodges the Opus SSE
1332
+ # re-chunk stall; Fable trades that for being routable at all.
1315
1333
  case "$_EFFECTIVE_MODEL" in
1316
- sr-*)
1334
+ sr-*|fable|claude-fable-5)
1317
1335
  if [ -n "$_LITELLM_OK" ] && [ -n "$SWITCHROOM_LITELLM_BASE" ]; then
1318
1336
  export ANTHROPIC_BASE_URL="$SWITCHROOM_LITELLM_BASE"
1319
- echo "session-model: effective model '$_EFFECTIVE_MODEL' is sr-* — routing via LiteLLM model router '$SWITCHROOM_LITELLM_BASE' (off /anthropic passthrough) so it reaches OpenRouter" >&2
1337
+ echo "session-model: effective model '$_EFFECTIVE_MODEL' is proxy-only (sr-*/fable) — routing via LiteLLM model router '$SWITCHROOM_LITELLM_BASE' (off /anthropic passthrough)" >&2
1320
1338
  fi
1321
1339
  ;;
1322
1340
  esac