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/dist/agent-scheduler/index.js +1 -0
- package/dist/auth-broker/index.js +11088 -10989
- package/dist/cli/notion-write-pretool.mjs +1 -0
- package/dist/cli/switchroom.js +10947 -10361
- package/dist/host-control/main.js +9512 -9410
- package/dist/vault/approvals/kernel-server.js +454 -423
- package/dist/vault/broker/server.js +1658 -1636
- package/package.json +1 -1
- package/telegram-plugin/dist/gateway/gateway.js +172 -27
- package/telegram-plugin/gateway/gateway.ts +5 -2
- package/telegram-plugin/gateway/model-command.ts +245 -23
- package/telegram-plugin/tests/model-command.test.ts +415 -4
- package/vendor/hindsight-memory/scripts/subagent_retain.py +103 -7
- package/vendor/hindsight-memory/scripts/tests/test_subagent_retain_learnings.py +377 -0
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.
|
|
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:
|
|
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 =
|
|
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
|
-
|
|
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: [
|
|
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
|
|
45328
|
-
{
|
|
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 {
|
|
45448
|
-
const already = claudeOptions.some((o) => o.label.toLowerCase() === label.toLowerCase() || o.label.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}${
|
|
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 =
|
|
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
|
-
|
|
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: [
|
|
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
|
|
83734
|
-
{
|
|
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
|
-
|
|
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 {
|
|
83873
|
-
const already = claudeOptions.some((o) => o.label.toLowerCase() === label.toLowerCase() || o.label.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}${
|
|
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.
|
|
99321
|
-
var COMMIT_SHA = "
|
|
99322
|
-
var COMMIT_DATE = "2026-07-
|
|
99323
|
-
var LATEST_PR =
|
|
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,
|
|
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
|
-
|
|
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,
|
|
17265
|
+
writeSessionModelFile(agentDir, expandModelAlias(action.arg), configured)
|
|
17263
17266
|
break
|
|
17264
17267
|
}
|
|
17265
17268
|
case 'clear-model':
|