switchroom 0.16.10 → 0.16.11
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/cli/switchroom.js +2 -2
- package/dist/host-control/main.js +1 -1
- package/package.json +1 -1
- package/telegram-plugin/dist/gateway/gateway.js +56 -39
- package/telegram-plugin/gateway/gateway.ts +31 -30
- package/telegram-plugin/gateway/model-command.ts +32 -4
- package/telegram-plugin/tests/model-command.test.ts +55 -1
- package/telegram-plugin/uat/scenarios/jtbd-model-litellm-sr-dm.test.ts +3 -1
package/dist/cli/switchroom.js
CHANGED
|
@@ -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.
|
|
51678
|
-
var COMMIT_SHA = "
|
|
51677
|
+
var VERSION = "0.16.11";
|
|
51678
|
+
var COMMIT_SHA = "7846a496";
|
|
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.
|
|
22590
|
+
var VERSION = "0.16.11";
|
|
22591
22591
|
|
|
22592
22592
|
// src/cli/resolve-version.ts
|
|
22593
22593
|
function readPackageVersion() {
|
package/package.json
CHANGED
|
@@ -46034,10 +46034,11 @@ function parseModelCommand(text) {
|
|
|
46034
46034
|
}
|
|
46035
46035
|
var PERSIST_NOTE = "<i>Session-only \u2014 lasts until restart. To persist, set <code>model:</code> in switchroom.yaml and restart.</i>";
|
|
46036
46036
|
function helpText2(deps, reason) {
|
|
46037
|
+
const srAliasExamples = Object.keys(SR_MODEL_ALIASES).map((a) => `<code>${a}</code>`).join(" \u00b7 ");
|
|
46037
46038
|
const lines = [];
|
|
46038
46039
|
if (reason)
|
|
46039
46040
|
lines.push(`\u26a0\ufe0f ${deps.escapeHtml(reason)}`);
|
|
46040
|
-
lines.push("<b>/model</b> \u2014 show or switch the Claude model", "<code>/model</code> \u2014 show the configured model", `<code>/model <name></code> \u2014 switch the live session (${MODEL_ALIASES.map((a) => `<code>${a}</code>`).join(" \u00b7 ")} or a full model id)`, PERSIST_NOTE);
|
|
46041
|
+
lines.push("<b>/model</b> \u2014 show or switch the Claude model", "<code>/model</code> \u2014 show the configured model", `<code>/model <name></code> \u2014 switch the live session (${MODEL_ALIASES.map((a) => `<code>${a}</code>`).join(" \u00b7 ")} or a full model id)`, `<i>OpenRouter shortcuts:</i> ${srAliasExamples}`, PERSIST_NOTE);
|
|
46041
46042
|
return { text: lines.join(`
|
|
46042
46043
|
`), html: true };
|
|
46043
46044
|
}
|
|
@@ -46047,11 +46048,13 @@ async function handleModelCommand(parsed, deps) {
|
|
|
46047
46048
|
if (parsed.kind === "show") {
|
|
46048
46049
|
const configured = deps.getConfiguredModel();
|
|
46049
46050
|
const shown = configured && configured.length > 0 ? configured : "default";
|
|
46051
|
+
const srAliasExamples = Object.keys(SR_MODEL_ALIASES).map((a) => `<code>/model ${a}</code>`).join(" \u00b7 ");
|
|
46050
46052
|
return {
|
|
46051
46053
|
text: [
|
|
46052
46054
|
`<b>Model \u2014 ${deps.escapeHtml(deps.getAgentName())}</b>`,
|
|
46053
46055
|
`Configured: <code>${deps.escapeHtml(shown)}</code>`,
|
|
46054
46056
|
`Switch the live session: ${MODEL_ALIASES.map((a) => `<code>/model ${a}</code>`).join(" \u00b7 ")}`,
|
|
46057
|
+
`OpenRouter shortcuts: ${srAliasExamples}`,
|
|
46055
46058
|
"or <code>/model <full-model-id></code>",
|
|
46056
46059
|
PERSIST_NOTE
|
|
46057
46060
|
].join(`
|
|
@@ -46062,10 +46065,11 @@ async function handleModelCommand(parsed, deps) {
|
|
|
46062
46065
|
if (!isValidModelArg(parsed.model)) {
|
|
46063
46066
|
return helpText2(deps, `not a valid model name: ${parsed.model}`);
|
|
46064
46067
|
}
|
|
46068
|
+
const model = expandSrAlias(parsed.model);
|
|
46065
46069
|
const currentSession = deps.getActiveSessionModel();
|
|
46066
|
-
if (currentSession !== null && isSrModel(currentSession) && isClaudeModel(
|
|
46070
|
+
if (currentSession !== null && isSrModel(currentSession) && isClaudeModel(model)) {
|
|
46067
46071
|
try {
|
|
46068
|
-
await deps.scheduleRestart(`user: /model ${
|
|
46072
|
+
await deps.scheduleRestart(`user: /model ${model} (sr-to-claude restart)`);
|
|
46069
46073
|
} catch (err) {
|
|
46070
46074
|
const msg = err instanceof Error ? err.message : String(err);
|
|
46071
46075
|
return {
|
|
@@ -46082,10 +46086,10 @@ async function handleModelCommand(parsed, deps) {
|
|
|
46082
46086
|
html: true
|
|
46083
46087
|
};
|
|
46084
46088
|
}
|
|
46085
|
-
const verbHtml = `<code>/model ${deps.escapeHtml(
|
|
46089
|
+
const verbHtml = `<code>/model ${deps.escapeHtml(model)}</code>`;
|
|
46086
46090
|
let result;
|
|
46087
46091
|
try {
|
|
46088
|
-
result = await deps.inject(deps.getAgentName(), `/model ${
|
|
46092
|
+
result = await deps.inject(deps.getAgentName(), `/model ${model}`);
|
|
46089
46093
|
} catch (err) {
|
|
46090
46094
|
const msg = err instanceof Error ? err.message : String(err);
|
|
46091
46095
|
return {
|
|
@@ -46136,8 +46140,20 @@ var SR_MODEL_LABELS = {
|
|
|
46136
46140
|
"sr-gemini-2.5-flash": "Gemini 2.5 Flash",
|
|
46137
46141
|
"sr-deepseek-r1": "DeepSeek R1",
|
|
46138
46142
|
"sr-deepseek-v3": "DeepSeek V3",
|
|
46139
|
-
"sr-glm-5": "GLM-5"
|
|
46143
|
+
"sr-glm-5": "GLM-5",
|
|
46144
|
+
"sr-codex-5.5": "Codex 5.5"
|
|
46140
46145
|
};
|
|
46146
|
+
var SR_MODEL_ALIASES = {
|
|
46147
|
+
flash: "sr-gemini-2.5-flash",
|
|
46148
|
+
gemini: "sr-gemini-2.5-pro",
|
|
46149
|
+
deepseek: "sr-deepseek-v3",
|
|
46150
|
+
r1: "sr-deepseek-r1",
|
|
46151
|
+
glm: "sr-glm-5",
|
|
46152
|
+
codex: "sr-codex-5.5"
|
|
46153
|
+
};
|
|
46154
|
+
function expandSrAlias(arg) {
|
|
46155
|
+
return SR_MODEL_ALIASES[arg.toLowerCase()] ?? arg;
|
|
46156
|
+
}
|
|
46141
46157
|
function srFriendlyLabel(srName) {
|
|
46142
46158
|
return SR_MODEL_LABELS[srName] ?? srName.replace(/^sr-/, "").replace(/-/g, " ");
|
|
46143
46159
|
}
|
|
@@ -56047,10 +56063,10 @@ function readTurnActiveMarkerAgeMs(stateDir, now) {
|
|
|
56047
56063
|
}
|
|
56048
56064
|
|
|
56049
56065
|
// ../src/build-info.ts
|
|
56050
|
-
var VERSION = "0.16.
|
|
56051
|
-
var COMMIT_SHA = "
|
|
56052
|
-
var COMMIT_DATE = "2026-06-
|
|
56053
|
-
var LATEST_PR =
|
|
56066
|
+
var VERSION = "0.16.11";
|
|
56067
|
+
var COMMIT_SHA = "7846a496";
|
|
56068
|
+
var COMMIT_DATE = "2026-06-28T09:51:30Z";
|
|
56069
|
+
var LATEST_PR = 2630;
|
|
56054
56070
|
var COMMITS_AHEAD_OF_TAG = 0;
|
|
56055
56071
|
|
|
56056
56072
|
// gateway/boot-version.ts
|
|
@@ -57709,12 +57725,13 @@ function loadAccess() {
|
|
|
57709
57725
|
return BOOT_ACCESS ?? readAccessFile();
|
|
57710
57726
|
}
|
|
57711
57727
|
function assertAllowedChat(chat_id) {
|
|
57728
|
+
const id = String(chat_id);
|
|
57712
57729
|
const access = loadAccess();
|
|
57713
|
-
if (access.allowFrom.includes(
|
|
57730
|
+
if (access.allowFrom.includes(id))
|
|
57714
57731
|
return;
|
|
57715
|
-
if (
|
|
57732
|
+
if (id in access.groups)
|
|
57716
57733
|
return;
|
|
57717
|
-
throw new Error(`chat ${
|
|
57734
|
+
throw new Error(`chat ${id} is not allowlisted \u2014 add via /telegram:access`);
|
|
57718
57735
|
}
|
|
57719
57736
|
function saveAccess(a) {
|
|
57720
57737
|
if (STATIC)
|
|
@@ -60688,7 +60705,7 @@ async function executeToolCall(tool, args) {
|
|
|
60688
60705
|
}
|
|
60689
60706
|
}
|
|
60690
60707
|
async function executeSendChecklist(args) {
|
|
60691
|
-
const chat_id = args.chat_id;
|
|
60708
|
+
const chat_id = String(args.chat_id ?? "");
|
|
60692
60709
|
if (!chat_id)
|
|
60693
60710
|
throw new Error("send_checklist: chat_id is required");
|
|
60694
60711
|
const title = args.title;
|
|
@@ -60754,7 +60771,7 @@ async function executeLinearAgentSetup(args) {
|
|
|
60754
60771
|
return runLinearAgentSetup(args);
|
|
60755
60772
|
}
|
|
60756
60773
|
async function executeUpdateChecklist(args) {
|
|
60757
|
-
const chat_id = args.chat_id;
|
|
60774
|
+
const chat_id = String(args.chat_id ?? "");
|
|
60758
60775
|
if (!chat_id)
|
|
60759
60776
|
throw new Error("update_checklist: chat_id is required");
|
|
60760
60777
|
const message_id = args.message_id;
|
|
@@ -60778,7 +60795,7 @@ function redactOutboundText(text2, site) {
|
|
|
60778
60795
|
}
|
|
60779
60796
|
async function executeReply(args) {
|
|
60780
60797
|
const turn = currentTurn;
|
|
60781
|
-
const chat_id = args.chat_id;
|
|
60798
|
+
const chat_id = String(args.chat_id ?? "");
|
|
60782
60799
|
if (!chat_id)
|
|
60783
60800
|
throw new Error("reply: chat_id is required");
|
|
60784
60801
|
const rawText = args.text;
|
|
@@ -61315,14 +61332,14 @@ async function executeStreamReply(args) {
|
|
|
61315
61332
|
args.text = scrub.scrubbed;
|
|
61316
61333
|
emitRuntimeMetric({
|
|
61317
61334
|
kind: "voice_scrub_applied",
|
|
61318
|
-
chatKey: statusKey(args.chat_id, args.message_thread_id != null ? Number(args.message_thread_id) : undefined),
|
|
61335
|
+
chatKey: statusKey(String(args.chat_id ?? ""), args.message_thread_id != null ? Number(args.message_thread_id) : undefined),
|
|
61319
61336
|
replaced: scrub.replaced,
|
|
61320
61337
|
site: "stream_reply"
|
|
61321
61338
|
});
|
|
61322
61339
|
}
|
|
61323
61340
|
}
|
|
61324
61341
|
if (args.done === true) {
|
|
61325
|
-
const sChatId = args.chat_id;
|
|
61342
|
+
const sChatId = String(args.chat_id ?? "");
|
|
61326
61343
|
const sThreadId = args.message_thread_id != null ? Number(args.message_thread_id) : undefined;
|
|
61327
61344
|
const sText = args.text;
|
|
61328
61345
|
const dup = outboundDedup.check(sChatId, sThreadId, sText, Date.now(), currentTurn?.registryKey ?? null);
|
|
@@ -61333,7 +61350,7 @@ async function executeStreamReply(args) {
|
|
|
61333
61350
|
}
|
|
61334
61351
|
}
|
|
61335
61352
|
const access = loadAccess();
|
|
61336
|
-
const streamChatId = args.chat_id;
|
|
61353
|
+
const streamChatId = String(args.chat_id ?? "");
|
|
61337
61354
|
const streamIsPrivate = isDmChatId(streamChatId);
|
|
61338
61355
|
const streamIsForumTopic = args.message_thread_id != null && args.message_thread_id !== "";
|
|
61339
61356
|
let streamReplyMarkup;
|
|
@@ -61433,18 +61450,18 @@ async function executeStreamReply(args) {
|
|
|
61433
61450
|
});
|
|
61434
61451
|
if (result.messageId != null) {
|
|
61435
61452
|
try {
|
|
61436
|
-
progressDriver?.recordOutboundDelivered(args.chat_id, args.message_thread_id);
|
|
61453
|
+
progressDriver?.recordOutboundDelivered(String(args.chat_id ?? ""), args.message_thread_id);
|
|
61437
61454
|
} catch {}
|
|
61438
61455
|
try {
|
|
61439
61456
|
const threadIdNum2 = args.message_thread_id != null ? Number(args.message_thread_id) : undefined;
|
|
61440
|
-
noteSignal(statusKey(args.chat_id, threadIdNum2), Date.now());
|
|
61457
|
+
noteSignal(statusKey(String(args.chat_id ?? ""), threadIdNum2), Date.now());
|
|
61441
61458
|
} catch {}
|
|
61442
61459
|
}
|
|
61443
61460
|
if (args.done === true && result.messageId != null && streamButtonMeta != null && streamButtonMeta.size > 0) {
|
|
61444
|
-
rememberAgentButtonMeta(args.chat_id, result.messageId, streamButtonMeta);
|
|
61461
|
+
rememberAgentButtonMeta(String(args.chat_id ?? ""), result.messageId, streamButtonMeta);
|
|
61445
61462
|
}
|
|
61446
61463
|
if (args.done === true && result.messageId != null) {
|
|
61447
|
-
const sChatId = args.chat_id;
|
|
61464
|
+
const sChatId = String(args.chat_id ?? "");
|
|
61448
61465
|
const sThreadId = args.message_thread_id != null ? Number(args.message_thread_id) : undefined;
|
|
61449
61466
|
outboundDedup.record(sChatId, sThreadId, args.text, Date.now(), currentTurn?.registryKey ?? null);
|
|
61450
61467
|
const streamFormat = args.format ?? (access.parseMode ?? "html");
|
|
@@ -61477,7 +61494,7 @@ async function executeStreamReply(args) {
|
|
|
61477
61494
|
clearSilentEndState(statusKey(streamChatId, streamThreadIdForClear));
|
|
61478
61495
|
}
|
|
61479
61496
|
{
|
|
61480
|
-
const sChat = args.chat_id;
|
|
61497
|
+
const sChat = String(args.chat_id ?? "");
|
|
61481
61498
|
const sThread = resolveThreadId(sChat, args.message_thread_id);
|
|
61482
61499
|
releaseTurnBufferGate(statusKey(sChat, sThread), turn ?? undefined);
|
|
61483
61500
|
if (turn?.finalAnswerDelivered === true) {
|
|
@@ -61491,7 +61508,7 @@ async function executeProgressUpdate(args) {
|
|
|
61491
61508
|
throw new Error("progress_update: chat_id is required");
|
|
61492
61509
|
if (!args.text)
|
|
61493
61510
|
throw new Error("progress_update: text is required");
|
|
61494
|
-
const chat_id = args.chat_id;
|
|
61511
|
+
const chat_id = String(args.chat_id ?? "");
|
|
61495
61512
|
let text2 = args.text;
|
|
61496
61513
|
const threadId = resolveThreadId(chat_id, args.message_thread_id);
|
|
61497
61514
|
const key = statusKey(chat_id, threadId);
|
|
@@ -61816,7 +61833,7 @@ function renderVaultRequestSaveCard(req, agentSlug) {
|
|
|
61816
61833
|
`);
|
|
61817
61834
|
}
|
|
61818
61835
|
async function executeVaultRequestSave(args) {
|
|
61819
|
-
const chat_id = args.chat_id;
|
|
61836
|
+
const chat_id = String(args.chat_id ?? "");
|
|
61820
61837
|
if (!chat_id)
|
|
61821
61838
|
throw new Error("vault_request_save: chat_id is required");
|
|
61822
61839
|
const key = args.key;
|
|
@@ -61904,7 +61921,7 @@ function renderSecretRequestCard(req) {
|
|
|
61904
61921
|
`);
|
|
61905
61922
|
}
|
|
61906
61923
|
async function executeRequestSecret(args) {
|
|
61907
|
-
const chat_id = args.chat_id;
|
|
61924
|
+
const chat_id = String(args.chat_id ?? "");
|
|
61908
61925
|
if (!chat_id)
|
|
61909
61926
|
throw new Error("request_secret: chat_id is required");
|
|
61910
61927
|
const key = args.key;
|
|
@@ -62110,7 +62127,7 @@ function renderVaultRequestAccessCard(req) {
|
|
|
62110
62127
|
`);
|
|
62111
62128
|
}
|
|
62112
62129
|
async function executeVaultRequestAccess(args) {
|
|
62113
|
-
const chat_id = args.chat_id;
|
|
62130
|
+
const chat_id = String(args.chat_id ?? "");
|
|
62114
62131
|
if (!chat_id)
|
|
62115
62132
|
throw new Error("vault_request_access: chat_id is required");
|
|
62116
62133
|
const key = args.key;
|
|
@@ -62195,8 +62212,8 @@ async function executeReact(args) {
|
|
|
62195
62212
|
throw new Error("react: message_id is required");
|
|
62196
62213
|
if (!args.emoji)
|
|
62197
62214
|
throw new Error("react: emoji is required");
|
|
62198
|
-
assertAllowedChat(args.chat_id);
|
|
62199
|
-
await lockedBot.api.setMessageReaction(args.chat_id, Number(args.message_id), [
|
|
62215
|
+
assertAllowedChat(String(args.chat_id ?? ""));
|
|
62216
|
+
await lockedBot.api.setMessageReaction(String(args.chat_id ?? ""), Number(args.message_id), [
|
|
62200
62217
|
{ type: "emoji", emoji: args.emoji }
|
|
62201
62218
|
]);
|
|
62202
62219
|
return { content: [{ type: "text", text: "reacted" }] };
|
|
@@ -62239,7 +62256,7 @@ async function executeEditMessage(args) {
|
|
|
62239
62256
|
throw new Error("edit_message: message_id is required");
|
|
62240
62257
|
if (args.text == null || args.text === "")
|
|
62241
62258
|
throw new Error("edit_message: text is required and cannot be empty");
|
|
62242
|
-
assertAllowedChat(args.chat_id);
|
|
62259
|
+
assertAllowedChat(String(args.chat_id ?? ""));
|
|
62243
62260
|
const editAccess = loadAccess();
|
|
62244
62261
|
const editConfigMode = editAccess.parseMode ?? "html";
|
|
62245
62262
|
const editFormat = args.format ?? editConfigMode;
|
|
@@ -62251,7 +62268,7 @@ async function executeEditMessage(args) {
|
|
|
62251
62268
|
editRawText = scrub.scrubbed;
|
|
62252
62269
|
emitRuntimeMetric({
|
|
62253
62270
|
kind: "voice_scrub_applied",
|
|
62254
|
-
chatKey: statusKey(args.chat_id, undefined),
|
|
62271
|
+
chatKey: statusKey(String(args.chat_id ?? ""), undefined),
|
|
62255
62272
|
replaced: scrub.replaced,
|
|
62256
62273
|
site: "edit_message"
|
|
62257
62274
|
});
|
|
@@ -62269,11 +62286,11 @@ async function executeEditMessage(args) {
|
|
|
62269
62286
|
editParseMode = undefined;
|
|
62270
62287
|
editText = editRawText;
|
|
62271
62288
|
}
|
|
62272
|
-
const edited = await robustApiCall(() => lockedBot.api.editMessageText(args.chat_id, Number(args.message_id), editText, ...editParseMode ? [{ parse_mode: editParseMode }] : []));
|
|
62289
|
+
const edited = await robustApiCall(() => lockedBot.api.editMessageText(String(args.chat_id ?? ""), Number(args.message_id), editText, ...editParseMode ? [{ parse_mode: editParseMode }] : []));
|
|
62273
62290
|
const id = typeof edited === "object" && edited ? edited.message_id : args.message_id;
|
|
62274
62291
|
if (HISTORY_ENABLED) {
|
|
62275
62292
|
try {
|
|
62276
|
-
recordEdit({ chat_id: args.chat_id, message_id: Number(args.message_id), text: args.text });
|
|
62293
|
+
recordEdit({ chat_id: String(args.chat_id ?? ""), message_id: Number(args.message_id), text: args.text });
|
|
62277
62294
|
} catch (err) {
|
|
62278
62295
|
process.stderr.write(`telegram gateway: history recordEdit failed: ${err}
|
|
62279
62296
|
`);
|
|
@@ -62284,7 +62301,7 @@ async function executeEditMessage(args) {
|
|
|
62284
62301
|
async function executeSendTyping(args) {
|
|
62285
62302
|
if (!args.chat_id)
|
|
62286
62303
|
throw new Error("send_typing: chat_id is required");
|
|
62287
|
-
const stChatId = args.chat_id;
|
|
62304
|
+
const stChatId = String(args.chat_id ?? "");
|
|
62288
62305
|
assertAllowedChat(stChatId);
|
|
62289
62306
|
const rawAction = args.action;
|
|
62290
62307
|
let action = "typing";
|
|
@@ -62308,7 +62325,7 @@ async function executePinMessage(args) {
|
|
|
62308
62325
|
throw new Error("pin_message: chat_id is required");
|
|
62309
62326
|
if (!args.message_id)
|
|
62310
62327
|
throw new Error("pin_message: message_id is required");
|
|
62311
|
-
const pinChatId = args.chat_id;
|
|
62328
|
+
const pinChatId = String(args.chat_id ?? "");
|
|
62312
62329
|
assertAllowedChat(pinChatId);
|
|
62313
62330
|
await robustApiCall(() => lockedBot.api.pinChatMessage(pinChatId, Number(args.message_id)), { chat_id: pinChatId, verb: "pin_message" });
|
|
62314
62331
|
return { content: [{ type: "text", text: `pinned message ${args.message_id}` }] };
|
|
@@ -62318,7 +62335,7 @@ async function executeDeleteMessage(args) {
|
|
|
62318
62335
|
throw new Error("delete_message: chat_id is required");
|
|
62319
62336
|
if (!args.message_id)
|
|
62320
62337
|
throw new Error("delete_message: message_id is required");
|
|
62321
|
-
const delChatId = args.chat_id;
|
|
62338
|
+
const delChatId = String(args.chat_id ?? "");
|
|
62322
62339
|
const delMessageId = Number(args.message_id);
|
|
62323
62340
|
assertAllowedChat(delChatId);
|
|
62324
62341
|
await robustApiCall(() => lockedBot.api.deleteMessage(delChatId, delMessageId), { chat_id: delChatId });
|
|
@@ -62339,7 +62356,7 @@ async function executeForwardMessage(args) {
|
|
|
62339
62356
|
throw new Error("forward_message: from_chat_id is required");
|
|
62340
62357
|
if (!args.message_id)
|
|
62341
62358
|
throw new Error("forward_message: message_id is required");
|
|
62342
|
-
const fwdChatId = args.chat_id;
|
|
62359
|
+
const fwdChatId = String(args.chat_id ?? "");
|
|
62343
62360
|
const fwdFromChatId = args.from_chat_id;
|
|
62344
62361
|
const fwdMsgId = Number(args.message_id);
|
|
62345
62362
|
assertAllowedChat(fwdChatId);
|
|
@@ -62371,7 +62388,7 @@ async function executeGetRecentMessages(args) {
|
|
|
62371
62388
|
}
|
|
62372
62389
|
if (!args.chat_id)
|
|
62373
62390
|
throw new Error("get_recent_messages: chat_id is required");
|
|
62374
|
-
const chat_id = args.chat_id;
|
|
62391
|
+
const chat_id = String(args.chat_id ?? "");
|
|
62375
62392
|
assertAllowedChat(chat_id);
|
|
62376
62393
|
const rawThread = args.message_thread_id;
|
|
62377
62394
|
let thread_id;
|
|
@@ -1030,11 +1030,12 @@ function loadAccess(): Access {
|
|
|
1030
1030
|
return BOOT_ACCESS ?? readAccessFile()
|
|
1031
1031
|
}
|
|
1032
1032
|
|
|
1033
|
-
function assertAllowedChat(chat_id: string): void {
|
|
1033
|
+
function assertAllowedChat(chat_id: string | number): void {
|
|
1034
|
+
const id = String(chat_id)
|
|
1034
1035
|
const access = loadAccess()
|
|
1035
|
-
if (access.allowFrom.includes(
|
|
1036
|
-
if (
|
|
1037
|
-
throw new Error(`chat ${
|
|
1036
|
+
if (access.allowFrom.includes(id)) return
|
|
1037
|
+
if (id in access.groups) return
|
|
1038
|
+
throw new Error(`chat ${id} is not allowlisted — add via /telegram:access`)
|
|
1038
1039
|
}
|
|
1039
1040
|
|
|
1040
1041
|
function saveAccess(a: Access): void {
|
|
@@ -7834,7 +7835,7 @@ async function executeToolCall(tool: string, args: Record<string, unknown>): Pro
|
|
|
7834
7835
|
}
|
|
7835
7836
|
|
|
7836
7837
|
async function executeSendChecklist(args: Record<string, unknown>): Promise<{ content: Array<{ type: string; text: string }> }> {
|
|
7837
|
-
const chat_id = args.chat_id
|
|
7838
|
+
const chat_id = String(args.chat_id ?? '')
|
|
7838
7839
|
if (!chat_id) throw new Error('send_checklist: chat_id is required')
|
|
7839
7840
|
const title = args.title as string | undefined
|
|
7840
7841
|
if (!title) throw new Error('send_checklist: title is required')
|
|
@@ -7922,7 +7923,7 @@ async function executeLinearAgentSetup(args: Record<string, unknown>): Promise<{
|
|
|
7922
7923
|
}
|
|
7923
7924
|
|
|
7924
7925
|
async function executeUpdateChecklist(args: Record<string, unknown>): Promise<{ content: Array<{ type: string; text: string }> }> {
|
|
7925
|
-
const chat_id = args.chat_id
|
|
7926
|
+
const chat_id = String(args.chat_id ?? '')
|
|
7926
7927
|
if (!chat_id) throw new Error('update_checklist: chat_id is required')
|
|
7927
7928
|
const message_id = args.message_id as string | undefined
|
|
7928
7929
|
if (!message_id) throw new Error('update_checklist: message_id is required')
|
|
@@ -7971,7 +7972,7 @@ async function executeReply(args: Record<string, unknown>): Promise<{ content: A
|
|
|
7971
7972
|
// module-scope currentTurn, which a future refactor could let roll over
|
|
7972
7973
|
// mid-call.
|
|
7973
7974
|
const turn = currentTurn
|
|
7974
|
-
const chat_id = args.chat_id
|
|
7975
|
+
const chat_id = String(args.chat_id ?? '')
|
|
7975
7976
|
if (!chat_id) throw new Error('reply: chat_id is required')
|
|
7976
7977
|
const rawText = args.text as string | undefined
|
|
7977
7978
|
if (rawText == null || rawText === '') throw new Error('reply: text is required and cannot be empty')
|
|
@@ -8997,7 +8998,7 @@ async function executeStreamReply(args: Record<string, unknown>): Promise<unknow
|
|
|
8997
8998
|
args.text = scrub.scrubbed
|
|
8998
8999
|
emitRuntimeMetric({
|
|
8999
9000
|
kind: 'voice_scrub_applied',
|
|
9000
|
-
chatKey: statusKey(args.chat_id
|
|
9001
|
+
chatKey: statusKey(String(args.chat_id ?? ''), args.message_thread_id != null
|
|
9001
9002
|
? Number(args.message_thread_id) : undefined),
|
|
9002
9003
|
replaced: scrub.replaced,
|
|
9003
9004
|
site: 'stream_reply',
|
|
@@ -9012,7 +9013,7 @@ async function executeStreamReply(args: Record<string, unknown>): Promise<unknow
|
|
|
9012
9013
|
// Only check on done=true (the terminal call); intermediate
|
|
9013
9014
|
// streaming chunks are progress edits, not full sends.
|
|
9014
9015
|
if (args.done === true) {
|
|
9015
|
-
const sChatId = args.chat_id
|
|
9016
|
+
const sChatId = String(args.chat_id ?? '')
|
|
9016
9017
|
const sThreadId = args.message_thread_id != null ? Number(args.message_thread_id) : undefined
|
|
9017
9018
|
const sText = args.text as string
|
|
9018
9019
|
const dup = outboundDedup.check(sChatId, sThreadId, sText, Date.now(), currentTurn?.registryKey ?? null)
|
|
@@ -9028,7 +9029,7 @@ async function executeStreamReply(args: Record<string, unknown>): Promise<unknow
|
|
|
9028
9029
|
const access = loadAccess()
|
|
9029
9030
|
// Detect chat type for throttle-default selection.
|
|
9030
9031
|
// Private (DM) chats have positive numeric IDs; groups/channels are negative.
|
|
9031
|
-
const streamChatId = args.chat_id
|
|
9032
|
+
const streamChatId = String(args.chat_id ?? '')
|
|
9032
9033
|
const streamIsPrivate = isDmChatId(streamChatId)
|
|
9033
9034
|
const streamIsForumTopic = args.message_thread_id != null && args.message_thread_id !== ''
|
|
9034
9035
|
// Pre-allocated draft handoff removed in #553 PR 5 — draft-stream
|
|
@@ -9192,7 +9193,7 @@ async function executeStreamReply(args: Record<string, unknown>): Promise<unknow
|
|
|
9192
9193
|
if (result.messageId != null) {
|
|
9193
9194
|
try {
|
|
9194
9195
|
progressDriver?.recordOutboundDelivered(
|
|
9195
|
-
args.chat_id
|
|
9196
|
+
String(args.chat_id ?? ''),
|
|
9196
9197
|
args.message_thread_id as string | undefined,
|
|
9197
9198
|
)
|
|
9198
9199
|
} catch { /* best-effort signal */ }
|
|
@@ -9207,7 +9208,7 @@ async function executeStreamReply(args: Record<string, unknown>): Promise<unknow
|
|
|
9207
9208
|
? Number(args.message_thread_id)
|
|
9208
9209
|
: undefined
|
|
9209
9210
|
signalTracker.noteSignal(
|
|
9210
|
-
statusKey(args.chat_id
|
|
9211
|
+
statusKey(String(args.chat_id ?? ''), threadIdNum),
|
|
9211
9212
|
Date.now(),
|
|
9212
9213
|
)
|
|
9213
9214
|
} catch { /* best-effort signal */ }
|
|
@@ -9220,13 +9221,13 @@ async function executeStreamReply(args: Record<string, unknown>): Promise<unknow
|
|
|
9220
9221
|
&& streamButtonMeta != null
|
|
9221
9222
|
&& streamButtonMeta.size > 0
|
|
9222
9223
|
) {
|
|
9223
|
-
rememberAgentButtonMeta(args.chat_id
|
|
9224
|
+
rememberAgentButtonMeta(String(args.chat_id ?? ''), result.messageId, streamButtonMeta)
|
|
9224
9225
|
}
|
|
9225
9226
|
// #546 dedup record: capture the final stream_reply text on the
|
|
9226
9227
|
// terminal call so a subsequent retry (different bridge, same
|
|
9227
9228
|
// content) lands as a no-op instead of a second message.
|
|
9228
9229
|
if (args.done === true && result.messageId != null) {
|
|
9229
|
-
const sChatId = args.chat_id
|
|
9230
|
+
const sChatId = String(args.chat_id ?? '')
|
|
9230
9231
|
const sThreadId = args.message_thread_id != null ? Number(args.message_thread_id) : undefined
|
|
9231
9232
|
outboundDedup.record(sChatId, sThreadId, args.text as string, Date.now(), currentTurn?.registryKey ?? null)
|
|
9232
9233
|
// #1445 cross-turn pending-async ambient. The terminal stream_reply
|
|
@@ -9306,7 +9307,7 @@ async function executeStreamReply(args: Record<string, unknown>): Promise<unknow
|
|
|
9306
9307
|
// (stream-reply-handler.ts:305) at this point — earlier failures
|
|
9307
9308
|
// throw or return before reaching here.
|
|
9308
9309
|
{
|
|
9309
|
-
const sChat = args.chat_id
|
|
9310
|
+
const sChat = String(args.chat_id ?? '')
|
|
9310
9311
|
const sThread = resolveThreadId(sChat, args.message_thread_id as string | undefined)
|
|
9311
9312
|
// Component 1: pass the turn (finalAnswerDelivered set above for a
|
|
9312
9313
|
// final stream emit). Interim stream chunks leave it false → no
|
|
@@ -9326,7 +9327,7 @@ async function executeProgressUpdate(args: Record<string, unknown>): Promise<unk
|
|
|
9326
9327
|
if (!args.chat_id) throw new Error('progress_update: chat_id is required')
|
|
9327
9328
|
if (!args.text) throw new Error('progress_update: text is required')
|
|
9328
9329
|
|
|
9329
|
-
const chat_id = args.chat_id
|
|
9330
|
+
const chat_id = String(args.chat_id ?? '')
|
|
9330
9331
|
let text = args.text as string
|
|
9331
9332
|
const threadId = resolveThreadId(chat_id, args.message_thread_id as string | undefined)
|
|
9332
9333
|
const key = statusKey(chat_id, threadId)
|
|
@@ -9810,7 +9811,7 @@ function renderVaultRequestSaveCard(req: PendingVaultRequestSave, agentSlug: str
|
|
|
9810
9811
|
* written to vault only on user tap. See #969 P1a.
|
|
9811
9812
|
*/
|
|
9812
9813
|
async function executeVaultRequestSave(args: Record<string, unknown>): Promise<{ content: Array<{ type: string; text: string }> }> {
|
|
9813
|
-
const chat_id = args.chat_id
|
|
9814
|
+
const chat_id = String(args.chat_id ?? '')
|
|
9814
9815
|
if (!chat_id) throw new Error('vault_request_save: chat_id is required')
|
|
9815
9816
|
const key = args.key as string
|
|
9816
9817
|
if (!key || typeof key !== 'string') throw new Error('vault_request_save: key is required')
|
|
@@ -9954,7 +9955,7 @@ function renderSecretRequestCard(req: PendingSecretRequest): string {
|
|
|
9954
9955
|
* capture (the operator's next message after they tap [Provide securely]).
|
|
9955
9956
|
*/
|
|
9956
9957
|
async function executeRequestSecret(args: Record<string, unknown>): Promise<{ content: Array<{ type: string; text: string }> }> {
|
|
9957
|
-
const chat_id = args.chat_id
|
|
9958
|
+
const chat_id = String(args.chat_id ?? '')
|
|
9958
9959
|
if (!chat_id) throw new Error('request_secret: chat_id is required')
|
|
9959
9960
|
const key = args.key as string
|
|
9960
9961
|
if (!key || typeof key !== 'string') throw new Error('request_secret: key is required')
|
|
@@ -10238,7 +10239,7 @@ function renderVaultRequestAccessCard(req: PendingVaultRequestAccess): string {
|
|
|
10238
10239
|
* the agent itself can only REQUEST.
|
|
10239
10240
|
*/
|
|
10240
10241
|
async function executeVaultRequestAccess(args: Record<string, unknown>): Promise<{ content: Array<{ type: string; text: string }> }> {
|
|
10241
|
-
const chat_id = args.chat_id
|
|
10242
|
+
const chat_id = String(args.chat_id ?? '')
|
|
10242
10243
|
if (!chat_id) throw new Error('vault_request_access: chat_id is required')
|
|
10243
10244
|
const key = args.key as string
|
|
10244
10245
|
if (!key || typeof key !== 'string') throw new Error('vault_request_access: key is required')
|
|
@@ -10356,8 +10357,8 @@ async function executeReact(args: Record<string, unknown>): Promise<unknown> {
|
|
|
10356
10357
|
if (!args.chat_id) throw new Error('react: chat_id is required')
|
|
10357
10358
|
if (!args.message_id) throw new Error('react: message_id is required')
|
|
10358
10359
|
if (!args.emoji) throw new Error('react: emoji is required')
|
|
10359
|
-
assertAllowedChat(args.chat_id
|
|
10360
|
-
await lockedBot.api.setMessageReaction(args.chat_id
|
|
10360
|
+
assertAllowedChat(String(args.chat_id ?? ''))
|
|
10361
|
+
await lockedBot.api.setMessageReaction(String(args.chat_id ?? ''), Number(args.message_id), [
|
|
10361
10362
|
{ type: 'emoji', emoji: args.emoji as ReactionTypeEmoji['emoji'] },
|
|
10362
10363
|
])
|
|
10363
10364
|
return { content: [{ type: 'text', text: 'reacted' }] }
|
|
@@ -10401,7 +10402,7 @@ async function executeEditMessage(args: Record<string, unknown>): Promise<unknow
|
|
|
10401
10402
|
if (!args.chat_id) throw new Error('edit_message: chat_id is required')
|
|
10402
10403
|
if (!args.message_id) throw new Error('edit_message: message_id is required')
|
|
10403
10404
|
if (args.text == null || args.text === '') throw new Error('edit_message: text is required and cannot be empty')
|
|
10404
|
-
assertAllowedChat(args.chat_id
|
|
10405
|
+
assertAllowedChat(String(args.chat_id ?? ''))
|
|
10405
10406
|
const editAccess = loadAccess()
|
|
10406
10407
|
const editConfigMode = editAccess.parseMode ?? 'html'
|
|
10407
10408
|
const editFormat = (args.format as string | undefined) ?? editConfigMode
|
|
@@ -10419,7 +10420,7 @@ async function executeEditMessage(args: Record<string, unknown>): Promise<unknow
|
|
|
10419
10420
|
editRawText = scrub.scrubbed
|
|
10420
10421
|
emitRuntimeMetric({
|
|
10421
10422
|
kind: 'voice_scrub_applied',
|
|
10422
|
-
chatKey: statusKey(args.chat_id
|
|
10423
|
+
chatKey: statusKey(String(args.chat_id ?? ''), undefined),
|
|
10423
10424
|
replaced: scrub.replaced,
|
|
10424
10425
|
site: 'edit_message',
|
|
10425
10426
|
})
|
|
@@ -10439,14 +10440,14 @@ async function executeEditMessage(args: Record<string, unknown>): Promise<unknow
|
|
|
10439
10440
|
}
|
|
10440
10441
|
const edited = await robustApiCall(
|
|
10441
10442
|
() => lockedBot.api.editMessageText(
|
|
10442
|
-
args.chat_id
|
|
10443
|
+
String(args.chat_id ?? ''), Number(args.message_id), editText,
|
|
10443
10444
|
...(editParseMode ? [{ parse_mode: editParseMode }] : []),
|
|
10444
10445
|
),
|
|
10445
10446
|
)
|
|
10446
10447
|
const id = typeof edited === 'object' && edited ? (edited as any).message_id : args.message_id
|
|
10447
10448
|
if (HISTORY_ENABLED) {
|
|
10448
10449
|
try {
|
|
10449
|
-
recordEdit({ chat_id: args.chat_id
|
|
10450
|
+
recordEdit({ chat_id: String(args.chat_id ?? ''), message_id: Number(args.message_id), text: args.text as string })
|
|
10450
10451
|
} catch (err) {
|
|
10451
10452
|
process.stderr.write(`telegram gateway: history recordEdit failed: ${err}\n`)
|
|
10452
10453
|
}
|
|
@@ -10456,7 +10457,7 @@ async function executeEditMessage(args: Record<string, unknown>): Promise<unknow
|
|
|
10456
10457
|
|
|
10457
10458
|
async function executeSendTyping(args: Record<string, unknown>): Promise<unknown> {
|
|
10458
10459
|
if (!args.chat_id) throw new Error('send_typing: chat_id is required')
|
|
10459
|
-
const stChatId = args.chat_id
|
|
10460
|
+
const stChatId = String(args.chat_id ?? '')
|
|
10460
10461
|
assertAllowedChat(stChatId)
|
|
10461
10462
|
// #273: granular chat actions. Default 'typing' preserves the
|
|
10462
10463
|
// existing tool semantics; agents can opt in to upload_document,
|
|
@@ -10490,7 +10491,7 @@ async function executeSendTyping(args: Record<string, unknown>): Promise<unknown
|
|
|
10490
10491
|
async function executePinMessage(args: Record<string, unknown>): Promise<unknown> {
|
|
10491
10492
|
if (!args.chat_id) throw new Error('pin_message: chat_id is required')
|
|
10492
10493
|
if (!args.message_id) throw new Error('pin_message: message_id is required')
|
|
10493
|
-
const pinChatId = args.chat_id
|
|
10494
|
+
const pinChatId = String(args.chat_id ?? '')
|
|
10494
10495
|
assertAllowedChat(pinChatId)
|
|
10495
10496
|
// #1075: wrap through robustApiCall so flood-wait / transient network
|
|
10496
10497
|
// errors are retried. THREAD_NOT_FOUND on a stale topic surfaces to the
|
|
@@ -10506,7 +10507,7 @@ async function executePinMessage(args: Record<string, unknown>): Promise<unknown
|
|
|
10506
10507
|
async function executeDeleteMessage(args: Record<string, unknown>): Promise<unknown> {
|
|
10507
10508
|
if (!args.chat_id) throw new Error('delete_message: chat_id is required')
|
|
10508
10509
|
if (!args.message_id) throw new Error('delete_message: message_id is required')
|
|
10509
|
-
const delChatId = args.chat_id
|
|
10510
|
+
const delChatId = String(args.chat_id ?? '')
|
|
10510
10511
|
const delMessageId = Number(args.message_id)
|
|
10511
10512
|
assertAllowedChat(delChatId)
|
|
10512
10513
|
await robustApiCall(() => lockedBot.api.deleteMessage(delChatId, delMessageId), { chat_id: delChatId })
|
|
@@ -10522,7 +10523,7 @@ async function executeForwardMessage(args: Record<string, unknown>): Promise<unk
|
|
|
10522
10523
|
if (!args.chat_id) throw new Error('forward_message: chat_id is required')
|
|
10523
10524
|
if (!args.from_chat_id) throw new Error('forward_message: from_chat_id is required')
|
|
10524
10525
|
if (!args.message_id) throw new Error('forward_message: message_id is required')
|
|
10525
|
-
const fwdChatId = args.chat_id
|
|
10526
|
+
const fwdChatId = String(args.chat_id ?? '')
|
|
10526
10527
|
const fwdFromChatId = args.from_chat_id as string
|
|
10527
10528
|
const fwdMsgId = Number(args.message_id)
|
|
10528
10529
|
assertAllowedChat(fwdChatId)
|
|
@@ -10556,7 +10557,7 @@ async function executeGetRecentMessages(args: Record<string, unknown>): Promise<
|
|
|
10556
10557
|
}
|
|
10557
10558
|
}
|
|
10558
10559
|
if (!args.chat_id) throw new Error('get_recent_messages: chat_id is required')
|
|
10559
|
-
const chat_id = args.chat_id
|
|
10560
|
+
const chat_id = String(args.chat_id ?? '')
|
|
10560
10561
|
assertAllowedChat(chat_id)
|
|
10561
10562
|
const rawThread = args.message_thread_id as string | undefined
|
|
10562
10563
|
let thread_id: number | null | undefined
|
|
@@ -144,12 +144,14 @@ const PERSIST_NOTE =
|
|
|
144
144
|
'<i>Session-only — lasts until restart. To persist, set <code>model:</code> in switchroom.yaml and restart.</i>'
|
|
145
145
|
|
|
146
146
|
function helpText(deps: ModelCommandDeps, reason?: string): ModelCommandReply {
|
|
147
|
+
const srAliasExamples = Object.keys(SR_MODEL_ALIASES).map(a => `<code>${a}</code>`).join(' · ')
|
|
147
148
|
const lines: string[] = []
|
|
148
149
|
if (reason) lines.push(`⚠️ ${deps.escapeHtml(reason)}`)
|
|
149
150
|
lines.push(
|
|
150
151
|
'<b>/model</b> — show or switch the Claude model',
|
|
151
152
|
'<code>/model</code> — show the configured model',
|
|
152
153
|
`<code>/model <name></code> — switch the live session (${MODEL_ALIASES.map(a => `<code>${a}</code>`).join(' · ')} or a full model id)`,
|
|
154
|
+
`<i>OpenRouter shortcuts:</i> ${srAliasExamples}`,
|
|
153
155
|
PERSIST_NOTE,
|
|
154
156
|
)
|
|
155
157
|
return { text: lines.join('\n'), html: true }
|
|
@@ -164,11 +166,13 @@ export async function handleModelCommand(
|
|
|
164
166
|
if (parsed.kind === 'show') {
|
|
165
167
|
const configured = deps.getConfiguredModel()
|
|
166
168
|
const shown = configured && configured.length > 0 ? configured : 'default'
|
|
169
|
+
const srAliasExamples = Object.keys(SR_MODEL_ALIASES).map(a => `<code>/model ${a}</code>`).join(' · ')
|
|
167
170
|
return {
|
|
168
171
|
text: [
|
|
169
172
|
`<b>Model — ${deps.escapeHtml(deps.getAgentName())}</b>`,
|
|
170
173
|
`Configured: <code>${deps.escapeHtml(shown)}</code>`,
|
|
171
174
|
`Switch the live session: ${MODEL_ALIASES.map(a => `<code>/model ${a}</code>`).join(' · ')}`,
|
|
175
|
+
`OpenRouter shortcuts: ${srAliasExamples}`,
|
|
172
176
|
'or <code>/model <full-model-id></code>',
|
|
173
177
|
PERSIST_NOTE,
|
|
174
178
|
].join('\n'),
|
|
@@ -182,15 +186,18 @@ export async function handleModelCommand(
|
|
|
182
186
|
return helpText(deps, `not a valid model name: ${parsed.model}`)
|
|
183
187
|
}
|
|
184
188
|
|
|
189
|
+
// Expand short aliases: `flash` → `sr-gemini-2.5-flash`, `codex` → `sr-codex-5.5`, etc.
|
|
190
|
+
const model = expandSrAlias(parsed.model)
|
|
191
|
+
|
|
185
192
|
// sr-* → Claude: an in-place `/model` inject would leave LiteLLM routing
|
|
186
193
|
// active in the live session because the sr-* model context was set by
|
|
187
194
|
// the proxy at session start, not by claude's own REPL. A graceful restart
|
|
188
195
|
// is the only clean path back to the native OAuth route. This matches the
|
|
189
196
|
// behaviour of the `/restart` command (same mechanism, same marker logic).
|
|
190
197
|
const currentSession = deps.getActiveSessionModel()
|
|
191
|
-
if (currentSession !== null && isSrModel(currentSession) && isClaudeModel(
|
|
198
|
+
if (currentSession !== null && isSrModel(currentSession) && isClaudeModel(model)) {
|
|
192
199
|
try {
|
|
193
|
-
await deps.scheduleRestart(`user: /model ${
|
|
200
|
+
await deps.scheduleRestart(`user: /model ${model} (sr-to-claude restart)`)
|
|
194
201
|
} catch (err) {
|
|
195
202
|
const msg = err instanceof Error ? err.message : String(err)
|
|
196
203
|
return {
|
|
@@ -207,10 +214,10 @@ export async function handleModelCommand(
|
|
|
207
214
|
}
|
|
208
215
|
}
|
|
209
216
|
|
|
210
|
-
const verbHtml = `<code>/model ${deps.escapeHtml(
|
|
217
|
+
const verbHtml = `<code>/model ${deps.escapeHtml(model)}</code>`
|
|
211
218
|
let result: InjectResult
|
|
212
219
|
try {
|
|
213
|
-
result = await deps.inject(deps.getAgentName(), `/model ${
|
|
220
|
+
result = await deps.inject(deps.getAgentName(), `/model ${model}`)
|
|
214
221
|
} catch (err) {
|
|
215
222
|
const msg = err instanceof Error ? err.message : String(err)
|
|
216
223
|
return {
|
|
@@ -315,6 +322,27 @@ export const SR_MODEL_LABELS: Record<string, string> = {
|
|
|
315
322
|
'sr-deepseek-r1': 'DeepSeek R1',
|
|
316
323
|
'sr-deepseek-v3': 'DeepSeek V3',
|
|
317
324
|
'sr-glm-5': 'GLM-5',
|
|
325
|
+
'sr-codex-5.5': 'Codex 5.5',
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Short text-command aliases for sr-* models. These let the operator type
|
|
330
|
+
* `/model flash`, `/model codex`, etc. instead of the full `sr-*` id.
|
|
331
|
+
* Expanded in handleModelCommand before injection; the full sr-* id is what
|
|
332
|
+
* reaches the agent session and LiteLLM.
|
|
333
|
+
*/
|
|
334
|
+
export const SR_MODEL_ALIASES: Record<string, string> = {
|
|
335
|
+
flash: 'sr-gemini-2.5-flash',
|
|
336
|
+
gemini: 'sr-gemini-2.5-pro',
|
|
337
|
+
deepseek: 'sr-deepseek-v3',
|
|
338
|
+
r1: 'sr-deepseek-r1',
|
|
339
|
+
glm: 'sr-glm-5',
|
|
340
|
+
codex: 'sr-codex-5.5',
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/** Expand a short alias (case-insensitive) to its full sr-* id, or return the original. */
|
|
344
|
+
export function expandSrAlias(arg: string): string {
|
|
345
|
+
return SR_MODEL_ALIASES[arg.toLowerCase()] ?? arg
|
|
318
346
|
}
|
|
319
347
|
|
|
320
348
|
function srFriendlyLabel(srName: string): string {
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* REPL verb — already on the inject allowlist) and relays the
|
|
14
14
|
* captured output, with the session-only persistence caveat.
|
|
15
15
|
*/
|
|
16
|
-
import { describe, it, expect } from "vitest";
|
|
16
|
+
import { describe, it, expect, beforeAll } from "vitest";
|
|
17
17
|
import {
|
|
18
18
|
parseModelCommand,
|
|
19
19
|
handleModelCommand,
|
|
@@ -344,6 +344,60 @@ describe("inject allowlist contract", () => {
|
|
|
344
344
|
// Picker-driven menu (v2) — buildModelMenu + handleModelMenuCallback
|
|
345
345
|
// ---------------------------------------------------------------------------
|
|
346
346
|
|
|
347
|
+
describe("SR_MODEL_ALIASES / expandSrAlias", () => {
|
|
348
|
+
let expandSrAlias: (arg: string) => string;
|
|
349
|
+
let SR_MODEL_ALIASES: Record<string, string>;
|
|
350
|
+
|
|
351
|
+
beforeAll(async () => {
|
|
352
|
+
const mod = await import("../gateway/model-command.js");
|
|
353
|
+
expandSrAlias = mod.expandSrAlias;
|
|
354
|
+
SR_MODEL_ALIASES = mod.SR_MODEL_ALIASES;
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
it("expands known short aliases to full sr-* ids", () => {
|
|
358
|
+
expect(expandSrAlias("flash")).toBe("sr-gemini-2.5-flash");
|
|
359
|
+
expect(expandSrAlias("gemini")).toBe("sr-gemini-2.5-pro");
|
|
360
|
+
expect(expandSrAlias("deepseek")).toBe("sr-deepseek-v3");
|
|
361
|
+
expect(expandSrAlias("r1")).toBe("sr-deepseek-r1");
|
|
362
|
+
expect(expandSrAlias("glm")).toBe("sr-glm-5");
|
|
363
|
+
expect(expandSrAlias("codex")).toBe("sr-codex-5.5");
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
it("is case-insensitive", () => {
|
|
367
|
+
expect(expandSrAlias("Flash")).toBe("sr-gemini-2.5-flash");
|
|
368
|
+
expect(expandSrAlias("CODEX")).toBe("sr-codex-5.5");
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
it("passes through unknown names unchanged", () => {
|
|
372
|
+
expect(expandSrAlias("opus")).toBe("opus");
|
|
373
|
+
expect(expandSrAlias("sr-gemini-2.5-flash")).toBe("sr-gemini-2.5-flash");
|
|
374
|
+
expect(expandSrAlias("claude-opus-4-8")).toBe("claude-opus-4-8");
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
it("every alias target is a valid sr-* model arg", () => {
|
|
378
|
+
for (const [alias, target] of Object.entries(SR_MODEL_ALIASES)) {
|
|
379
|
+
expect(target.startsWith("sr-"), `${alias} → ${target} must start with sr-`).toBe(true);
|
|
380
|
+
}
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
it("handleModelCommand injects expanded sr-* id, not the short alias", async () => {
|
|
384
|
+
const { deps, calls } = makeDeps({ getActiveSessionModel: () => null });
|
|
385
|
+
await handleModelCommand({ kind: "set", model: "flash" }, deps);
|
|
386
|
+
expect(calls).toHaveLength(1);
|
|
387
|
+
expect(calls[0].command).toBe("/model sr-gemini-2.5-flash");
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
it("handleModelCommand with alias schedules restart when session is on sr-*", async () => {
|
|
391
|
+
const { deps, calls, restartCalls } = makeDeps({
|
|
392
|
+
getActiveSessionModel: () => "sr-deepseek-v3",
|
|
393
|
+
});
|
|
394
|
+
await handleModelCommand({ kind: "set", model: "opus" }, deps);
|
|
395
|
+
expect(calls).toHaveLength(0);
|
|
396
|
+
expect(restartCalls).toHaveLength(1);
|
|
397
|
+
expect(restartCalls[0]).toContain("opus");
|
|
398
|
+
});
|
|
399
|
+
});
|
|
400
|
+
|
|
347
401
|
import {
|
|
348
402
|
buildModelMenu,
|
|
349
403
|
handleModelMenuCallback,
|
|
@@ -42,9 +42,11 @@ describe("uat: /model sr-* LiteLLM routing — section headers + session switch
|
|
|
42
42
|
const sc = await spinUp({ agent: AGENT });
|
|
43
43
|
try {
|
|
44
44
|
await sc.sendDM("/model");
|
|
45
|
+
// 60s — if an obligation turn is being processed when /model lands,
|
|
46
|
+
// the menu may be buffered until the turn drains (~40s max).
|
|
45
47
|
const menu = await sc.expectMessage(/Default \(new sessions\):/i, {
|
|
46
48
|
from: "bot",
|
|
47
|
-
timeout:
|
|
49
|
+
timeout: 60_000,
|
|
48
50
|
});
|
|
49
51
|
|
|
50
52
|
// ── 1. Section headers ──────────────────────────────────────────
|