switchroom 0.19.34 → 0.19.36
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/auth-broker/index.js +7 -1
- package/dist/cli/skill-validate-pretool.mjs +15 -2
- package/dist/cli/switchroom.js +1005 -482
- package/dist/host-control/main.js +156 -4
- package/dist/vault/approvals/kernel-server.js +7 -1
- package/dist/vault/broker/server.js +7 -1
- package/package.json +4 -2
- package/profiles/_base/start.sh.hbs +37 -12
- package/telegram-plugin/dist/gateway/gateway.js +476 -116
- package/telegram-plugin/format.ts +70 -15
- package/telegram-plugin/gateway/gateway.ts +27 -31
- package/telegram-plugin/gateway/ipc-server.ts +18 -15
- package/telegram-plugin/gateway/model-command.ts +279 -23
- package/telegram-plugin/gateway/outbound-send-path.ts +12 -1
- package/telegram-plugin/operator-events.ts +40 -16
- package/telegram-plugin/secret-detect/db-uri.ts +90 -0
- package/telegram-plugin/secret-detect/index.ts +24 -1
- package/telegram-plugin/secret-detect/inert-values.ts +147 -0
- package/telegram-plugin/secret-detect/kv-scanner.ts +108 -0
- package/telegram-plugin/secret-detect/patterns.ts +24 -4
- package/telegram-plugin/tests/format-consistency.test.ts +93 -0
- package/telegram-plugin/tests/gateway-session-model-relaunch.test.ts +40 -2
- package/telegram-plugin/tests/ipc-server-validate-operator.test.ts +20 -11
- package/telegram-plugin/tests/model-command.test.ts +415 -4
- package/telegram-plugin/tests/outbound-send-path.test.ts +1 -1
- package/telegram-plugin/tests/secret-detect-cross-engine.test.ts +263 -0
- package/telegram-plugin/tests/secret-detect-write-path.test.ts +403 -0
- package/telegram-plugin/tests/turn-flush-safety.test.ts +2 -2
- package/vendor/hindsight-memory/scripts/lib/client.py +58 -0
- package/vendor/hindsight-memory/scripts/lib/secret_patterns.json +431 -0
- package/vendor/hindsight-memory/scripts/lib/secret_redact.py +563 -0
- package/vendor/hindsight-memory/scripts/lib/secret_redaction_vectors.json +397 -0
- package/vendor/hindsight-memory/scripts/subagent_retain.py +103 -7
- package/vendor/hindsight-memory/scripts/tests/test_secret_redact.py +522 -0
- package/vendor/hindsight-memory/scripts/tests/test_subagent_retain_learnings.py +377 -0
|
@@ -6936,10 +6936,18 @@ function listItemContent(line) {
|
|
|
6936
6936
|
function isFullyBolded(fragment) {
|
|
6937
6937
|
return /^\*\*[^*]+\*\*[.,:;!?]?$/.test(fragment.trim());
|
|
6938
6938
|
}
|
|
6939
|
+
function isPseudoHeadingBlock(block) {
|
|
6940
|
+
const lines = block.split(`
|
|
6941
|
+
`).filter((l) => l.trim() !== "");
|
|
6942
|
+
if (lines.length !== 1)
|
|
6943
|
+
return false;
|
|
6944
|
+
const t = lines[0].trim();
|
|
6945
|
+
return t.length <= PSEUDO_HEADING_MAX_CHARS && isFullyBolded(t);
|
|
6946
|
+
}
|
|
6939
6947
|
function unbold(fragment) {
|
|
6940
6948
|
return fragment.replace(/\*\*([^*]+)\*\*/g, "$1");
|
|
6941
6949
|
}
|
|
6942
|
-
function stripExcessBold(text) {
|
|
6950
|
+
function stripExcessBold(text, onStrip) {
|
|
6943
6951
|
if (!text.includes("**"))
|
|
6944
6952
|
return text;
|
|
6945
6953
|
const nonce = Math.random().toString(36).slice(2);
|
|
@@ -6950,10 +6958,15 @@ function stripExcessBold(text) {
|
|
|
6950
6958
|
let boldChars = 0;
|
|
6951
6959
|
for (const m of visible.matchAll(/\*\*([^*]+)\*\*/g))
|
|
6952
6960
|
boldChars += m[1].length;
|
|
6953
|
-
|
|
6954
|
-
return restore(unbold(masked));
|
|
6955
|
-
}
|
|
6961
|
+
const ratio = boldChars / visible.length;
|
|
6956
6962
|
const blocks = masked.split(/\n{2,}/);
|
|
6963
|
+
if (ratio > 0.3) {
|
|
6964
|
+
const rebuilt2 = blocks.map((block) => isPseudoHeadingBlock(block) ? block : unbold(block));
|
|
6965
|
+
const out2 = rejoinBlocks(masked, rebuilt2);
|
|
6966
|
+
if (out2 !== masked)
|
|
6967
|
+
onStrip?.({ rule: "global", ratio });
|
|
6968
|
+
return restore(out2);
|
|
6969
|
+
}
|
|
6957
6970
|
const rebuilt = blocks.map((block) => {
|
|
6958
6971
|
const lines = block.split(`
|
|
6959
6972
|
`).filter((l) => l.trim() !== "");
|
|
@@ -6970,17 +6983,23 @@ function stripExcessBold(text) {
|
|
|
6970
6983
|
return block;
|
|
6971
6984
|
if (!lines.every((l) => isFullyBolded(l)))
|
|
6972
6985
|
return block;
|
|
6973
|
-
if (
|
|
6986
|
+
if (isPseudoHeadingBlock(block))
|
|
6974
6987
|
return block;
|
|
6975
6988
|
return unbold(block);
|
|
6976
6989
|
});
|
|
6990
|
+
const out = rejoinBlocks(masked, rebuilt);
|
|
6991
|
+
if (out !== masked)
|
|
6992
|
+
onStrip?.({ rule: "per-block", ratio });
|
|
6993
|
+
return restore(out);
|
|
6994
|
+
}
|
|
6995
|
+
function rejoinBlocks(masked, rebuilt) {
|
|
6977
6996
|
const seps = masked.match(/\n{2,}/g) ?? [];
|
|
6978
6997
|
let out = rebuilt[0] ?? "";
|
|
6979
6998
|
for (let i = 1;i < rebuilt.length; i++)
|
|
6980
6999
|
out += (seps[i - 1] ?? `
|
|
6981
7000
|
|
|
6982
7001
|
`) + rebuilt[i];
|
|
6983
|
-
return
|
|
7002
|
+
return out;
|
|
6984
7003
|
}
|
|
6985
7004
|
function isListItemLine(line) {
|
|
6986
7005
|
const t = line.trimStart();
|
|
@@ -7183,7 +7202,7 @@ function backOffOpenInline(text, cut) {
|
|
|
7183
7202
|
}
|
|
7184
7203
|
return earliest;
|
|
7185
7204
|
}
|
|
7186
|
-
var RICH_MESSAGE_MAX_CHARS = 32768, PARAGRAPH_SPACER = "\u00a0", INLINE_SPAN_PATTERNS;
|
|
7205
|
+
var RICH_MESSAGE_MAX_CHARS = 32768, PARAGRAPH_SPACER = "\u00a0", PSEUDO_HEADING_MAX_CHARS = 48, INLINE_SPAN_PATTERNS;
|
|
7187
7206
|
var init_format = __esm(() => {
|
|
7188
7207
|
INLINE_SPAN_PATTERNS = [
|
|
7189
7208
|
/`[^`\n]+`/g,
|
|
@@ -12660,16 +12679,22 @@ var init_patterns = __esm(() => {
|
|
|
12660
12679
|
},
|
|
12661
12680
|
{
|
|
12662
12681
|
rule_id: "bearer_auth_header",
|
|
12663
|
-
regex: /Authorization\s*[:=]\s*Bearer\s+([A-Za-z0-9._\-+=]+)/
|
|
12682
|
+
regex: /Authorization\s*[:=]\s*Bearer\s+([A-Za-z0-9._\-+=]+)/gi,
|
|
12664
12683
|
captureIndex: 1,
|
|
12665
12684
|
slugHint: "bearer_token"
|
|
12666
12685
|
},
|
|
12667
12686
|
{
|
|
12668
12687
|
rule_id: "bearer_loose",
|
|
12669
|
-
regex: /\bBearer\s+([A-Za-z0-9._\-+=]{18,})\b/
|
|
12688
|
+
regex: /\bBearer\s+([A-Za-z0-9._\-+=]{18,})\b/gi,
|
|
12670
12689
|
captureIndex: 1,
|
|
12671
12690
|
slugHint: "bearer_token"
|
|
12672
12691
|
},
|
|
12692
|
+
{
|
|
12693
|
+
rule_id: "basic_auth_header",
|
|
12694
|
+
regex: /Authorization\s*[:=]\s*Basic\s+([A-Za-z0-9+/=]{8,})/gi,
|
|
12695
|
+
captureIndex: 1,
|
|
12696
|
+
slugHint: "basic_auth"
|
|
12697
|
+
},
|
|
12673
12698
|
{
|
|
12674
12699
|
rule_id: "pem_private_key",
|
|
12675
12700
|
regex: /-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]+?-----END [A-Z ]*PRIVATE KEY-----/g,
|
|
@@ -12729,7 +12754,104 @@ function shannonEntropy(s) {
|
|
|
12729
12754
|
return h;
|
|
12730
12755
|
}
|
|
12731
12756
|
|
|
12757
|
+
// secret-detect/inert-values.ts
|
|
12758
|
+
function runIsCredentialShaped(run) {
|
|
12759
|
+
let classes = 0;
|
|
12760
|
+
if (/[a-z]/.test(run))
|
|
12761
|
+
classes++;
|
|
12762
|
+
if (/[A-Z]/.test(run))
|
|
12763
|
+
classes++;
|
|
12764
|
+
if (/[0-9]/.test(run))
|
|
12765
|
+
classes++;
|
|
12766
|
+
return run.length >= (classes >= 2 ? MIXED_CLASS_RUN_MIN : SINGLE_CLASS_RUN_MIN);
|
|
12767
|
+
}
|
|
12768
|
+
function hasCredentialShapedRun(value) {
|
|
12769
|
+
for (const run of value.match(CREDENTIAL_RUN_RE) ?? []) {
|
|
12770
|
+
if (runIsCredentialShaped(run))
|
|
12771
|
+
return true;
|
|
12772
|
+
}
|
|
12773
|
+
return false;
|
|
12774
|
+
}
|
|
12775
|
+
function isInertValue(value) {
|
|
12776
|
+
if (!INERT_VALUE_RE.some((re) => re.test(value)))
|
|
12777
|
+
return false;
|
|
12778
|
+
return !hasCredentialShapedRun(value);
|
|
12779
|
+
}
|
|
12780
|
+
function stripTrailingPunctuation(value) {
|
|
12781
|
+
return value.replace(TRAILING_PUNCT_RE, "");
|
|
12782
|
+
}
|
|
12783
|
+
var INERT_VALUE_RE, CREDENTIAL_RUN_RE, MIXED_CLASS_RUN_MIN = 12, SINGLE_CLASS_RUN_MIN = 16, INERT_GATED_RULES, TRAILING_PUNCT_RE;
|
|
12784
|
+
var init_inert_values = __esm(() => {
|
|
12785
|
+
INERT_VALUE_RE = [
|
|
12786
|
+
/^\[REDACTED(?::[A-Za-z0-9_]+)?\]$/i,
|
|
12787
|
+
/^\$\{?[A-Za-z_][A-Za-z0-9_]*\}?$/,
|
|
12788
|
+
/^\{\{[^\n\r\u2028\u2029]*\}\}$/,
|
|
12789
|
+
/^<[^<>\n\r\u2028\u2029]*>$/,
|
|
12790
|
+
/^%[A-Za-z_][A-Za-z0-9_]*%$/,
|
|
12791
|
+
/^vault:[A-Za-z0-9_./-]*$/i,
|
|
12792
|
+
/^[*x\u2022.]+$/i,
|
|
12793
|
+
/^(?:process\.env|import\.meta\.env|os\.environ)(?:\.[A-Za-z_$][A-Za-z0-9_$]*|\[["']?[A-Za-z_$][A-Za-z0-9_$]*["']?\])*$/,
|
|
12794
|
+
/^(?:changeme|change-me|replaceme|replace-me|placeholder|todo|tbd|yourkey|your-key|yourpassword|your-password|yoursecret|your-secret|yourtoken|your-token)(?:[-_][A-Za-z0-9-]+)?$/i,
|
|
12795
|
+
/^[a-z]+(?: [a-z]+){2,}$/
|
|
12796
|
+
];
|
|
12797
|
+
CREDENTIAL_RUN_RE = /[A-Za-z0-9]{12,}/g;
|
|
12798
|
+
INERT_GATED_RULES = new Set([
|
|
12799
|
+
"env_key_value",
|
|
12800
|
+
"json_secret_field",
|
|
12801
|
+
"cli_flag"
|
|
12802
|
+
]);
|
|
12803
|
+
TRAILING_PUNCT_RE = /[.,;:!?)\]}]+$/;
|
|
12804
|
+
});
|
|
12805
|
+
|
|
12732
12806
|
// secret-detect/kv-scanner.ts
|
|
12807
|
+
function charClassCount(value) {
|
|
12808
|
+
let n = 0;
|
|
12809
|
+
if (/[a-z]/.test(value))
|
|
12810
|
+
n++;
|
|
12811
|
+
if (/[A-Z]/.test(value))
|
|
12812
|
+
n++;
|
|
12813
|
+
if (/[0-9]/.test(value))
|
|
12814
|
+
n++;
|
|
12815
|
+
if (/[^A-Za-z0-9]/.test(value))
|
|
12816
|
+
n++;
|
|
12817
|
+
return n;
|
|
12818
|
+
}
|
|
12819
|
+
function looksLikeMemorablePassword(value) {
|
|
12820
|
+
if (isInertValue(value))
|
|
12821
|
+
return false;
|
|
12822
|
+
const core = stripTrailingPunctuation(value);
|
|
12823
|
+
if (core.length < 8 || core.length > 64)
|
|
12824
|
+
return false;
|
|
12825
|
+
if (isInertValue(core))
|
|
12826
|
+
return false;
|
|
12827
|
+
if (new Set(core).size < 4)
|
|
12828
|
+
return false;
|
|
12829
|
+
return charClassCount(core) >= MEMORABLE_PW_MIN_CLASSES;
|
|
12830
|
+
}
|
|
12831
|
+
function scanMemorablePasswords(text) {
|
|
12832
|
+
const hits = [];
|
|
12833
|
+
MEMORABLE_PW_RE.lastIndex = 0;
|
|
12834
|
+
let m;
|
|
12835
|
+
while ((m = MEMORABLE_PW_RE.exec(text)) !== null) {
|
|
12836
|
+
const keyName = m[1];
|
|
12837
|
+
const value = m[3];
|
|
12838
|
+
if (!value || !looksLikeMemorablePassword(value))
|
|
12839
|
+
continue;
|
|
12840
|
+
const valueOffsetInMatch = m[0].indexOf(value, keyName.length);
|
|
12841
|
+
if (valueOffsetInMatch < 0)
|
|
12842
|
+
continue;
|
|
12843
|
+
const start = m.index + valueOffsetInMatch;
|
|
12844
|
+
hits.push({
|
|
12845
|
+
rule_id: MEMORABLE_PW_RULE_ID,
|
|
12846
|
+
start,
|
|
12847
|
+
end: start + value.length,
|
|
12848
|
+
matched_text: value,
|
|
12849
|
+
key_name: keyName,
|
|
12850
|
+
confidence: "ambiguous"
|
|
12851
|
+
});
|
|
12852
|
+
}
|
|
12853
|
+
return hits;
|
|
12854
|
+
}
|
|
12733
12855
|
function scanKeyValue(text) {
|
|
12734
12856
|
const hits = [];
|
|
12735
12857
|
KV_RE.lastIndex = 0;
|
|
@@ -12738,6 +12860,8 @@ function scanKeyValue(text) {
|
|
|
12738
12860
|
const [, keyName, value] = m;
|
|
12739
12861
|
if (!value)
|
|
12740
12862
|
continue;
|
|
12863
|
+
if (isInertValue(value))
|
|
12864
|
+
continue;
|
|
12741
12865
|
const h = shannonEntropy(value);
|
|
12742
12866
|
if (h < KV_ENTROPY_THRESHOLD)
|
|
12743
12867
|
continue;
|
|
@@ -12757,9 +12881,44 @@ function scanKeyValue(text) {
|
|
|
12757
12881
|
}
|
|
12758
12882
|
return hits;
|
|
12759
12883
|
}
|
|
12760
|
-
var KV_RE, KV_ENTROPY_THRESHOLD = 4;
|
|
12884
|
+
var KV_RE, KV_ENTROPY_THRESHOLD = 4, MEMORABLE_PW_RE, MEMORABLE_PW_RULE_ID = "memorable_password", MEMORABLE_PW_MIN_CLASSES = 2;
|
|
12761
12885
|
var init_kv_scanner = __esm(() => {
|
|
12886
|
+
init_inert_values();
|
|
12762
12887
|
KV_RE = /\b([A-Za-z_][A-Za-z0-9_-]*(?:password|passwd|token|secret|key|api[_-]?key))\s*[:=]\s*["']?([^\s"'\\]{8,})["']?/gi;
|
|
12888
|
+
MEMORABLE_PW_RE = /\b([A-Za-z0-9_-]*(?:password|passwd|passphrase|pwd))\b\s*(?:[:=]\s*|\s+is\s+)(["']?)([^\s"']{8,64})\2/gi;
|
|
12889
|
+
});
|
|
12890
|
+
|
|
12891
|
+
// secret-detect/db-uri.ts
|
|
12892
|
+
function isInertPassword(value) {
|
|
12893
|
+
if (value.startsWith("[REDACTED"))
|
|
12894
|
+
return true;
|
|
12895
|
+
return /^[*x]+$/i.test(value);
|
|
12896
|
+
}
|
|
12897
|
+
function scanDbUris(text) {
|
|
12898
|
+
const hits = [];
|
|
12899
|
+
DB_URI_RE.lastIndex = 0;
|
|
12900
|
+
let m;
|
|
12901
|
+
while ((m = DB_URI_RE.exec(text)) !== null) {
|
|
12902
|
+
const scheme = m[1];
|
|
12903
|
+
const user = m[2];
|
|
12904
|
+
const password = m[3];
|
|
12905
|
+
if (isInertPassword(password))
|
|
12906
|
+
continue;
|
|
12907
|
+
const start = m.index + scheme.length + 3 + user.length + 1;
|
|
12908
|
+
hits.push({
|
|
12909
|
+
rule_id: DB_URI_RULE_ID,
|
|
12910
|
+
start,
|
|
12911
|
+
end: start + password.length,
|
|
12912
|
+
matched_text: password,
|
|
12913
|
+
key_name: `${scheme}_password`,
|
|
12914
|
+
confidence: "ambiguous"
|
|
12915
|
+
});
|
|
12916
|
+
}
|
|
12917
|
+
return hits;
|
|
12918
|
+
}
|
|
12919
|
+
var DB_URI_RE, DB_URI_RULE_ID = "db_uri_password";
|
|
12920
|
+
var init_db_uri = __esm(() => {
|
|
12921
|
+
DB_URI_RE = /\b([a-zA-Z][a-zA-Z0-9+.-]+):\/\/([^\s:/@]+):([^\s/?#]+)@([^\s/@?#]+)/g;
|
|
12763
12922
|
});
|
|
12764
12923
|
|
|
12765
12924
|
// secret-detect/generic-entropy.ts
|
|
@@ -12953,6 +13112,8 @@ function detectSecrets(text) {
|
|
|
12953
13112
|
const globalStart = win.offset + matchStart;
|
|
12954
13113
|
const globalEnd = globalStart + cap.length;
|
|
12955
13114
|
const keyName = p.rule_id === "env_key_value" ? m[1] : undefined;
|
|
13115
|
+
if (INERT_GATED_RULES.has(p.rule_id) && isInertValue(cap))
|
|
13116
|
+
continue;
|
|
12956
13117
|
if (p.rule_id === "env_key_value") {
|
|
12957
13118
|
const ENV_KV_MIN_LEN = 12;
|
|
12958
13119
|
const ENV_KV_MIN_ENTROPY = 3.5;
|
|
@@ -12975,6 +13136,12 @@ function detectSecrets(text) {
|
|
|
12975
13136
|
for (const h of kvHits) {
|
|
12976
13137
|
raw.push({ ...h, start: h.start + win.offset, end: h.end + win.offset });
|
|
12977
13138
|
}
|
|
13139
|
+
for (const h of scanDbUris(win.text)) {
|
|
13140
|
+
raw.push({ ...h, start: h.start + win.offset, end: h.end + win.offset });
|
|
13141
|
+
}
|
|
13142
|
+
for (const h of scanMemorablePasswords(win.text)) {
|
|
13143
|
+
raw.push({ ...h, start: h.start + win.offset, end: h.end + win.offset });
|
|
13144
|
+
}
|
|
12978
13145
|
const genHits = scanGenericSecrets(win.text);
|
|
12979
13146
|
for (const h of genHits) {
|
|
12980
13147
|
raw.push({ ...h, start: h.start + win.offset, end: h.end + win.offset });
|
|
@@ -13024,6 +13191,8 @@ function dropOverlaps(hits) {
|
|
|
13024
13191
|
var init_secret_detect = __esm(() => {
|
|
13025
13192
|
init_patterns();
|
|
13026
13193
|
init_kv_scanner();
|
|
13194
|
+
init_db_uri();
|
|
13195
|
+
init_inert_values();
|
|
13027
13196
|
init_generic_entropy();
|
|
13028
13197
|
init_chunker();
|
|
13029
13198
|
init_suppressor();
|
|
@@ -45183,8 +45352,37 @@ function registerStartInfoCommands(bot, deps) {
|
|
|
45183
45352
|
init_rich_send();
|
|
45184
45353
|
init_format();
|
|
45185
45354
|
|
|
45355
|
+
// ../src/config/thinking-effort-risk.ts
|
|
45356
|
+
var CLAUDE_CLI_THINKING_MERGE_FIX_VERSION = "2.1.156";
|
|
45357
|
+
var RISKY_EFFORTS = new Set(["medium", "high", "xhigh", "max"]);
|
|
45358
|
+
function isRiskyThinkingEffort(effort) {
|
|
45359
|
+
if (!effort)
|
|
45360
|
+
return false;
|
|
45361
|
+
return RISKY_EFFORTS.has(effort.trim().toLowerCase());
|
|
45362
|
+
}
|
|
45363
|
+
function isAdaptiveThinkingOpus(model) {
|
|
45364
|
+
if (!model)
|
|
45365
|
+
return false;
|
|
45366
|
+
const m = model.trim().toLowerCase();
|
|
45367
|
+
return m.startsWith("claude-opus-4");
|
|
45368
|
+
}
|
|
45369
|
+
function assessThinkingEffortRisk(model, effort) {
|
|
45370
|
+
if (!isRiskyThinkingEffort(effort))
|
|
45371
|
+
return { risky: false };
|
|
45372
|
+
if (!isAdaptiveThinkingOpus(model))
|
|
45373
|
+
return { risky: false };
|
|
45374
|
+
return {
|
|
45375
|
+
risky: true,
|
|
45376
|
+
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.`
|
|
45377
|
+
};
|
|
45378
|
+
}
|
|
45379
|
+
|
|
45186
45380
|
// gateway/model-command.ts
|
|
45187
45381
|
var MODEL_ALIASES = ["opus", "sonnet", "haiku", "fable", "default"];
|
|
45382
|
+
var CLAUDE_MODEL_ALIASES = {
|
|
45383
|
+
opus48: "claude-opus-4-8",
|
|
45384
|
+
"opus-4-8": "claude-opus-4-8"
|
|
45385
|
+
};
|
|
45188
45386
|
var MODEL_ARG_RE = /^[A-Za-z0-9][A-Za-z0-9._/\[\]-]{0,99}$/;
|
|
45189
45387
|
function isValidModelArg(arg) {
|
|
45190
45388
|
return MODEL_ARG_RE.test(arg);
|
|
@@ -45218,7 +45416,7 @@ function planModelCommand(parsed, ctx) {
|
|
|
45218
45416
|
if (parsed.kind === "show" && ctx.menuEnabled)
|
|
45219
45417
|
return { kind: "menu" };
|
|
45220
45418
|
if (parsed.kind === "set" && isModelCommandBusy(ctx)) {
|
|
45221
|
-
return { kind: "queue", target:
|
|
45419
|
+
return { kind: "queue", target: expandModelAlias(parsed.model) };
|
|
45222
45420
|
}
|
|
45223
45421
|
return { kind: "apply", parsed };
|
|
45224
45422
|
}
|
|
@@ -45227,12 +45425,22 @@ function modelCommandReceiptLine(agent, parsed, busy) {
|
|
|
45227
45425
|
return `telegram gateway: gw /model received agent=${agent} kind=${parsed.kind} arg=${arg} busy=${busy}`;
|
|
45228
45426
|
}
|
|
45229
45427
|
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._";
|
|
45428
|
+
function claudeAliasHints(prefix) {
|
|
45429
|
+
const byTarget = new Map;
|
|
45430
|
+
for (const [alias, target] of Object.entries(CLAUDE_MODEL_ALIASES)) {
|
|
45431
|
+
const spellings = byTarget.get(target) ?? [];
|
|
45432
|
+
spellings.push(alias);
|
|
45433
|
+
byTarget.set(target, spellings);
|
|
45434
|
+
}
|
|
45435
|
+
return [...byTarget].map(([target, spellings]) => `${spellings.map((a) => `\`${prefix}${a}\``).join(" \u00b7 ")} \u2192 \`${target}\``).join("; ");
|
|
45436
|
+
}
|
|
45230
45437
|
function helpText2(deps, reason) {
|
|
45231
45438
|
const srAliasExamples = Object.keys(SR_MODEL_ALIASES).map((a) => `\`${a}\``).join(" \u00b7 ");
|
|
45439
|
+
const claudeAliasExamples = claudeAliasHints("");
|
|
45232
45440
|
const lines = [];
|
|
45233
45441
|
if (reason)
|
|
45234
45442
|
lines.push(`\u26a0\ufe0f ${deps.escapeHtml(reason)}`);
|
|
45235
|
-
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);
|
|
45443
|
+
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);
|
|
45236
45444
|
return { text: lines.join(`
|
|
45237
45445
|
`), html: true };
|
|
45238
45446
|
}
|
|
@@ -45248,6 +45456,7 @@ async function handleModelCommand(parsed, deps) {
|
|
|
45248
45456
|
`**Model \u2014 ${deps.escapeHtml(deps.getAgentName())}**`,
|
|
45249
45457
|
`Configured: \`${deps.escapeHtml(shown)}\``,
|
|
45250
45458
|
`Switch the live session: ${MODEL_ALIASES.map((a) => `\`/model ${a}\``).join(" \u00b7 ")}`,
|
|
45459
|
+
`Pinned Claude shortcuts: ${claudeAliasHints("/model ")}`,
|
|
45251
45460
|
`OpenRouter shortcuts: ${srAliasExamples}`,
|
|
45252
45461
|
"or `/model <full-model-id>`",
|
|
45253
45462
|
PERSIST_NOTE
|
|
@@ -45259,7 +45468,7 @@ async function handleModelCommand(parsed, deps) {
|
|
|
45259
45468
|
if (!isValidModelArg(parsed.model)) {
|
|
45260
45469
|
return helpText2(deps, `not a valid model name: ${parsed.model}`);
|
|
45261
45470
|
}
|
|
45262
|
-
const model =
|
|
45471
|
+
const model = expandModelAlias(parsed.model);
|
|
45263
45472
|
if (deps.isBusy()) {
|
|
45264
45473
|
return {
|
|
45265
45474
|
text: "\u23f3 The agent is mid-turn \u2014 a model switch needs an idle session. The switch was not applied.",
|
|
@@ -45286,10 +45495,25 @@ function relaunchErrorReply(deps, model, err) {
|
|
|
45286
45495
|
return { text: `\u274c Could not schedule model switch: ${deps.escapeHtml(msg)}`, html: true };
|
|
45287
45496
|
}
|
|
45288
45497
|
function unvalidatedIdCaveat(deps, model) {
|
|
45289
|
-
|
|
45498
|
+
const lower = canonicalModelToken(model).toLowerCase();
|
|
45499
|
+
if (!lower.startsWith("claude-"))
|
|
45500
|
+
return null;
|
|
45501
|
+
if (Object.values(CLAUDE_MODEL_ALIASES).includes(lower))
|
|
45290
45502
|
return null;
|
|
45291
45503
|
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._`;
|
|
45292
45504
|
}
|
|
45505
|
+
function thinkingEffortCaveat(deps, model) {
|
|
45506
|
+
let effort;
|
|
45507
|
+
try {
|
|
45508
|
+
effort = deps.getConfiguredEffort();
|
|
45509
|
+
} catch {
|
|
45510
|
+
return null;
|
|
45511
|
+
}
|
|
45512
|
+
const risk = assessThinkingEffortRisk(model, effort ?? undefined);
|
|
45513
|
+
if (!risk.risky || !risk.reason)
|
|
45514
|
+
return null;
|
|
45515
|
+
return `\u26a0\ufe0f _${deps.escapeHtml(risk.reason)}_`;
|
|
45516
|
+
}
|
|
45293
45517
|
async function scheduleRelaunchReply(deps, model, reason) {
|
|
45294
45518
|
try {
|
|
45295
45519
|
await deps.scheduleModelRelaunch(model, reason);
|
|
@@ -45297,8 +45521,14 @@ async function scheduleRelaunchReply(deps, model, reason) {
|
|
|
45297
45521
|
return relaunchErrorReply(deps, model, err);
|
|
45298
45522
|
}
|
|
45299
45523
|
const caveat = unvalidatedIdCaveat(deps, model);
|
|
45524
|
+
const effortRisk = thinkingEffortCaveat(deps, model);
|
|
45300
45525
|
return {
|
|
45301
|
-
text: [
|
|
45526
|
+
text: [
|
|
45527
|
+
switchingLine(deps, model),
|
|
45528
|
+
...caveat ? [caveat] : [],
|
|
45529
|
+
...effortRisk ? [effortRisk] : [],
|
|
45530
|
+
PERSIST_NOTE
|
|
45531
|
+
].join(`
|
|
45302
45532
|
`),
|
|
45303
45533
|
html: true
|
|
45304
45534
|
};
|
|
@@ -45325,8 +45555,9 @@ var MODEL_CALLBACK_HEADER = "mdl:h";
|
|
|
45325
45555
|
var MODEL_CALLBACK_ALIAS = "mdl:alias:";
|
|
45326
45556
|
var MODEL_CALLBACK_PAGE_EXTERNAL = "mdl:page:ext";
|
|
45327
45557
|
var MODEL_CALLBACK_PAGE_MAIN = "mdl:page:main";
|
|
45328
|
-
var
|
|
45329
|
-
{
|
|
45558
|
+
var EXTRA_CLAUDE_MENU_TOKENS = [
|
|
45559
|
+
{ token: "fable", label: "Fable" },
|
|
45560
|
+
{ token: "claude-opus-4-8", label: "Opus 4.8" }
|
|
45330
45561
|
];
|
|
45331
45562
|
var SR_MODEL_LABELS = {
|
|
45332
45563
|
"sr-gemini-2.5-pro": "Gemini 2.5 Pro",
|
|
@@ -45368,6 +45599,17 @@ var SR_MODEL_ALIASES = {
|
|
|
45368
45599
|
function expandSrAlias(arg) {
|
|
45369
45600
|
return SR_MODEL_ALIASES[arg.toLowerCase()] ?? arg;
|
|
45370
45601
|
}
|
|
45602
|
+
function canonicalModelToken(arg) {
|
|
45603
|
+
const trimmed = arg.trim();
|
|
45604
|
+
const lower = trimmed.toLowerCase();
|
|
45605
|
+
return lower.startsWith("claude-") ? lower : trimmed;
|
|
45606
|
+
}
|
|
45607
|
+
function expandClaudeAlias(arg) {
|
|
45608
|
+
return CLAUDE_MODEL_ALIASES[arg.trim().toLowerCase()] ?? arg;
|
|
45609
|
+
}
|
|
45610
|
+
function expandModelAlias(arg) {
|
|
45611
|
+
return canonicalModelToken(expandSrAlias(expandClaudeAlias(arg)));
|
|
45612
|
+
}
|
|
45371
45613
|
function srFriendlyLabel(srName) {
|
|
45372
45614
|
return SR_MODEL_LABELS[srName] ?? srName.replace(/^sr-/, "").replace(/-/g, " ");
|
|
45373
45615
|
}
|
|
@@ -45409,6 +45651,11 @@ function busyStaticMenu(deps, page) {
|
|
|
45409
45651
|
callback_data: `${MODEL_CALLBACK_ALIAS}${alias}`
|
|
45410
45652
|
}]);
|
|
45411
45653
|
}
|
|
45654
|
+
for (const { token, label } of EXTRA_CLAUDE_MENU_TOKENS) {
|
|
45655
|
+
if (MODEL_ALIASES.includes(token.toLowerCase()))
|
|
45656
|
+
continue;
|
|
45657
|
+
rows.push([{ text: label, callback_data: `${MODEL_CALLBACK_ALIAS}${token}` }]);
|
|
45658
|
+
}
|
|
45412
45659
|
rows.push([{ text: "Default (configured)", callback_data: `${MODEL_CALLBACK_ALIAS}default` }]);
|
|
45413
45660
|
if (externalNames.length > 0) {
|
|
45414
45661
|
rows.push([{ text: "\uD83C\uDF10 External models \u25b8", callback_data: MODEL_CALLBACK_PAGE_EXTERNAL }]);
|
|
@@ -45445,11 +45692,11 @@ function mainPageKeyboard(claudeOptions, hasExternal) {
|
|
|
45445
45692
|
callback_data: modelSelectCallbackData(o.label)
|
|
45446
45693
|
}]);
|
|
45447
45694
|
}
|
|
45448
|
-
for (const {
|
|
45449
|
-
const already = claudeOptions.some((o) => o.label.toLowerCase() === label.toLowerCase() || o.label.toLowerCase() ===
|
|
45695
|
+
for (const { token, label } of EXTRA_CLAUDE_MENU_TOKENS) {
|
|
45696
|
+
const already = claudeOptions.some((o) => o.label.toLowerCase() === label.toLowerCase() || o.label.toLowerCase() === token.toLowerCase());
|
|
45450
45697
|
if (already)
|
|
45451
45698
|
continue;
|
|
45452
|
-
rows.push([{ text: label, callback_data: `${MODEL_CALLBACK_ALIAS}${
|
|
45699
|
+
rows.push([{ text: label, callback_data: `${MODEL_CALLBACK_ALIAS}${token}` }]);
|
|
45453
45700
|
}
|
|
45454
45701
|
if (hasExternal) {
|
|
45455
45702
|
rows.push([{ text: "\uD83C\uDF10 External models \u25b8", callback_data: MODEL_CALLBACK_PAGE_EXTERNAL }]);
|
|
@@ -70961,6 +71208,23 @@ function describeProviderCreditRemedy(provider) {
|
|
|
70961
71208
|
}
|
|
70962
71209
|
|
|
70963
71210
|
// operator-events.ts
|
|
71211
|
+
var OPERATOR_EVENT_KINDS = [
|
|
71212
|
+
"credentials-expired",
|
|
71213
|
+
"credentials-invalid",
|
|
71214
|
+
"proxy-misconfig",
|
|
71215
|
+
"credit-exhausted",
|
|
71216
|
+
"provider-credit-exhausted",
|
|
71217
|
+
"mcp-dependency-blocked",
|
|
71218
|
+
"quota-exhausted",
|
|
71219
|
+
"rate-limited",
|
|
71220
|
+
"agent-crashed",
|
|
71221
|
+
"agent-restarted-unexpectedly",
|
|
71222
|
+
"unknown-4xx",
|
|
71223
|
+
"unknown-5xx",
|
|
71224
|
+
"config-warning",
|
|
71225
|
+
"always-allow-persist-failed",
|
|
71226
|
+
"mental-model-persist-failed"
|
|
71227
|
+
];
|
|
70964
71228
|
function classifyClaudeError(raw) {
|
|
70965
71229
|
try {
|
|
70966
71230
|
return classifyInner(raw);
|
|
@@ -77570,7 +77834,12 @@ function normalizeOutboundBody(rawText, site, redact2, opts = {}) {
|
|
|
77570
77834
|
text4 = normalizeParagraphBreaks(text4);
|
|
77571
77835
|
text4 = redact2(text4, site);
|
|
77572
77836
|
if (!literalText) {
|
|
77573
|
-
let formatted = stripExcessBold(normalizePunctuation(text4))
|
|
77837
|
+
let formatted = stripExcessBold(normalizePunctuation(text4), (d) => {
|
|
77838
|
+
try {
|
|
77839
|
+
process.stderr.write(`telegram gateway: strip-excess-bold: fired site=${site} rule=${d.rule} ratio=${d.ratio.toFixed(3)}
|
|
77840
|
+
`);
|
|
77841
|
+
} catch {}
|
|
77842
|
+
});
|
|
77574
77843
|
if (addSpacers)
|
|
77575
77844
|
formatted = addParagraphSpacers(formatted);
|
|
77576
77845
|
text4 = formatted;
|
|
@@ -79016,7 +79285,12 @@ function normalizeOutboundBody2(rawText, site, redact2, opts = {}) {
|
|
|
79016
79285
|
text4 = normalizeParagraphBreaks(text4);
|
|
79017
79286
|
text4 = redact2(text4, site);
|
|
79018
79287
|
if (!literalText) {
|
|
79019
|
-
let formatted = stripExcessBold(normalizePunctuation(text4))
|
|
79288
|
+
let formatted = stripExcessBold(normalizePunctuation(text4), (d) => {
|
|
79289
|
+
try {
|
|
79290
|
+
process.stderr.write(`telegram gateway: strip-excess-bold: fired site=${site} rule=${d.rule} ratio=${d.ratio.toFixed(3)}
|
|
79291
|
+
`);
|
|
79292
|
+
} catch {}
|
|
79293
|
+
});
|
|
79020
79294
|
if (addSpacers)
|
|
79021
79295
|
formatted = addParagraphSpacers(formatted);
|
|
79022
79296
|
text4 = formatted;
|
|
@@ -83497,10 +83771,35 @@ Allowed: \`${deps.escapeHtml(allow)}\``, { html: true });
|
|
|
83497
83771
|
|
|
83498
83772
|
// gateway/model-command.ts
|
|
83499
83773
|
var MODEL_ALIASES2 = ["opus", "sonnet", "haiku", "fable", "default"];
|
|
83774
|
+
var CLAUDE_MODEL_ALIASES2 = {
|
|
83775
|
+
opus48: "claude-opus-4-8",
|
|
83776
|
+
"opus-4-8": "claude-opus-4-8"
|
|
83777
|
+
};
|
|
83500
83778
|
var MODEL_ARG_RE2 = /^[A-Za-z0-9][A-Za-z0-9._/\[\]-]{0,99}$/;
|
|
83501
83779
|
function isValidModelArg2(arg) {
|
|
83502
83780
|
return MODEL_ARG_RE2.test(arg);
|
|
83503
83781
|
}
|
|
83782
|
+
var DEFAULT_SESSION_MODEL_RESOLUTION_TIMEOUT_MS = 180000;
|
|
83783
|
+
var SESSION_MODEL_RESOLUTION_POLL_MS = 250;
|
|
83784
|
+
function resolveSessionModelResolutionTimeoutMs(raw) {
|
|
83785
|
+
if (raw == null || raw.trim() === "")
|
|
83786
|
+
return DEFAULT_SESSION_MODEL_RESOLUTION_TIMEOUT_MS;
|
|
83787
|
+
const value = Number(raw);
|
|
83788
|
+
return Number.isFinite(value) && value >= 0 ? value : DEFAULT_SESSION_MODEL_RESOLUTION_TIMEOUT_MS;
|
|
83789
|
+
}
|
|
83790
|
+
async function waitForSessionModelResolution(input) {
|
|
83791
|
+
const pollMs = input.pollMs ?? SESSION_MODEL_RESOLUTION_POLL_MS;
|
|
83792
|
+
const sleep2 = input.sleep ?? ((ms) => new Promise((resolve7) => setTimeout(resolve7, ms)));
|
|
83793
|
+
const startedAt = Date.now();
|
|
83794
|
+
for (;; ) {
|
|
83795
|
+
if (input.barrierExists())
|
|
83796
|
+
return true;
|
|
83797
|
+
const remaining = input.timeoutMs - (Date.now() - startedAt);
|
|
83798
|
+
if (remaining <= 0)
|
|
83799
|
+
return false;
|
|
83800
|
+
await sleep2(Math.min(pollMs, remaining));
|
|
83801
|
+
}
|
|
83802
|
+
}
|
|
83504
83803
|
function isSrModel2(name) {
|
|
83505
83804
|
return name.startsWith("sr-");
|
|
83506
83805
|
}
|
|
@@ -83632,12 +83931,22 @@ function resolveStaleAwareBusy(input) {
|
|
|
83632
83931
|
};
|
|
83633
83932
|
}
|
|
83634
83933
|
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._";
|
|
83934
|
+
function claudeAliasHints2(prefix) {
|
|
83935
|
+
const byTarget = new Map;
|
|
83936
|
+
for (const [alias, target] of Object.entries(CLAUDE_MODEL_ALIASES2)) {
|
|
83937
|
+
const spellings = byTarget.get(target) ?? [];
|
|
83938
|
+
spellings.push(alias);
|
|
83939
|
+
byTarget.set(target, spellings);
|
|
83940
|
+
}
|
|
83941
|
+
return [...byTarget].map(([target, spellings]) => `${spellings.map((a) => `\`${prefix}${a}\``).join(" \u00b7 ")} \u2192 \`${target}\``).join("; ");
|
|
83942
|
+
}
|
|
83635
83943
|
function helpText4(deps, reason) {
|
|
83636
83944
|
const srAliasExamples = Object.keys(SR_MODEL_ALIASES2).map((a) => `\`${a}\``).join(" \u00b7 ");
|
|
83945
|
+
const claudeAliasExamples = claudeAliasHints2("");
|
|
83637
83946
|
const lines = [];
|
|
83638
83947
|
if (reason)
|
|
83639
83948
|
lines.push(`\u26a0\ufe0f ${deps.escapeHtml(reason)}`);
|
|
83640
|
-
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);
|
|
83949
|
+
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);
|
|
83641
83950
|
return { text: lines.join(`
|
|
83642
83951
|
`), html: true };
|
|
83643
83952
|
}
|
|
@@ -83653,6 +83962,7 @@ async function handleModelCommand2(parsed, deps) {
|
|
|
83653
83962
|
`**Model \u2014 ${deps.escapeHtml(deps.getAgentName())}**`,
|
|
83654
83963
|
`Configured: \`${deps.escapeHtml(shown)}\``,
|
|
83655
83964
|
`Switch the live session: ${MODEL_ALIASES2.map((a) => `\`/model ${a}\``).join(" \u00b7 ")}`,
|
|
83965
|
+
`Pinned Claude shortcuts: ${claudeAliasHints2("/model ")}`,
|
|
83656
83966
|
`OpenRouter shortcuts: ${srAliasExamples}`,
|
|
83657
83967
|
"or `/model <full-model-id>`",
|
|
83658
83968
|
PERSIST_NOTE3
|
|
@@ -83664,7 +83974,7 @@ async function handleModelCommand2(parsed, deps) {
|
|
|
83664
83974
|
if (!isValidModelArg2(parsed.model)) {
|
|
83665
83975
|
return helpText4(deps, `not a valid model name: ${parsed.model}`);
|
|
83666
83976
|
}
|
|
83667
|
-
const model =
|
|
83977
|
+
const model = expandModelAlias2(parsed.model);
|
|
83668
83978
|
if (deps.isBusy()) {
|
|
83669
83979
|
return {
|
|
83670
83980
|
text: "\u23f3 The agent is mid-turn \u2014 a model switch needs an idle session. The switch was not applied.",
|
|
@@ -83691,10 +84001,25 @@ function relaunchErrorReply2(deps, model, err) {
|
|
|
83691
84001
|
return { text: `\u274c Could not schedule model switch: ${deps.escapeHtml(msg)}`, html: true };
|
|
83692
84002
|
}
|
|
83693
84003
|
function unvalidatedIdCaveat2(deps, model) {
|
|
83694
|
-
|
|
84004
|
+
const lower = canonicalModelToken2(model).toLowerCase();
|
|
84005
|
+
if (!lower.startsWith("claude-"))
|
|
84006
|
+
return null;
|
|
84007
|
+
if (Object.values(CLAUDE_MODEL_ALIASES2).includes(lower))
|
|
83695
84008
|
return null;
|
|
83696
84009
|
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._`;
|
|
83697
84010
|
}
|
|
84011
|
+
function thinkingEffortCaveat2(deps, model) {
|
|
84012
|
+
let effort;
|
|
84013
|
+
try {
|
|
84014
|
+
effort = deps.getConfiguredEffort();
|
|
84015
|
+
} catch {
|
|
84016
|
+
return null;
|
|
84017
|
+
}
|
|
84018
|
+
const risk = assessThinkingEffortRisk(model, effort ?? undefined);
|
|
84019
|
+
if (!risk.risky || !risk.reason)
|
|
84020
|
+
return null;
|
|
84021
|
+
return `\u26a0\ufe0f _${deps.escapeHtml(risk.reason)}_`;
|
|
84022
|
+
}
|
|
83698
84023
|
async function scheduleRelaunchReply2(deps, model, reason) {
|
|
83699
84024
|
try {
|
|
83700
84025
|
await deps.scheduleModelRelaunch(model, reason);
|
|
@@ -83702,8 +84027,14 @@ async function scheduleRelaunchReply2(deps, model, reason) {
|
|
|
83702
84027
|
return relaunchErrorReply2(deps, model, err);
|
|
83703
84028
|
}
|
|
83704
84029
|
const caveat = unvalidatedIdCaveat2(deps, model);
|
|
84030
|
+
const effortRisk = thinkingEffortCaveat2(deps, model);
|
|
83705
84031
|
return {
|
|
83706
|
-
text: [
|
|
84032
|
+
text: [
|
|
84033
|
+
switchingLine2(deps, model),
|
|
84034
|
+
...caveat ? [caveat] : [],
|
|
84035
|
+
...effortRisk ? [effortRisk] : [],
|
|
84036
|
+
PERSIST_NOTE3
|
|
84037
|
+
].join(`
|
|
83707
84038
|
`),
|
|
83708
84039
|
html: true
|
|
83709
84040
|
};
|
|
@@ -83731,8 +84062,9 @@ var MODEL_CALLBACK_HEADER2 = "mdl:h";
|
|
|
83731
84062
|
var MODEL_CALLBACK_ALIAS2 = "mdl:alias:";
|
|
83732
84063
|
var MODEL_CALLBACK_PAGE_EXTERNAL2 = "mdl:page:ext";
|
|
83733
84064
|
var MODEL_CALLBACK_PAGE_MAIN2 = "mdl:page:main";
|
|
83734
|
-
var
|
|
83735
|
-
{
|
|
84065
|
+
var EXTRA_CLAUDE_MENU_TOKENS2 = [
|
|
84066
|
+
{ token: "fable", label: "Fable" },
|
|
84067
|
+
{ token: "claude-opus-4-8", label: "Opus 4.8" }
|
|
83736
84068
|
];
|
|
83737
84069
|
var SR_MODEL_LABELS2 = {
|
|
83738
84070
|
"sr-gemini-2.5-pro": "Gemini 2.5 Pro",
|
|
@@ -83777,11 +84109,26 @@ function isOfflineTrustedModelToken(token) {
|
|
|
83777
84109
|
return true;
|
|
83778
84110
|
if (lower in SR_MODEL_ALIASES2)
|
|
83779
84111
|
return true;
|
|
83780
|
-
|
|
84112
|
+
if (Object.values(SR_MODEL_ALIASES2).includes(lower))
|
|
84113
|
+
return true;
|
|
84114
|
+
if (lower in CLAUDE_MODEL_ALIASES2)
|
|
84115
|
+
return true;
|
|
84116
|
+
return Object.values(CLAUDE_MODEL_ALIASES2).includes(lower);
|
|
83781
84117
|
}
|
|
83782
84118
|
function expandSrAlias2(arg) {
|
|
83783
84119
|
return SR_MODEL_ALIASES2[arg.toLowerCase()] ?? arg;
|
|
83784
84120
|
}
|
|
84121
|
+
function canonicalModelToken2(arg) {
|
|
84122
|
+
const trimmed = arg.trim();
|
|
84123
|
+
const lower = trimmed.toLowerCase();
|
|
84124
|
+
return lower.startsWith("claude-") ? lower : trimmed;
|
|
84125
|
+
}
|
|
84126
|
+
function expandClaudeAlias2(arg) {
|
|
84127
|
+
return CLAUDE_MODEL_ALIASES2[arg.trim().toLowerCase()] ?? arg;
|
|
84128
|
+
}
|
|
84129
|
+
function expandModelAlias2(arg) {
|
|
84130
|
+
return canonicalModelToken2(expandSrAlias2(expandClaudeAlias2(arg)));
|
|
84131
|
+
}
|
|
83785
84132
|
function srFriendlyLabel2(srName) {
|
|
83786
84133
|
return SR_MODEL_LABELS2[srName] ?? srName.replace(/^sr-/, "").replace(/-/g, " ");
|
|
83787
84134
|
}
|
|
@@ -83834,6 +84181,11 @@ function busyStaticMenu2(deps, page) {
|
|
|
83834
84181
|
callback_data: `${MODEL_CALLBACK_ALIAS2}${alias}`
|
|
83835
84182
|
}]);
|
|
83836
84183
|
}
|
|
84184
|
+
for (const { token, label } of EXTRA_CLAUDE_MENU_TOKENS2) {
|
|
84185
|
+
if (MODEL_ALIASES2.includes(token.toLowerCase()))
|
|
84186
|
+
continue;
|
|
84187
|
+
rows.push([{ text: label, callback_data: `${MODEL_CALLBACK_ALIAS2}${token}` }]);
|
|
84188
|
+
}
|
|
83837
84189
|
rows.push([{ text: "Default (configured)", callback_data: `${MODEL_CALLBACK_ALIAS2}default` }]);
|
|
83838
84190
|
if (externalNames.length > 0) {
|
|
83839
84191
|
rows.push([{ text: "\uD83C\uDF10 External models \u25b8", callback_data: MODEL_CALLBACK_PAGE_EXTERNAL2 }]);
|
|
@@ -83870,11 +84222,11 @@ function mainPageKeyboard2(claudeOptions, hasExternal) {
|
|
|
83870
84222
|
callback_data: modelSelectCallbackData2(o.label)
|
|
83871
84223
|
}]);
|
|
83872
84224
|
}
|
|
83873
|
-
for (const {
|
|
83874
|
-
const already = claudeOptions.some((o) => o.label.toLowerCase() === label.toLowerCase() || o.label.toLowerCase() ===
|
|
84225
|
+
for (const { token, label } of EXTRA_CLAUDE_MENU_TOKENS2) {
|
|
84226
|
+
const already = claudeOptions.some((o) => o.label.toLowerCase() === label.toLowerCase() || o.label.toLowerCase() === token.toLowerCase());
|
|
83875
84227
|
if (already)
|
|
83876
84228
|
continue;
|
|
83877
|
-
rows.push([{ text: label, callback_data: `${MODEL_CALLBACK_ALIAS2}${
|
|
84229
|
+
rows.push([{ text: label, callback_data: `${MODEL_CALLBACK_ALIAS2}${token}` }]);
|
|
83878
84230
|
}
|
|
83879
84231
|
if (hasExternal) {
|
|
83880
84232
|
rows.push([{ text: "\uD83C\uDF10 External models \u25b8", callback_data: MODEL_CALLBACK_PAGE_EXTERNAL2 }]);
|
|
@@ -83973,6 +84325,7 @@ async function handleModelMenuCallback(data, deps) {
|
|
|
83973
84325
|
if (!isValidModelArg2(token)) {
|
|
83974
84326
|
return { answer: "Invalid model name", reply: await buildModelMenu2(deps) };
|
|
83975
84327
|
}
|
|
84328
|
+
token = expandModelAlias2(token);
|
|
83976
84329
|
if (!isRecognizedSwitchToken(token)) {
|
|
83977
84330
|
return { answer: "Model list changed \u2014 menu refreshed", reply: await buildModelMenu2(deps) };
|
|
83978
84331
|
}
|
|
@@ -84010,9 +84363,11 @@ async function menuRelaunchOutcome(deps, token, label) {
|
|
|
84010
84363
|
};
|
|
84011
84364
|
}
|
|
84012
84365
|
const friendly = isDefault ? "the configured default" : label;
|
|
84366
|
+
const effortRisk = isDefault ? null : thinkingEffortCaveat2(deps, token);
|
|
84013
84367
|
return {
|
|
84014
84368
|
answer: `Switching to ${isDefault ? "default" : label} \u2014 relaunching (~30s)`,
|
|
84015
|
-
reply: await menuWithBannerStatic(deps, `\uD83D\uDD04 Switching session to **${deps.escapeHtml(friendly)}** \u2014 relaunching (~30s)
|
|
84369
|
+
reply: await menuWithBannerStatic(deps, `\uD83D\uDD04 Switching session to **${deps.escapeHtml(friendly)}** \u2014 relaunching (~30s).${effortRisk ? `
|
|
84370
|
+
${effortRisk}` : ""}
|
|
84016
84371
|
${PERSIST_NOTE3}`)
|
|
84017
84372
|
};
|
|
84018
84373
|
}
|
|
@@ -84586,12 +84941,15 @@ function pgMib(mib) {
|
|
|
84586
84941
|
}
|
|
84587
84942
|
var HINDSIGHT_PG_EFFECTIVE_CACHE_SIZE_ENV = "SWITCHROOM_HINDSIGHT_PG_EFFECTIVE_CACHE_SIZE";
|
|
84588
84943
|
var HINDSIGHT_PG_SHARED_BUFFERS_ENV = "SWITCHROOM_HINDSIGHT_PG_SHARED_BUFFERS";
|
|
84944
|
+
var HINDSIGHT_PG_FSYNC_ENV = "SWITCHROOM_HINDSIGHT_PG_FSYNC";
|
|
84945
|
+
var HINDSIGHT_PG_DEFAULT_FSYNC = "on";
|
|
84589
84946
|
var HINDSIGHT_PG_DEFAULTS = [
|
|
84590
84947
|
[
|
|
84591
84948
|
HINDSIGHT_PG_EFFECTIVE_CACHE_SIZE_ENV,
|
|
84592
84949
|
pgMib(HINDSIGHT_PG_DEFAULT_EFFECTIVE_CACHE_SIZE_MIB)
|
|
84593
84950
|
],
|
|
84594
|
-
[HINDSIGHT_PG_SHARED_BUFFERS_ENV, pgMib(HINDSIGHT_PG_DEFAULT_SHARED_BUFFERS_MIB)]
|
|
84951
|
+
[HINDSIGHT_PG_SHARED_BUFFERS_ENV, pgMib(HINDSIGHT_PG_DEFAULT_SHARED_BUFFERS_MIB)],
|
|
84952
|
+
[HINDSIGHT_PG_FSYNC_ENV, HINDSIGHT_PG_DEFAULT_FSYNC]
|
|
84595
84953
|
];
|
|
84596
84954
|
var HINDSIGHT_PG_ENV_KEYS = new Set(HINDSIGHT_PG_DEFAULTS.map(([k]) => k));
|
|
84597
84955
|
|
|
@@ -88706,17 +89064,7 @@ function recordWebhookEvent(rec, deps = {}) {
|
|
|
88706
89064
|
init_format();
|
|
88707
89065
|
import { renameSync as renameSync17, unlinkSync as unlinkSync20, chmodSync as chmodSync11 } from "fs";
|
|
88708
89066
|
var MAX_BUFFER_SIZE = 1024 * 1024;
|
|
88709
|
-
var VALID_OPERATOR_KINDS = new Set(
|
|
88710
|
-
"credentials-expired",
|
|
88711
|
-
"credentials-invalid",
|
|
88712
|
-
"credit-exhausted",
|
|
88713
|
-
"quota-exhausted",
|
|
88714
|
-
"rate-limited",
|
|
88715
|
-
"agent-crashed",
|
|
88716
|
-
"agent-restarted-unexpectedly",
|
|
88717
|
-
"unknown-4xx",
|
|
88718
|
-
"unknown-5xx"
|
|
88719
|
-
]);
|
|
89067
|
+
var VALID_OPERATOR_KINDS = new Set(OPERATOR_EVENT_KINDS);
|
|
88720
89068
|
var AGENT_NAME_RE3 = /^[a-z0-9][a-z0-9_-]{0,50}$/;
|
|
88721
89069
|
var OPERATOR_EVENT_DETAIL_MAX = 1000;
|
|
88722
89070
|
function validateClientMessage(msg) {
|
|
@@ -99320,10 +99668,10 @@ function startOutboxSweep(deps) {
|
|
|
99320
99668
|
}
|
|
99321
99669
|
|
|
99322
99670
|
// ../src/build-info.ts
|
|
99323
|
-
var VERSION2 = "0.19.
|
|
99324
|
-
var COMMIT_SHA = "
|
|
99325
|
-
var COMMIT_DATE = "2026-07-
|
|
99326
|
-
var LATEST_PR =
|
|
99671
|
+
var VERSION2 = "0.19.36";
|
|
99672
|
+
var COMMIT_SHA = "ba505b15";
|
|
99673
|
+
var COMMIT_DATE = "2026-07-30T07:53:24Z";
|
|
99674
|
+
var LATEST_PR = 4019;
|
|
99327
99675
|
var COMMITS_AHEAD_OF_TAG = 0;
|
|
99328
99676
|
|
|
99329
99677
|
// gateway/boot-version.ts
|
|
@@ -108836,6 +109184,7 @@ function buildModelDeps(restartCtx) {
|
|
|
108836
109184
|
const data = switchroomExecJson(["agent", "list"]);
|
|
108837
109185
|
return data?.agents?.find((a) => a.name === getMyAgentName())?.model ?? null;
|
|
108838
109186
|
},
|
|
109187
|
+
getConfiguredEffort: () => getConfiguredEffortForPersist(),
|
|
108839
109188
|
escapeHtml: escapeHtmlForTg2,
|
|
108840
109189
|
preBlock,
|
|
108841
109190
|
scheduleRestart: async (reason) => {
|
|
@@ -108986,7 +109335,7 @@ function persistQueuedCommandForRestart(action) {
|
|
|
108986
109335
|
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.`;
|
|
108987
109336
|
}
|
|
108988
109337
|
const configured = readConfiguredDefaultModel(agentDir) ?? resolveMainModel(undefined);
|
|
108989
|
-
writeSessionModelFile(agentDir,
|
|
109338
|
+
writeSessionModelFile(agentDir, expandModelAlias2(action.arg), configured);
|
|
108990
109339
|
break;
|
|
108991
109340
|
}
|
|
108992
109341
|
case "clear-model":
|
|
@@ -112564,76 +112913,87 @@ async function startGateway() {
|
|
|
112564
112913
|
try {
|
|
112565
112914
|
const smAgentDir = resolveAgentDirFromEnv();
|
|
112566
112915
|
if (smAgentDir) {
|
|
112567
|
-
const
|
|
112568
|
-
|
|
112569
|
-
|
|
112570
|
-
|
|
112571
|
-
|
|
112572
|
-
|
|
112573
|
-
|
|
112574
|
-
|
|
112575
|
-
|
|
112576
|
-
|
|
112577
|
-
|
|
112578
|
-
|
|
112579
|
-
|
|
112580
|
-
|
|
112581
|
-
|
|
112582
|
-
|
|
112583
|
-
|
|
112584
|
-
|
|
112585
|
-
|
|
112586
|
-
|
|
112587
|
-
|
|
112588
|
-
|
|
112589
|
-
|
|
112590
|
-
|
|
112591
|
-
|
|
112592
|
-
|
|
112593
|
-
|
|
112594
|
-
|
|
112595
|
-
|
|
112596
|
-
|
|
112597
|
-
|
|
112598
|
-
|
|
112599
|
-
|
|
112600
|
-
|
|
112601
|
-
|
|
112916
|
+
const resolutionTimeoutMs = resolveSessionModelResolutionTimeoutMs(process.env.SWITCHROOM_SESSION_MODEL_RESOLUTION_TIMEOUT_MS);
|
|
112917
|
+
const resolved = await waitForSessionModelResolution({
|
|
112918
|
+
barrierExists: () => existsSync57(join68(smAgentDir, ".session-model-resolved")),
|
|
112919
|
+
timeoutMs: resolutionTimeoutMs
|
|
112920
|
+
});
|
|
112921
|
+
if (!resolved) {
|
|
112922
|
+
const target = modelSwitchReason == null ? "(none)" : parseModelSwitchTarget(modelSwitchReason) ?? "(unknown)";
|
|
112923
|
+
process.stderr.write(`telegram gateway: gw /model relaunch UNRESOLVED agent=${getMyAgentName()} target=${target} (barrier timeout after ${resolutionTimeoutMs}ms)
|
|
112924
|
+
`);
|
|
112925
|
+
} else {
|
|
112926
|
+
const activePath = join68(smAgentDir, ".active-session-model");
|
|
112927
|
+
if (existsSync57(activePath)) {
|
|
112928
|
+
try {
|
|
112929
|
+
const launched = readFileSync60(activePath, "utf8").trim();
|
|
112930
|
+
const configured = (() => {
|
|
112931
|
+
const d = switchroomExecJson(["agent", "list"]);
|
|
112932
|
+
const raw = d?.agents?.find((a) => a.name === getMyAgentName())?.model ?? null;
|
|
112933
|
+
return resolveMainModel(raw ?? undefined);
|
|
112934
|
+
})();
|
|
112935
|
+
const isApplyBoot = launched.length > 0 && launched !== configured;
|
|
112936
|
+
sessionModelSource.setOverride(isApplyBoot ? launched : null, { verify: true });
|
|
112937
|
+
const modelBootCardDeps = {
|
|
112938
|
+
agent: getMyAgentName(),
|
|
112939
|
+
chat: modelSwitchMarkerChat,
|
|
112940
|
+
log: (line) => process.stderr.write(line),
|
|
112941
|
+
sendCard: (chatId, body, opts) => lockedBot.api.sendMessage(chatId, body, opts)
|
|
112942
|
+
};
|
|
112943
|
+
if (isApplyBoot) {
|
|
112944
|
+
sessionModelSource.setDivergenceHandler(buildServedModelDivergenceHandler(modelBootCardDeps));
|
|
112945
|
+
}
|
|
112946
|
+
const confirmation = modelSwitchReason != null ? classifyModelSwitchConfirmation({
|
|
112947
|
+
reason: modelSwitchReason,
|
|
112948
|
+
launched,
|
|
112949
|
+
configured
|
|
112950
|
+
}) : null;
|
|
112951
|
+
process.stderr.write(formatModelRelaunchDiagLog({
|
|
112952
|
+
agent: getMyAgentName(),
|
|
112953
|
+
launched,
|
|
112954
|
+
configured,
|
|
112602
112955
|
confirmation,
|
|
112603
|
-
|
|
112604
|
-
});
|
|
112605
|
-
|
|
112606
|
-
|
|
112607
|
-
|
|
112608
|
-
|
|
112609
|
-
|
|
112610
|
-
|
|
112611
|
-
|
|
112612
|
-
|
|
112613
|
-
sessionEffortOverride = launchedEffort.length > 0 && launchedEffort !== configuredEffort ? launchedEffort : null;
|
|
112614
|
-
} catch {}
|
|
112615
|
-
}
|
|
112616
|
-
const alertPath = join68(smAgentDir, ".session-model-alert");
|
|
112617
|
-
if (existsSync57(alertPath)) {
|
|
112618
|
-
let alertText = null;
|
|
112619
|
-
try {
|
|
112620
|
-
alertText = readFileSync60(alertPath, "utf8").trim();
|
|
112621
|
-
} catch {
|
|
112622
|
-
alertText = null;
|
|
112956
|
+
isApplyBoot
|
|
112957
|
+
}));
|
|
112958
|
+
if (confirmation != null) {
|
|
112959
|
+
deliverModelSwitchBootNotice({
|
|
112960
|
+
...modelBootCardDeps,
|
|
112961
|
+
confirmation,
|
|
112962
|
+
hasSessionModelAlert: existsSync57(join68(smAgentDir, ".session-model-alert"))
|
|
112963
|
+
});
|
|
112964
|
+
}
|
|
112965
|
+
} catch {}
|
|
112623
112966
|
}
|
|
112624
|
-
|
|
112625
|
-
|
|
112626
|
-
|
|
112627
|
-
|
|
112628
|
-
|
|
112629
|
-
|
|
112630
|
-
|
|
112631
|
-
|
|
112632
|
-
|
|
112633
|
-
|
|
112967
|
+
const activeEffortPath = join68(smAgentDir, ".active-session-effort");
|
|
112968
|
+
if (existsSync57(activeEffortPath)) {
|
|
112969
|
+
try {
|
|
112970
|
+
const launchedEffort = readFileSync60(activeEffortPath, "utf8").trim();
|
|
112971
|
+
const configuredEffort = getConfiguredEffortForPersist();
|
|
112972
|
+
sessionEffortOverride = launchedEffort.length > 0 && launchedEffort !== configuredEffort ? launchedEffort : null;
|
|
112973
|
+
} catch {}
|
|
112974
|
+
}
|
|
112975
|
+
const alertPath = join68(smAgentDir, ".session-model-alert");
|
|
112976
|
+
if (existsSync57(alertPath)) {
|
|
112977
|
+
let alertText = null;
|
|
112978
|
+
try {
|
|
112979
|
+
alertText = readFileSync60(alertPath, "utf8").trim();
|
|
112980
|
+
} catch {
|
|
112981
|
+
alertText = null;
|
|
112634
112982
|
}
|
|
112635
|
-
|
|
112983
|
+
try {
|
|
112984
|
+
unlinkSync30(alertPath);
|
|
112985
|
+
} catch {}
|
|
112986
|
+
if (alertText && alertText.length > 0) {
|
|
112987
|
+
const operators = loadAccess().allowFrom;
|
|
112988
|
+
for (const operator of operators) {
|
|
112989
|
+
if (!operator)
|
|
112990
|
+
continue;
|
|
112991
|
+
lockedBot.api.sendMessage(operator, `\u26A0\uFE0F ${alertText}`).catch((err) => process.stderr.write(`telegram gateway: session-model alert send failed for ${operator}: ${err?.message ?? String(err)}
|
|
112992
|
+
`));
|
|
112993
|
+
}
|
|
112994
|
+
process.stderr.write(`telegram gateway: session-model: LiteLLM-down override drop \u2014 ${alertText}
|
|
112636
112995
|
`);
|
|
112996
|
+
}
|
|
112637
112997
|
}
|
|
112638
112998
|
}
|
|
112639
112999
|
}
|