switchroom 0.19.33 → 0.19.35

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.
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.19.33",
4
+ "version": "0.19.35",
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": {
@@ -21972,6 +21972,7 @@ var init_schema = __esm(() => {
21972
21972
  }).describe("Per-operation LLM override. Every field optional; an unset field (or " + "an omitted op block) inherits the global `hindsight.llm.*`, which is " + "already the engine's fallback \u2014 switchroom emits only the vars set.");
21973
21973
  HindsightConfigSchema = exports_external.object({
21974
21974
  gpu: exports_external.boolean().optional().describe("Force GPU passthrough for the hindsight container on (`true`) or off " + "(`false`), overriding host autodetection in BOTH directions. Absent " + "(the default) \u2192 autodetect from the persisted host-capabilities " + "verdict (`~/.switchroom/host-capabilities.json`), which enables " + "`--gpus all` only when that file proves BOTH a GPU and the nvidia " + "container toolkit. Set `true` when that verdict is wrong or unreadable " + "and you know the host has a working toolkit \u2014 switchroom cannot verify " + "it for you, and `docker run --gpus all` hard-fails container create on " + "a host without one. Set `false` to pin the container to CPU on a GPU " + "host. This is also the declarative opt-out for the recreate-time GPU " + "drop guard (`switchroom memory setup --recreate` refuses to silently " + "turn a GPU container into a CPU one). `--gpu`/`--no-gpu` on `memory " + "setup` override this for a single run."),
21975
+ cp_access_key: exports_external.string().min(1).optional().describe("Access key for the hindsight control-plane DASHBOARD (upstream " + "`HINDSIGHT_CP_ACCESS_KEY`, port 9999). Literal or \u2014 strongly preferred " + "\u2014 a `vault:` reference such as `vault:hindsight_cp_access_key`, read " + "through the broker at container-launch time. This is the ONLY thing " + "that arms the dashboard's login: upstream's middleware short-circuits " + "to `next()` when the var is unset, so an unset key means the dashboard " + "has no authentication at all, not weak authentication. Because of that, " + "leaving it unset is FAIL-CLOSED: switchroom pins " + "`HINDSIGHT_CP_HOSTNAME=127.0.0.1` so the loginless dashboard is " + "reachable only from the host, and warns. Set this to serve the " + "dashboard on the LAN/tailnet."),
21975
21976
  llm: exports_external.object({
21976
21977
  provider: exports_external.string().min(1).optional().describe("Hindsight LLM provider (upstream `HINDSIGHT_API_LLM_PROVIDER`). " + "Defaults to `claude-code` (subscription-honest, broker-fed OAuth). " + "Any litellm-routable provider the upstream image supports is valid. " + "Serves as the GLOBAL default for every op absent a per-op override."),
21977
21978
  model: exports_external.string().min(1).optional().describe("Hindsight LLM model (upstream `HINDSIGHT_API_LLM_MODEL`). Defaults " + "to HINDSIGHT_DEFAULT_MODEL. Any model your LiteLLM proxy can route " + "is valid, e.g. `openrouter/z-ai/glm-5.2` when routing through the " + "fleet proxy. With provider=claude-code this value is ALSO exported " + "as `ANTHROPIC_MODEL` to the claude subprocess. Serves as the GLOBAL " + "default for every op absent a per-op override."),
@@ -45182,8 +45183,37 @@ function registerStartInfoCommands(bot, deps) {
45182
45183
  init_rich_send();
45183
45184
  init_format();
45184
45185
 
45186
+ // ../src/config/thinking-effort-risk.ts
45187
+ var CLAUDE_CLI_THINKING_MERGE_FIX_VERSION = "2.1.156";
45188
+ var RISKY_EFFORTS = new Set(["medium", "high", "xhigh", "max"]);
45189
+ function isRiskyThinkingEffort(effort) {
45190
+ if (!effort)
45191
+ return false;
45192
+ return RISKY_EFFORTS.has(effort.trim().toLowerCase());
45193
+ }
45194
+ function isAdaptiveThinkingOpus(model) {
45195
+ if (!model)
45196
+ return false;
45197
+ const m = model.trim().toLowerCase();
45198
+ return m.startsWith("claude-opus-4");
45199
+ }
45200
+ function assessThinkingEffortRisk(model, effort) {
45201
+ if (!isRiskyThinkingEffort(effort))
45202
+ return { risky: false };
45203
+ if (!isAdaptiveThinkingOpus(model))
45204
+ return { risky: false };
45205
+ return {
45206
+ risky: true,
45207
+ reason: `thinking_effort '${effort}' on pinned Opus 4.x model '${model}' could trigger ` + `'400 thinking/redacted_thinking blocks cannot be modified' errors when work runs ` + `through concurrent sub-agents (issue #1978). The upstream claude-CLI fix shipped in ` + `${CLAUDE_CLI_THINKING_MERGE_FIX_VERSION}, but the Opus 4.x reproduction has not been ` + `re-tested since, so pin 'thinking_effort: low' or move the agent to a current Opus model.`
45208
+ };
45209
+ }
45210
+
45185
45211
  // gateway/model-command.ts
45186
45212
  var MODEL_ALIASES = ["opus", "sonnet", "haiku", "fable", "default"];
45213
+ var CLAUDE_MODEL_ALIASES = {
45214
+ opus48: "claude-opus-4-8",
45215
+ "opus-4-8": "claude-opus-4-8"
45216
+ };
45187
45217
  var MODEL_ARG_RE = /^[A-Za-z0-9][A-Za-z0-9._/\[\]-]{0,99}$/;
45188
45218
  function isValidModelArg(arg) {
45189
45219
  return MODEL_ARG_RE.test(arg);
@@ -45217,7 +45247,7 @@ function planModelCommand(parsed, ctx) {
45217
45247
  if (parsed.kind === "show" && ctx.menuEnabled)
45218
45248
  return { kind: "menu" };
45219
45249
  if (parsed.kind === "set" && isModelCommandBusy(ctx)) {
45220
- return { kind: "queue", target: expandSrAlias(parsed.model) };
45250
+ return { kind: "queue", target: expandModelAlias(parsed.model) };
45221
45251
  }
45222
45252
  return { kind: "apply", parsed };
45223
45253
  }
@@ -45226,12 +45256,22 @@ function modelCommandReceiptLine(agent, parsed, busy) {
45226
45256
  return `telegram gateway: gw /model received agent=${agent} kind=${parsed.kind} arg=${arg} busy=${busy}`;
45227
45257
  }
45228
45258
  var PERSIST_NOTE = "_A `/model` switch relaunches the session (~30s) on the chosen model. Session-only \u2014 reverts to the configured `model:` on the next restart. `/model default` reverts now. Live scrollback is replaced by a fresh session; memory and the handoff briefing carry the context. To change the default permanently, set `model:` in switchroom.yaml._";
45259
+ function claudeAliasHints(prefix) {
45260
+ const byTarget = new Map;
45261
+ for (const [alias, target] of Object.entries(CLAUDE_MODEL_ALIASES)) {
45262
+ const spellings = byTarget.get(target) ?? [];
45263
+ spellings.push(alias);
45264
+ byTarget.set(target, spellings);
45265
+ }
45266
+ return [...byTarget].map(([target, spellings]) => `${spellings.map((a) => `\`${prefix}${a}\``).join(" \u00b7 ")} \u2192 \`${target}\``).join("; ");
45267
+ }
45229
45268
  function helpText2(deps, reason) {
45230
45269
  const srAliasExamples = Object.keys(SR_MODEL_ALIASES).map((a) => `\`${a}\``).join(" \u00b7 ");
45270
+ const claudeAliasExamples = claudeAliasHints("");
45231
45271
  const lines = [];
45232
45272
  if (reason)
45233
45273
  lines.push(`\u26a0\ufe0f ${deps.escapeHtml(reason)}`);
45234
- lines.push("**/model** \u2014 show or switch the Claude model", "`/model` \u2014 show the configured model", `\`/model <name>\` \u2014 switch the live session (${MODEL_ALIASES.map((a) => `\`${a}\``).join(" \u00b7 ")} or a full model id)`, `_OpenRouter shortcuts:_ ${srAliasExamples}`, "_Every switch relaunches the session (~30s) on the chosen model \u2014 Claude and OpenRouter (sr-\\*) alike._", PERSIST_NOTE);
45274
+ lines.push("**/model** \u2014 show or switch the Claude model", "`/model` \u2014 show the configured model", `\`/model <name>\` \u2014 switch the live session (${MODEL_ALIASES.map((a) => `\`${a}\``).join(" \u00b7 ")} or a full model id)`, `_Pinned Claude shortcuts:_ ${claudeAliasExamples}`, `_OpenRouter shortcuts:_ ${srAliasExamples}`, "_Every switch relaunches the session (~30s) on the chosen model \u2014 Claude and OpenRouter (sr-\\*) alike._", PERSIST_NOTE);
45235
45275
  return { text: lines.join(`
45236
45276
  `), html: true };
45237
45277
  }
@@ -45247,6 +45287,7 @@ async function handleModelCommand(parsed, deps) {
45247
45287
  `**Model \u2014 ${deps.escapeHtml(deps.getAgentName())}**`,
45248
45288
  `Configured: \`${deps.escapeHtml(shown)}\``,
45249
45289
  `Switch the live session: ${MODEL_ALIASES.map((a) => `\`/model ${a}\``).join(" \u00b7 ")}`,
45290
+ `Pinned Claude shortcuts: ${claudeAliasHints("/model ")}`,
45250
45291
  `OpenRouter shortcuts: ${srAliasExamples}`,
45251
45292
  "or `/model <full-model-id>`",
45252
45293
  PERSIST_NOTE
@@ -45258,7 +45299,7 @@ async function handleModelCommand(parsed, deps) {
45258
45299
  if (!isValidModelArg(parsed.model)) {
45259
45300
  return helpText2(deps, `not a valid model name: ${parsed.model}`);
45260
45301
  }
45261
- const model = expandSrAlias(parsed.model);
45302
+ const model = expandModelAlias(parsed.model);
45262
45303
  if (deps.isBusy()) {
45263
45304
  return {
45264
45305
  text: "\u23f3 The agent is mid-turn \u2014 a model switch needs an idle session. The switch was not applied.",
@@ -45285,10 +45326,25 @@ function relaunchErrorReply(deps, model, err) {
45285
45326
  return { text: `\u274c Could not schedule model switch: ${deps.escapeHtml(msg)}`, html: true };
45286
45327
  }
45287
45328
  function unvalidatedIdCaveat(deps, model) {
45288
- if (!model.trim().toLowerCase().startsWith("claude-"))
45329
+ const lower = canonicalModelToken(model).toLowerCase();
45330
+ if (!lower.startsWith("claude-"))
45331
+ return null;
45332
+ if (Object.values(CLAUDE_MODEL_ALIASES).includes(lower))
45289
45333
  return null;
45290
45334
  return `_\`${deps.escapeHtml(model)}\` can't be validated before launch \u2014 if it isn't a real Claude model id, claude will silently serve the configured fallback model instead. I check the first reply and will warn if that happens._`;
45291
45335
  }
45336
+ function thinkingEffortCaveat(deps, model) {
45337
+ let effort;
45338
+ try {
45339
+ effort = deps.getConfiguredEffort();
45340
+ } catch {
45341
+ return null;
45342
+ }
45343
+ const risk = assessThinkingEffortRisk(model, effort ?? undefined);
45344
+ if (!risk.risky || !risk.reason)
45345
+ return null;
45346
+ return `\u26a0\ufe0f _${deps.escapeHtml(risk.reason)}_`;
45347
+ }
45292
45348
  async function scheduleRelaunchReply(deps, model, reason) {
45293
45349
  try {
45294
45350
  await deps.scheduleModelRelaunch(model, reason);
@@ -45296,8 +45352,14 @@ async function scheduleRelaunchReply(deps, model, reason) {
45296
45352
  return relaunchErrorReply(deps, model, err);
45297
45353
  }
45298
45354
  const caveat = unvalidatedIdCaveat(deps, model);
45355
+ const effortRisk = thinkingEffortCaveat(deps, model);
45299
45356
  return {
45300
- text: [switchingLine(deps, model), ...caveat ? [caveat] : [], PERSIST_NOTE].join(`
45357
+ text: [
45358
+ switchingLine(deps, model),
45359
+ ...caveat ? [caveat] : [],
45360
+ ...effortRisk ? [effortRisk] : [],
45361
+ PERSIST_NOTE
45362
+ ].join(`
45301
45363
  `),
45302
45364
  html: true
45303
45365
  };
@@ -45324,8 +45386,9 @@ var MODEL_CALLBACK_HEADER = "mdl:h";
45324
45386
  var MODEL_CALLBACK_ALIAS = "mdl:alias:";
45325
45387
  var MODEL_CALLBACK_PAGE_EXTERNAL = "mdl:page:ext";
45326
45388
  var MODEL_CALLBACK_PAGE_MAIN = "mdl:page:main";
45327
- var EXTRA_CLAUDE_ALIASES = [
45328
- { alias: "fable", label: "Fable" }
45389
+ var EXTRA_CLAUDE_MENU_TOKENS = [
45390
+ { token: "fable", label: "Fable" },
45391
+ { token: "claude-opus-4-8", label: "Opus 4.8" }
45329
45392
  ];
45330
45393
  var SR_MODEL_LABELS = {
45331
45394
  "sr-gemini-2.5-pro": "Gemini 2.5 Pro",
@@ -45367,6 +45430,17 @@ var SR_MODEL_ALIASES = {
45367
45430
  function expandSrAlias(arg) {
45368
45431
  return SR_MODEL_ALIASES[arg.toLowerCase()] ?? arg;
45369
45432
  }
45433
+ function canonicalModelToken(arg) {
45434
+ const trimmed = arg.trim();
45435
+ const lower = trimmed.toLowerCase();
45436
+ return lower.startsWith("claude-") ? lower : trimmed;
45437
+ }
45438
+ function expandClaudeAlias(arg) {
45439
+ return CLAUDE_MODEL_ALIASES[arg.trim().toLowerCase()] ?? arg;
45440
+ }
45441
+ function expandModelAlias(arg) {
45442
+ return canonicalModelToken(expandSrAlias(expandClaudeAlias(arg)));
45443
+ }
45370
45444
  function srFriendlyLabel(srName) {
45371
45445
  return SR_MODEL_LABELS[srName] ?? srName.replace(/^sr-/, "").replace(/-/g, " ");
45372
45446
  }
@@ -45408,6 +45482,11 @@ function busyStaticMenu(deps, page) {
45408
45482
  callback_data: `${MODEL_CALLBACK_ALIAS}${alias}`
45409
45483
  }]);
45410
45484
  }
45485
+ for (const { token, label } of EXTRA_CLAUDE_MENU_TOKENS) {
45486
+ if (MODEL_ALIASES.includes(token.toLowerCase()))
45487
+ continue;
45488
+ rows.push([{ text: label, callback_data: `${MODEL_CALLBACK_ALIAS}${token}` }]);
45489
+ }
45411
45490
  rows.push([{ text: "Default (configured)", callback_data: `${MODEL_CALLBACK_ALIAS}default` }]);
45412
45491
  if (externalNames.length > 0) {
45413
45492
  rows.push([{ text: "\uD83C\uDF10 External models \u25b8", callback_data: MODEL_CALLBACK_PAGE_EXTERNAL }]);
@@ -45444,11 +45523,11 @@ function mainPageKeyboard(claudeOptions, hasExternal) {
45444
45523
  callback_data: modelSelectCallbackData(o.label)
45445
45524
  }]);
45446
45525
  }
45447
- for (const { alias, label } of EXTRA_CLAUDE_ALIASES) {
45448
- const already = claudeOptions.some((o) => o.label.toLowerCase() === label.toLowerCase() || o.label.toLowerCase() === alias.toLowerCase());
45526
+ for (const { token, label } of EXTRA_CLAUDE_MENU_TOKENS) {
45527
+ const already = claudeOptions.some((o) => o.label.toLowerCase() === label.toLowerCase() || o.label.toLowerCase() === token.toLowerCase());
45449
45528
  if (already)
45450
45529
  continue;
45451
- rows.push([{ text: label, callback_data: `${MODEL_CALLBACK_ALIAS}${alias}` }]);
45530
+ rows.push([{ text: label, callback_data: `${MODEL_CALLBACK_ALIAS}${token}` }]);
45452
45531
  }
45453
45532
  if (hasExternal) {
45454
45533
  rows.push([{ text: "\uD83C\uDF10 External models \u25b8", callback_data: MODEL_CALLBACK_PAGE_EXTERNAL }]);
@@ -83496,6 +83575,10 @@ Allowed: \`${deps.escapeHtml(allow)}\``, { html: true });
83496
83575
 
83497
83576
  // gateway/model-command.ts
83498
83577
  var MODEL_ALIASES2 = ["opus", "sonnet", "haiku", "fable", "default"];
83578
+ var CLAUDE_MODEL_ALIASES2 = {
83579
+ opus48: "claude-opus-4-8",
83580
+ "opus-4-8": "claude-opus-4-8"
83581
+ };
83499
83582
  var MODEL_ARG_RE2 = /^[A-Za-z0-9][A-Za-z0-9._/\[\]-]{0,99}$/;
83500
83583
  function isValidModelArg2(arg) {
83501
83584
  return MODEL_ARG_RE2.test(arg);
@@ -83631,12 +83714,22 @@ function resolveStaleAwareBusy(input) {
83631
83714
  };
83632
83715
  }
83633
83716
  var PERSIST_NOTE3 = "_A `/model` switch relaunches the session (~30s) on the chosen model. Session-only \u2014 reverts to the configured `model:` on the next restart. `/model default` reverts now. Live scrollback is replaced by a fresh session; memory and the handoff briefing carry the context. To change the default permanently, set `model:` in switchroom.yaml._";
83717
+ function claudeAliasHints2(prefix) {
83718
+ const byTarget = new Map;
83719
+ for (const [alias, target] of Object.entries(CLAUDE_MODEL_ALIASES2)) {
83720
+ const spellings = byTarget.get(target) ?? [];
83721
+ spellings.push(alias);
83722
+ byTarget.set(target, spellings);
83723
+ }
83724
+ return [...byTarget].map(([target, spellings]) => `${spellings.map((a) => `\`${prefix}${a}\``).join(" \u00b7 ")} \u2192 \`${target}\``).join("; ");
83725
+ }
83634
83726
  function helpText4(deps, reason) {
83635
83727
  const srAliasExamples = Object.keys(SR_MODEL_ALIASES2).map((a) => `\`${a}\``).join(" \u00b7 ");
83728
+ const claudeAliasExamples = claudeAliasHints2("");
83636
83729
  const lines = [];
83637
83730
  if (reason)
83638
83731
  lines.push(`\u26a0\ufe0f ${deps.escapeHtml(reason)}`);
83639
- lines.push("**/model** \u2014 show or switch the Claude model", "`/model` \u2014 show the configured model", `\`/model <name>\` \u2014 switch the live session (${MODEL_ALIASES2.map((a) => `\`${a}\``).join(" \u00b7 ")} or a full model id)`, `_OpenRouter shortcuts:_ ${srAliasExamples}`, "_Every switch relaunches the session (~30s) on the chosen model \u2014 Claude and OpenRouter (sr-\\*) alike._", PERSIST_NOTE3);
83732
+ lines.push("**/model** \u2014 show or switch the Claude model", "`/model` \u2014 show the configured model", `\`/model <name>\` \u2014 switch the live session (${MODEL_ALIASES2.map((a) => `\`${a}\``).join(" \u00b7 ")} or a full model id)`, `_Pinned Claude shortcuts:_ ${claudeAliasExamples}`, `_OpenRouter shortcuts:_ ${srAliasExamples}`, "_Every switch relaunches the session (~30s) on the chosen model \u2014 Claude and OpenRouter (sr-\\*) alike._", PERSIST_NOTE3);
83640
83733
  return { text: lines.join(`
83641
83734
  `), html: true };
83642
83735
  }
@@ -83652,6 +83745,7 @@ async function handleModelCommand2(parsed, deps) {
83652
83745
  `**Model \u2014 ${deps.escapeHtml(deps.getAgentName())}**`,
83653
83746
  `Configured: \`${deps.escapeHtml(shown)}\``,
83654
83747
  `Switch the live session: ${MODEL_ALIASES2.map((a) => `\`/model ${a}\``).join(" \u00b7 ")}`,
83748
+ `Pinned Claude shortcuts: ${claudeAliasHints2("/model ")}`,
83655
83749
  `OpenRouter shortcuts: ${srAliasExamples}`,
83656
83750
  "or `/model <full-model-id>`",
83657
83751
  PERSIST_NOTE3
@@ -83663,7 +83757,7 @@ async function handleModelCommand2(parsed, deps) {
83663
83757
  if (!isValidModelArg2(parsed.model)) {
83664
83758
  return helpText4(deps, `not a valid model name: ${parsed.model}`);
83665
83759
  }
83666
- const model = expandSrAlias2(parsed.model);
83760
+ const model = expandModelAlias2(parsed.model);
83667
83761
  if (deps.isBusy()) {
83668
83762
  return {
83669
83763
  text: "\u23f3 The agent is mid-turn \u2014 a model switch needs an idle session. The switch was not applied.",
@@ -83690,10 +83784,25 @@ function relaunchErrorReply2(deps, model, err) {
83690
83784
  return { text: `\u274c Could not schedule model switch: ${deps.escapeHtml(msg)}`, html: true };
83691
83785
  }
83692
83786
  function unvalidatedIdCaveat2(deps, model) {
83693
- if (!model.trim().toLowerCase().startsWith("claude-"))
83787
+ const lower = canonicalModelToken2(model).toLowerCase();
83788
+ if (!lower.startsWith("claude-"))
83789
+ return null;
83790
+ if (Object.values(CLAUDE_MODEL_ALIASES2).includes(lower))
83694
83791
  return null;
83695
83792
  return `_\`${deps.escapeHtml(model)}\` can't be validated before launch \u2014 if it isn't a real Claude model id, claude will silently serve the configured fallback model instead. I check the first reply and will warn if that happens._`;
83696
83793
  }
83794
+ function thinkingEffortCaveat2(deps, model) {
83795
+ let effort;
83796
+ try {
83797
+ effort = deps.getConfiguredEffort();
83798
+ } catch {
83799
+ return null;
83800
+ }
83801
+ const risk = assessThinkingEffortRisk(model, effort ?? undefined);
83802
+ if (!risk.risky || !risk.reason)
83803
+ return null;
83804
+ return `\u26a0\ufe0f _${deps.escapeHtml(risk.reason)}_`;
83805
+ }
83697
83806
  async function scheduleRelaunchReply2(deps, model, reason) {
83698
83807
  try {
83699
83808
  await deps.scheduleModelRelaunch(model, reason);
@@ -83701,8 +83810,14 @@ async function scheduleRelaunchReply2(deps, model, reason) {
83701
83810
  return relaunchErrorReply2(deps, model, err);
83702
83811
  }
83703
83812
  const caveat = unvalidatedIdCaveat2(deps, model);
83813
+ const effortRisk = thinkingEffortCaveat2(deps, model);
83704
83814
  return {
83705
- text: [switchingLine2(deps, model), ...caveat ? [caveat] : [], PERSIST_NOTE3].join(`
83815
+ text: [
83816
+ switchingLine2(deps, model),
83817
+ ...caveat ? [caveat] : [],
83818
+ ...effortRisk ? [effortRisk] : [],
83819
+ PERSIST_NOTE3
83820
+ ].join(`
83706
83821
  `),
83707
83822
  html: true
83708
83823
  };
@@ -83730,8 +83845,9 @@ var MODEL_CALLBACK_HEADER2 = "mdl:h";
83730
83845
  var MODEL_CALLBACK_ALIAS2 = "mdl:alias:";
83731
83846
  var MODEL_CALLBACK_PAGE_EXTERNAL2 = "mdl:page:ext";
83732
83847
  var MODEL_CALLBACK_PAGE_MAIN2 = "mdl:page:main";
83733
- var EXTRA_CLAUDE_ALIASES2 = [
83734
- { alias: "fable", label: "Fable" }
83848
+ var EXTRA_CLAUDE_MENU_TOKENS2 = [
83849
+ { token: "fable", label: "Fable" },
83850
+ { token: "claude-opus-4-8", label: "Opus 4.8" }
83735
83851
  ];
83736
83852
  var SR_MODEL_LABELS2 = {
83737
83853
  "sr-gemini-2.5-pro": "Gemini 2.5 Pro",
@@ -83776,11 +83892,26 @@ function isOfflineTrustedModelToken(token) {
83776
83892
  return true;
83777
83893
  if (lower in SR_MODEL_ALIASES2)
83778
83894
  return true;
83779
- return Object.values(SR_MODEL_ALIASES2).includes(lower);
83895
+ if (Object.values(SR_MODEL_ALIASES2).includes(lower))
83896
+ return true;
83897
+ if (lower in CLAUDE_MODEL_ALIASES2)
83898
+ return true;
83899
+ return Object.values(CLAUDE_MODEL_ALIASES2).includes(lower);
83780
83900
  }
83781
83901
  function expandSrAlias2(arg) {
83782
83902
  return SR_MODEL_ALIASES2[arg.toLowerCase()] ?? arg;
83783
83903
  }
83904
+ function canonicalModelToken2(arg) {
83905
+ const trimmed = arg.trim();
83906
+ const lower = trimmed.toLowerCase();
83907
+ return lower.startsWith("claude-") ? lower : trimmed;
83908
+ }
83909
+ function expandClaudeAlias2(arg) {
83910
+ return CLAUDE_MODEL_ALIASES2[arg.trim().toLowerCase()] ?? arg;
83911
+ }
83912
+ function expandModelAlias2(arg) {
83913
+ return canonicalModelToken2(expandSrAlias2(expandClaudeAlias2(arg)));
83914
+ }
83784
83915
  function srFriendlyLabel2(srName) {
83785
83916
  return SR_MODEL_LABELS2[srName] ?? srName.replace(/^sr-/, "").replace(/-/g, " ");
83786
83917
  }
@@ -83833,6 +83964,11 @@ function busyStaticMenu2(deps, page) {
83833
83964
  callback_data: `${MODEL_CALLBACK_ALIAS2}${alias}`
83834
83965
  }]);
83835
83966
  }
83967
+ for (const { token, label } of EXTRA_CLAUDE_MENU_TOKENS2) {
83968
+ if (MODEL_ALIASES2.includes(token.toLowerCase()))
83969
+ continue;
83970
+ rows.push([{ text: label, callback_data: `${MODEL_CALLBACK_ALIAS2}${token}` }]);
83971
+ }
83836
83972
  rows.push([{ text: "Default (configured)", callback_data: `${MODEL_CALLBACK_ALIAS2}default` }]);
83837
83973
  if (externalNames.length > 0) {
83838
83974
  rows.push([{ text: "\uD83C\uDF10 External models \u25b8", callback_data: MODEL_CALLBACK_PAGE_EXTERNAL2 }]);
@@ -83869,11 +84005,11 @@ function mainPageKeyboard2(claudeOptions, hasExternal) {
83869
84005
  callback_data: modelSelectCallbackData2(o.label)
83870
84006
  }]);
83871
84007
  }
83872
- for (const { alias, label } of EXTRA_CLAUDE_ALIASES2) {
83873
- const already = claudeOptions.some((o) => o.label.toLowerCase() === label.toLowerCase() || o.label.toLowerCase() === alias.toLowerCase());
84008
+ for (const { token, label } of EXTRA_CLAUDE_MENU_TOKENS2) {
84009
+ const already = claudeOptions.some((o) => o.label.toLowerCase() === label.toLowerCase() || o.label.toLowerCase() === token.toLowerCase());
83874
84010
  if (already)
83875
84011
  continue;
83876
- rows.push([{ text: label, callback_data: `${MODEL_CALLBACK_ALIAS2}${alias}` }]);
84012
+ rows.push([{ text: label, callback_data: `${MODEL_CALLBACK_ALIAS2}${token}` }]);
83877
84013
  }
83878
84014
  if (hasExternal) {
83879
84015
  rows.push([{ text: "\uD83C\uDF10 External models \u25b8", callback_data: MODEL_CALLBACK_PAGE_EXTERNAL2 }]);
@@ -83972,6 +84108,7 @@ async function handleModelMenuCallback(data, deps) {
83972
84108
  if (!isValidModelArg2(token)) {
83973
84109
  return { answer: "Invalid model name", reply: await buildModelMenu2(deps) };
83974
84110
  }
84111
+ token = expandModelAlias2(token);
83975
84112
  if (!isRecognizedSwitchToken(token)) {
83976
84113
  return { answer: "Model list changed \u2014 menu refreshed", reply: await buildModelMenu2(deps) };
83977
84114
  }
@@ -84009,9 +84146,11 @@ async function menuRelaunchOutcome(deps, token, label) {
84009
84146
  };
84010
84147
  }
84011
84148
  const friendly = isDefault ? "the configured default" : label;
84149
+ const effortRisk = isDefault ? null : thinkingEffortCaveat2(deps, token);
84012
84150
  return {
84013
84151
  answer: `Switching to ${isDefault ? "default" : label} \u2014 relaunching (~30s)`,
84014
- reply: await menuWithBannerStatic(deps, `\uD83D\uDD04 Switching session to **${deps.escapeHtml(friendly)}** \u2014 relaunching (~30s).
84152
+ reply: await menuWithBannerStatic(deps, `\uD83D\uDD04 Switching session to **${deps.escapeHtml(friendly)}** \u2014 relaunching (~30s).${effortRisk ? `
84153
+ ${effortRisk}` : ""}
84015
84154
  ${PERSIST_NOTE3}`)
84016
84155
  };
84017
84156
  }
@@ -84585,12 +84724,15 @@ function pgMib(mib) {
84585
84724
  }
84586
84725
  var HINDSIGHT_PG_EFFECTIVE_CACHE_SIZE_ENV = "SWITCHROOM_HINDSIGHT_PG_EFFECTIVE_CACHE_SIZE";
84587
84726
  var HINDSIGHT_PG_SHARED_BUFFERS_ENV = "SWITCHROOM_HINDSIGHT_PG_SHARED_BUFFERS";
84727
+ var HINDSIGHT_PG_FSYNC_ENV = "SWITCHROOM_HINDSIGHT_PG_FSYNC";
84728
+ var HINDSIGHT_PG_DEFAULT_FSYNC = "on";
84588
84729
  var HINDSIGHT_PG_DEFAULTS = [
84589
84730
  [
84590
84731
  HINDSIGHT_PG_EFFECTIVE_CACHE_SIZE_ENV,
84591
84732
  pgMib(HINDSIGHT_PG_DEFAULT_EFFECTIVE_CACHE_SIZE_MIB)
84592
84733
  ],
84593
- [HINDSIGHT_PG_SHARED_BUFFERS_ENV, pgMib(HINDSIGHT_PG_DEFAULT_SHARED_BUFFERS_MIB)]
84734
+ [HINDSIGHT_PG_SHARED_BUFFERS_ENV, pgMib(HINDSIGHT_PG_DEFAULT_SHARED_BUFFERS_MIB)],
84735
+ [HINDSIGHT_PG_FSYNC_ENV, HINDSIGHT_PG_DEFAULT_FSYNC]
84594
84736
  ];
84595
84737
  var HINDSIGHT_PG_ENV_KEYS = new Set(HINDSIGHT_PG_DEFAULTS.map(([k]) => k));
84596
84738
 
@@ -84752,6 +84894,8 @@ var HINDSIGHT_RETAIN_CLIENT_DEADLINE_S = hindsightRetainClientTimeoutSeconds() +
84752
84894
  var HINDSIGHT_HEALTHCHECK_PY = 'import urllib.request,sys; sys.exit(0 if urllib.request.urlopen("http://localhost:8888/health",timeout=4).getcode()==200 else 1)';
84753
84895
  var HINDSIGHT_HEALTHCHECK_CMD = `python3 -c '${HINDSIGHT_HEALTHCHECK_PY}'`;
84754
84896
  var DOCKER_PROBE_TIMEOUT_MS = 60 * 1000;
84897
+ var HINDSIGHT_CP_UNAUTHENTICATED_BIND_ADDR = "127.0.0.1";
84898
+ var HINDSIGHT_CP_NO_ACCESS_KEY_WARNING = "hindsight: no `hindsight.cp_access_key` configured \u2014 the control-plane " + "dashboard has NO login (the access-key middleware is inert when " + "HINDSIGHT_CP_ACCESS_KEY is unset), so it is pinned to " + `${HINDSIGHT_CP_UNAUTHENTICATED_BIND_ADDR} and will NOT be reachable from ` + "the LAN or tailnet. Set `hindsight.cp_access_key: vault:<key>` in " + "switchroom.yaml to arm the login and open it up.";
84755
84899
 
84756
84900
  // ../src/memory/hindsight.ts
84757
84901
  var DEFAULT_RETAIN_MISSION = `Extract durable facts that will still be true and useful weeks from now, once this session is forgotten.
@@ -99317,10 +99461,10 @@ function startOutboxSweep(deps) {
99317
99461
  }
99318
99462
 
99319
99463
  // ../src/build-info.ts
99320
- var VERSION2 = "0.19.33";
99321
- var COMMIT_SHA = "4ccbde6b";
99322
- var COMMIT_DATE = "2026-07-29T02:39:39Z";
99323
- var LATEST_PR = 3973;
99464
+ var VERSION2 = "0.19.35";
99465
+ var COMMIT_SHA = "76ae22ca";
99466
+ var COMMIT_DATE = "2026-07-30T01:27:21Z";
99467
+ var LATEST_PR = 4011;
99324
99468
  var COMMITS_AHEAD_OF_TAG = 0;
99325
99469
 
99326
99470
  // gateway/boot-version.ts
@@ -108833,6 +108977,7 @@ function buildModelDeps(restartCtx) {
108833
108977
  const data = switchroomExecJson(["agent", "list"]);
108834
108978
  return data?.agents?.find((a) => a.name === getMyAgentName())?.model ?? null;
108835
108979
  },
108980
+ getConfiguredEffort: () => getConfiguredEffortForPersist(),
108836
108981
  escapeHtml: escapeHtmlForTg2,
108837
108982
  preBlock,
108838
108983
  scheduleRestart: async (reason) => {
@@ -108983,7 +109128,7 @@ function persistQueuedCommandForRestart(action) {
108983
109128
  return `\u21A9\uFE0F Couldn\u2019t verify \`${escapeHtmlForTg2(action.cmd.targetLabel || action.arg)}\` as a known model without the live session \u2014 it was NOT saved. Re-issue \`/model ${escapeHtmlForTg2(action.arg)}\` once the agent is back.`;
108984
109129
  }
108985
109130
  const configured = readConfiguredDefaultModel(agentDir) ?? resolveMainModel(undefined);
108986
- writeSessionModelFile(agentDir, expandSrAlias2(action.arg), configured);
109131
+ writeSessionModelFile(agentDir, expandModelAlias2(action.arg), configured);
108987
109132
  break;
108988
109133
  }
108989
109134
  case "clear-model":
@@ -564,7 +564,7 @@ import {
564
564
  MODEL_CALLBACK_PAGE_EXTERNAL,
565
565
  MODEL_CALLBACK_PAGE_MAIN,
566
566
  srFriendlyLabel,
567
- expandSrAlias,
567
+ expandModelAlias,
568
568
  isSrModel,
569
569
  isBusyRefusalText,
570
570
  isOfflineTrustedModelToken,
@@ -16961,6 +16961,9 @@ function buildModelDeps(restartCtx?: ModelDepsRestartContext): ModelMenuDeps & M
16961
16961
  const data = switchroomExecJson<AgentListResp>(['agent', 'list'])
16962
16962
  return data?.agents?.find(a => a.name === getMyAgentName())?.model ?? null
16963
16963
  },
16964
+ // Feeds the switch-time #1978 assessment (thinkingEffortCaveat); see the
16965
+ // ModelCommandDeps.getConfiguredEffort doc for why doctor can't make it.
16966
+ getConfiguredEffort: () => getConfiguredEffortForPersist(),
16964
16967
  escapeHtml: escapeHtmlForTg,
16965
16968
  preBlock,
16966
16969
  /**
@@ -17259,7 +17262,7 @@ function persistQueuedCommandForRestart(action: ShutdownResolutionAction): strin
17259
17262
  const configured =
17260
17263
  readConfiguredDefaultModel(agentDir) ?? resolveMainModel(undefined)
17261
17264
  // Consume-once carrier: applied by the next boot, then reverts.
17262
- writeSessionModelFile(agentDir, expandSrAlias(action.arg), configured)
17265
+ writeSessionModelFile(agentDir, expandModelAlias(action.arg), configured)
17263
17266
  break
17264
17267
  }
17265
17268
  case 'clear-model':