switchroom 0.19.35 → 0.19.37
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/skill-validate-pretool.mjs +15 -2
- package/dist/cli/switchroom.js +678 -444
- package/dist/host-control/main.js +149 -3
- package/package.json +4 -2
- package/profiles/_base/start.sh.hbs +37 -12
- package/telegram-plugin/bridge/bridge.ts +4 -4
- package/telegram-plugin/dist/bridge/bridge.js +4 -4
- package/telegram-plugin/dist/gateway/gateway.js +317 -95
- package/telegram-plugin/dist/server.js +4 -4
- package/telegram-plugin/format.ts +100 -29
- package/telegram-plugin/gateway/gateway.ts +22 -29
- package/telegram-plugin/gateway/ipc-server.ts +18 -15
- package/telegram-plugin/gateway/model-command.ts +34 -0
- package/telegram-plugin/gateway/outbound-send-path.ts +12 -1
- package/telegram-plugin/operator-events.ts +40 -16
- package/telegram-plugin/render/unsupported-token-guard.ts +10 -6
- 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 +111 -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/mcp-instructions-budget.test.ts +8 -1
- package/telegram-plugin/tests/outbound-send-path.test.ts +1 -1
- package/telegram-plugin/tests/render/unsupported-token-guard.test.ts +36 -5
- 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/tests/test_secret_redact.py +522 -0
|
@@ -6923,7 +6923,11 @@ function normalizePunctuation(text) {
|
|
|
6923
6923
|
const escNonce = nonce.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
6924
6924
|
const linkRestoreRe = new RegExp(`\x00RML${escNonce}_(\\d+)\x00`, "g");
|
|
6925
6925
|
const restoreLinks = (s) => s.replace(linkRestoreRe, (_m, idx) => linkMasks[Number(idx)] ?? _m);
|
|
6926
|
-
|
|
6926
|
+
const rewriteDashes = (line) => line.replace(/(\S)[ \t][\u2014\u2013][ \t](?=(\S))/g, (_m, a, b) => /\d/.test(a) && /\d/.test(b) ? `${a}-` : `${a}, `).replace(/(\w)\u2014(?=(\w))/g, (_m, a, b) => /\d/.test(a) && /\d/.test(b) ? `${a}-` : `${a}, `).replace(/(\w)\u2013(?=\w)/g, "$1-");
|
|
6927
|
+
const isQuotedLine = (line) => isBlockquoteLine(line) || line.trimStart().startsWith("**>");
|
|
6928
|
+
let out = maskedAutolinks.split(`
|
|
6929
|
+
`).map((line) => isQuotedLine(line) ? line : rewriteDashes(line)).join(`
|
|
6930
|
+
`);
|
|
6927
6931
|
out = restoreLinks(out);
|
|
6928
6932
|
out = out.split(`
|
|
6929
6933
|
`).map((line) => line.replace(/^([ \t]*)[\u2022\u00b7][ \t]+/, "$1- ")).join(`
|
|
@@ -6936,10 +6940,18 @@ function listItemContent(line) {
|
|
|
6936
6940
|
function isFullyBolded(fragment) {
|
|
6937
6941
|
return /^\*\*[^*]+\*\*[.,:;!?]?$/.test(fragment.trim());
|
|
6938
6942
|
}
|
|
6943
|
+
function isPseudoHeadingBlock(block) {
|
|
6944
|
+
const lines = block.split(`
|
|
6945
|
+
`).filter((l) => l.trim() !== "");
|
|
6946
|
+
if (lines.length !== 1)
|
|
6947
|
+
return false;
|
|
6948
|
+
const t = lines[0].trim();
|
|
6949
|
+
return t.length <= PSEUDO_HEADING_MAX_CHARS && isFullyBolded(t);
|
|
6950
|
+
}
|
|
6939
6951
|
function unbold(fragment) {
|
|
6940
6952
|
return fragment.replace(/\*\*([^*]+)\*\*/g, "$1");
|
|
6941
6953
|
}
|
|
6942
|
-
function stripExcessBold(text) {
|
|
6954
|
+
function stripExcessBold(text, onStrip) {
|
|
6943
6955
|
if (!text.includes("**"))
|
|
6944
6956
|
return text;
|
|
6945
6957
|
const nonce = Math.random().toString(36).slice(2);
|
|
@@ -6950,10 +6962,15 @@ function stripExcessBold(text) {
|
|
|
6950
6962
|
let boldChars = 0;
|
|
6951
6963
|
for (const m of visible.matchAll(/\*\*([^*]+)\*\*/g))
|
|
6952
6964
|
boldChars += m[1].length;
|
|
6953
|
-
|
|
6954
|
-
return restore(unbold(masked));
|
|
6955
|
-
}
|
|
6965
|
+
const ratio = boldChars / visible.length;
|
|
6956
6966
|
const blocks = masked.split(/\n{2,}/);
|
|
6967
|
+
if (ratio > 0.3) {
|
|
6968
|
+
const rebuilt2 = blocks.map((block) => isPseudoHeadingBlock(block) ? block : unbold(block));
|
|
6969
|
+
const out2 = rejoinBlocks(masked, rebuilt2);
|
|
6970
|
+
if (out2 !== masked)
|
|
6971
|
+
onStrip?.({ rule: "global", ratio });
|
|
6972
|
+
return restore(out2);
|
|
6973
|
+
}
|
|
6957
6974
|
const rebuilt = blocks.map((block) => {
|
|
6958
6975
|
const lines = block.split(`
|
|
6959
6976
|
`).filter((l) => l.trim() !== "");
|
|
@@ -6970,17 +6987,23 @@ function stripExcessBold(text) {
|
|
|
6970
6987
|
return block;
|
|
6971
6988
|
if (!lines.every((l) => isFullyBolded(l)))
|
|
6972
6989
|
return block;
|
|
6973
|
-
if (
|
|
6990
|
+
if (isPseudoHeadingBlock(block))
|
|
6974
6991
|
return block;
|
|
6975
6992
|
return unbold(block);
|
|
6976
6993
|
});
|
|
6994
|
+
const out = rejoinBlocks(masked, rebuilt);
|
|
6995
|
+
if (out !== masked)
|
|
6996
|
+
onStrip?.({ rule: "per-block", ratio });
|
|
6997
|
+
return restore(out);
|
|
6998
|
+
}
|
|
6999
|
+
function rejoinBlocks(masked, rebuilt) {
|
|
6977
7000
|
const seps = masked.match(/\n{2,}/g) ?? [];
|
|
6978
7001
|
let out = rebuilt[0] ?? "";
|
|
6979
7002
|
for (let i = 1;i < rebuilt.length; i++)
|
|
6980
7003
|
out += (seps[i - 1] ?? `
|
|
6981
7004
|
|
|
6982
7005
|
`) + rebuilt[i];
|
|
6983
|
-
return
|
|
7006
|
+
return out;
|
|
6984
7007
|
}
|
|
6985
7008
|
function isListItemLine(line) {
|
|
6986
7009
|
const t = line.trimStart();
|
|
@@ -7183,7 +7206,7 @@ function backOffOpenInline(text, cut) {
|
|
|
7183
7206
|
}
|
|
7184
7207
|
return earliest;
|
|
7185
7208
|
}
|
|
7186
|
-
var RICH_MESSAGE_MAX_CHARS = 32768, PARAGRAPH_SPACER = "\u00a0", INLINE_SPAN_PATTERNS;
|
|
7209
|
+
var RICH_MESSAGE_MAX_CHARS = 32768, PARAGRAPH_SPACER = "\u00a0", PSEUDO_HEADING_MAX_CHARS = 48, INLINE_SPAN_PATTERNS;
|
|
7187
7210
|
var init_format = __esm(() => {
|
|
7188
7211
|
INLINE_SPAN_PATTERNS = [
|
|
7189
7212
|
/`[^`\n]+`/g,
|
|
@@ -12660,16 +12683,22 @@ var init_patterns = __esm(() => {
|
|
|
12660
12683
|
},
|
|
12661
12684
|
{
|
|
12662
12685
|
rule_id: "bearer_auth_header",
|
|
12663
|
-
regex: /Authorization\s*[:=]\s*Bearer\s+([A-Za-z0-9._\-+=]+)/
|
|
12686
|
+
regex: /Authorization\s*[:=]\s*Bearer\s+([A-Za-z0-9._\-+=]+)/gi,
|
|
12664
12687
|
captureIndex: 1,
|
|
12665
12688
|
slugHint: "bearer_token"
|
|
12666
12689
|
},
|
|
12667
12690
|
{
|
|
12668
12691
|
rule_id: "bearer_loose",
|
|
12669
|
-
regex: /\bBearer\s+([A-Za-z0-9._\-+=]{18,})\b/
|
|
12692
|
+
regex: /\bBearer\s+([A-Za-z0-9._\-+=]{18,})\b/gi,
|
|
12670
12693
|
captureIndex: 1,
|
|
12671
12694
|
slugHint: "bearer_token"
|
|
12672
12695
|
},
|
|
12696
|
+
{
|
|
12697
|
+
rule_id: "basic_auth_header",
|
|
12698
|
+
regex: /Authorization\s*[:=]\s*Basic\s+([A-Za-z0-9+/=]{8,})/gi,
|
|
12699
|
+
captureIndex: 1,
|
|
12700
|
+
slugHint: "basic_auth"
|
|
12701
|
+
},
|
|
12673
12702
|
{
|
|
12674
12703
|
rule_id: "pem_private_key",
|
|
12675
12704
|
regex: /-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]+?-----END [A-Z ]*PRIVATE KEY-----/g,
|
|
@@ -12729,7 +12758,104 @@ function shannonEntropy(s) {
|
|
|
12729
12758
|
return h;
|
|
12730
12759
|
}
|
|
12731
12760
|
|
|
12761
|
+
// secret-detect/inert-values.ts
|
|
12762
|
+
function runIsCredentialShaped(run) {
|
|
12763
|
+
let classes = 0;
|
|
12764
|
+
if (/[a-z]/.test(run))
|
|
12765
|
+
classes++;
|
|
12766
|
+
if (/[A-Z]/.test(run))
|
|
12767
|
+
classes++;
|
|
12768
|
+
if (/[0-9]/.test(run))
|
|
12769
|
+
classes++;
|
|
12770
|
+
return run.length >= (classes >= 2 ? MIXED_CLASS_RUN_MIN : SINGLE_CLASS_RUN_MIN);
|
|
12771
|
+
}
|
|
12772
|
+
function hasCredentialShapedRun(value) {
|
|
12773
|
+
for (const run of value.match(CREDENTIAL_RUN_RE) ?? []) {
|
|
12774
|
+
if (runIsCredentialShaped(run))
|
|
12775
|
+
return true;
|
|
12776
|
+
}
|
|
12777
|
+
return false;
|
|
12778
|
+
}
|
|
12779
|
+
function isInertValue(value) {
|
|
12780
|
+
if (!INERT_VALUE_RE.some((re) => re.test(value)))
|
|
12781
|
+
return false;
|
|
12782
|
+
return !hasCredentialShapedRun(value);
|
|
12783
|
+
}
|
|
12784
|
+
function stripTrailingPunctuation(value) {
|
|
12785
|
+
return value.replace(TRAILING_PUNCT_RE, "");
|
|
12786
|
+
}
|
|
12787
|
+
var INERT_VALUE_RE, CREDENTIAL_RUN_RE, MIXED_CLASS_RUN_MIN = 12, SINGLE_CLASS_RUN_MIN = 16, INERT_GATED_RULES, TRAILING_PUNCT_RE;
|
|
12788
|
+
var init_inert_values = __esm(() => {
|
|
12789
|
+
INERT_VALUE_RE = [
|
|
12790
|
+
/^\[REDACTED(?::[A-Za-z0-9_]+)?\]$/i,
|
|
12791
|
+
/^\$\{?[A-Za-z_][A-Za-z0-9_]*\}?$/,
|
|
12792
|
+
/^\{\{[^\n\r\u2028\u2029]*\}\}$/,
|
|
12793
|
+
/^<[^<>\n\r\u2028\u2029]*>$/,
|
|
12794
|
+
/^%[A-Za-z_][A-Za-z0-9_]*%$/,
|
|
12795
|
+
/^vault:[A-Za-z0-9_./-]*$/i,
|
|
12796
|
+
/^[*x\u2022.]+$/i,
|
|
12797
|
+
/^(?:process\.env|import\.meta\.env|os\.environ)(?:\.[A-Za-z_$][A-Za-z0-9_$]*|\[["']?[A-Za-z_$][A-Za-z0-9_$]*["']?\])*$/,
|
|
12798
|
+
/^(?: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,
|
|
12799
|
+
/^[a-z]+(?: [a-z]+){2,}$/
|
|
12800
|
+
];
|
|
12801
|
+
CREDENTIAL_RUN_RE = /[A-Za-z0-9]{12,}/g;
|
|
12802
|
+
INERT_GATED_RULES = new Set([
|
|
12803
|
+
"env_key_value",
|
|
12804
|
+
"json_secret_field",
|
|
12805
|
+
"cli_flag"
|
|
12806
|
+
]);
|
|
12807
|
+
TRAILING_PUNCT_RE = /[.,;:!?)\]}]+$/;
|
|
12808
|
+
});
|
|
12809
|
+
|
|
12732
12810
|
// secret-detect/kv-scanner.ts
|
|
12811
|
+
function charClassCount(value) {
|
|
12812
|
+
let n = 0;
|
|
12813
|
+
if (/[a-z]/.test(value))
|
|
12814
|
+
n++;
|
|
12815
|
+
if (/[A-Z]/.test(value))
|
|
12816
|
+
n++;
|
|
12817
|
+
if (/[0-9]/.test(value))
|
|
12818
|
+
n++;
|
|
12819
|
+
if (/[^A-Za-z0-9]/.test(value))
|
|
12820
|
+
n++;
|
|
12821
|
+
return n;
|
|
12822
|
+
}
|
|
12823
|
+
function looksLikeMemorablePassword(value) {
|
|
12824
|
+
if (isInertValue(value))
|
|
12825
|
+
return false;
|
|
12826
|
+
const core = stripTrailingPunctuation(value);
|
|
12827
|
+
if (core.length < 8 || core.length > 64)
|
|
12828
|
+
return false;
|
|
12829
|
+
if (isInertValue(core))
|
|
12830
|
+
return false;
|
|
12831
|
+
if (new Set(core).size < 4)
|
|
12832
|
+
return false;
|
|
12833
|
+
return charClassCount(core) >= MEMORABLE_PW_MIN_CLASSES;
|
|
12834
|
+
}
|
|
12835
|
+
function scanMemorablePasswords(text) {
|
|
12836
|
+
const hits = [];
|
|
12837
|
+
MEMORABLE_PW_RE.lastIndex = 0;
|
|
12838
|
+
let m;
|
|
12839
|
+
while ((m = MEMORABLE_PW_RE.exec(text)) !== null) {
|
|
12840
|
+
const keyName = m[1];
|
|
12841
|
+
const value = m[3];
|
|
12842
|
+
if (!value || !looksLikeMemorablePassword(value))
|
|
12843
|
+
continue;
|
|
12844
|
+
const valueOffsetInMatch = m[0].indexOf(value, keyName.length);
|
|
12845
|
+
if (valueOffsetInMatch < 0)
|
|
12846
|
+
continue;
|
|
12847
|
+
const start = m.index + valueOffsetInMatch;
|
|
12848
|
+
hits.push({
|
|
12849
|
+
rule_id: MEMORABLE_PW_RULE_ID,
|
|
12850
|
+
start,
|
|
12851
|
+
end: start + value.length,
|
|
12852
|
+
matched_text: value,
|
|
12853
|
+
key_name: keyName,
|
|
12854
|
+
confidence: "ambiguous"
|
|
12855
|
+
});
|
|
12856
|
+
}
|
|
12857
|
+
return hits;
|
|
12858
|
+
}
|
|
12733
12859
|
function scanKeyValue(text) {
|
|
12734
12860
|
const hits = [];
|
|
12735
12861
|
KV_RE.lastIndex = 0;
|
|
@@ -12738,6 +12864,8 @@ function scanKeyValue(text) {
|
|
|
12738
12864
|
const [, keyName, value] = m;
|
|
12739
12865
|
if (!value)
|
|
12740
12866
|
continue;
|
|
12867
|
+
if (isInertValue(value))
|
|
12868
|
+
continue;
|
|
12741
12869
|
const h = shannonEntropy(value);
|
|
12742
12870
|
if (h < KV_ENTROPY_THRESHOLD)
|
|
12743
12871
|
continue;
|
|
@@ -12757,9 +12885,44 @@ function scanKeyValue(text) {
|
|
|
12757
12885
|
}
|
|
12758
12886
|
return hits;
|
|
12759
12887
|
}
|
|
12760
|
-
var KV_RE, KV_ENTROPY_THRESHOLD = 4;
|
|
12888
|
+
var KV_RE, KV_ENTROPY_THRESHOLD = 4, MEMORABLE_PW_RE, MEMORABLE_PW_RULE_ID = "memorable_password", MEMORABLE_PW_MIN_CLASSES = 2;
|
|
12761
12889
|
var init_kv_scanner = __esm(() => {
|
|
12890
|
+
init_inert_values();
|
|
12762
12891
|
KV_RE = /\b([A-Za-z_][A-Za-z0-9_-]*(?:password|passwd|token|secret|key|api[_-]?key))\s*[:=]\s*["']?([^\s"'\\]{8,})["']?/gi;
|
|
12892
|
+
MEMORABLE_PW_RE = /\b([A-Za-z0-9_-]*(?:password|passwd|passphrase|pwd))\b\s*(?:[:=]\s*|\s+is\s+)(["']?)([^\s"']{8,64})\2/gi;
|
|
12893
|
+
});
|
|
12894
|
+
|
|
12895
|
+
// secret-detect/db-uri.ts
|
|
12896
|
+
function isInertPassword(value) {
|
|
12897
|
+
if (value.startsWith("[REDACTED"))
|
|
12898
|
+
return true;
|
|
12899
|
+
return /^[*x]+$/i.test(value);
|
|
12900
|
+
}
|
|
12901
|
+
function scanDbUris(text) {
|
|
12902
|
+
const hits = [];
|
|
12903
|
+
DB_URI_RE.lastIndex = 0;
|
|
12904
|
+
let m;
|
|
12905
|
+
while ((m = DB_URI_RE.exec(text)) !== null) {
|
|
12906
|
+
const scheme = m[1];
|
|
12907
|
+
const user = m[2];
|
|
12908
|
+
const password = m[3];
|
|
12909
|
+
if (isInertPassword(password))
|
|
12910
|
+
continue;
|
|
12911
|
+
const start = m.index + scheme.length + 3 + user.length + 1;
|
|
12912
|
+
hits.push({
|
|
12913
|
+
rule_id: DB_URI_RULE_ID,
|
|
12914
|
+
start,
|
|
12915
|
+
end: start + password.length,
|
|
12916
|
+
matched_text: password,
|
|
12917
|
+
key_name: `${scheme}_password`,
|
|
12918
|
+
confidence: "ambiguous"
|
|
12919
|
+
});
|
|
12920
|
+
}
|
|
12921
|
+
return hits;
|
|
12922
|
+
}
|
|
12923
|
+
var DB_URI_RE, DB_URI_RULE_ID = "db_uri_password";
|
|
12924
|
+
var init_db_uri = __esm(() => {
|
|
12925
|
+
DB_URI_RE = /\b([a-zA-Z][a-zA-Z0-9+.-]+):\/\/([^\s:/@]+):([^\s/?#]+)@([^\s/@?#]+)/g;
|
|
12763
12926
|
});
|
|
12764
12927
|
|
|
12765
12928
|
// secret-detect/generic-entropy.ts
|
|
@@ -12953,6 +13116,8 @@ function detectSecrets(text) {
|
|
|
12953
13116
|
const globalStart = win.offset + matchStart;
|
|
12954
13117
|
const globalEnd = globalStart + cap.length;
|
|
12955
13118
|
const keyName = p.rule_id === "env_key_value" ? m[1] : undefined;
|
|
13119
|
+
if (INERT_GATED_RULES.has(p.rule_id) && isInertValue(cap))
|
|
13120
|
+
continue;
|
|
12956
13121
|
if (p.rule_id === "env_key_value") {
|
|
12957
13122
|
const ENV_KV_MIN_LEN = 12;
|
|
12958
13123
|
const ENV_KV_MIN_ENTROPY = 3.5;
|
|
@@ -12975,6 +13140,12 @@ function detectSecrets(text) {
|
|
|
12975
13140
|
for (const h of kvHits) {
|
|
12976
13141
|
raw.push({ ...h, start: h.start + win.offset, end: h.end + win.offset });
|
|
12977
13142
|
}
|
|
13143
|
+
for (const h of scanDbUris(win.text)) {
|
|
13144
|
+
raw.push({ ...h, start: h.start + win.offset, end: h.end + win.offset });
|
|
13145
|
+
}
|
|
13146
|
+
for (const h of scanMemorablePasswords(win.text)) {
|
|
13147
|
+
raw.push({ ...h, start: h.start + win.offset, end: h.end + win.offset });
|
|
13148
|
+
}
|
|
12978
13149
|
const genHits = scanGenericSecrets(win.text);
|
|
12979
13150
|
for (const h of genHits) {
|
|
12980
13151
|
raw.push({ ...h, start: h.start + win.offset, end: h.end + win.offset });
|
|
@@ -13024,6 +13195,8 @@ function dropOverlaps(hits) {
|
|
|
13024
13195
|
var init_secret_detect = __esm(() => {
|
|
13025
13196
|
init_patterns();
|
|
13026
13197
|
init_kv_scanner();
|
|
13198
|
+
init_db_uri();
|
|
13199
|
+
init_inert_values();
|
|
13027
13200
|
init_generic_entropy();
|
|
13028
13201
|
init_chunker();
|
|
13029
13202
|
init_suppressor();
|
|
@@ -13438,7 +13611,7 @@ function guardUnsupportedTokens(text) {
|
|
|
13438
13611
|
var CARET_PAIR, FOOTNOTE_MARKER, DETAILS_BLOCK, ORPHAN_TAGS;
|
|
13439
13612
|
var init_unsupported_token_guard = __esm(() => {
|
|
13440
13613
|
CARET_PAIR = /\^([A-Za-z0-9]+)\^/g;
|
|
13441
|
-
FOOTNOTE_MARKER = /\[
|
|
13614
|
+
FOOTNOTE_MARKER = /\[\^[A-Za-z0-9]{1,10}\](?!:)/g;
|
|
13442
13615
|
DETAILS_BLOCK = /<details[^>]*>\s*(?:<summary[^>]*>([\s\S]*?)<\/summary>)?([\s\S]*?)<\/details>/gi;
|
|
13443
13616
|
ORPHAN_TAGS = /<\/?(?:details|summary)[^>]*>/gi;
|
|
13444
13617
|
});
|
|
@@ -71039,6 +71212,23 @@ function describeProviderCreditRemedy(provider) {
|
|
|
71039
71212
|
}
|
|
71040
71213
|
|
|
71041
71214
|
// operator-events.ts
|
|
71215
|
+
var OPERATOR_EVENT_KINDS = [
|
|
71216
|
+
"credentials-expired",
|
|
71217
|
+
"credentials-invalid",
|
|
71218
|
+
"proxy-misconfig",
|
|
71219
|
+
"credit-exhausted",
|
|
71220
|
+
"provider-credit-exhausted",
|
|
71221
|
+
"mcp-dependency-blocked",
|
|
71222
|
+
"quota-exhausted",
|
|
71223
|
+
"rate-limited",
|
|
71224
|
+
"agent-crashed",
|
|
71225
|
+
"agent-restarted-unexpectedly",
|
|
71226
|
+
"unknown-4xx",
|
|
71227
|
+
"unknown-5xx",
|
|
71228
|
+
"config-warning",
|
|
71229
|
+
"always-allow-persist-failed",
|
|
71230
|
+
"mental-model-persist-failed"
|
|
71231
|
+
];
|
|
71042
71232
|
function classifyClaudeError(raw) {
|
|
71043
71233
|
try {
|
|
71044
71234
|
return classifyInner(raw);
|
|
@@ -77648,7 +77838,12 @@ function normalizeOutboundBody(rawText, site, redact2, opts = {}) {
|
|
|
77648
77838
|
text4 = normalizeParagraphBreaks(text4);
|
|
77649
77839
|
text4 = redact2(text4, site);
|
|
77650
77840
|
if (!literalText) {
|
|
77651
|
-
let formatted = stripExcessBold(normalizePunctuation(text4))
|
|
77841
|
+
let formatted = stripExcessBold(normalizePunctuation(text4), (d) => {
|
|
77842
|
+
try {
|
|
77843
|
+
process.stderr.write(`telegram gateway: strip-excess-bold: fired site=${site} rule=${d.rule} ratio=${d.ratio.toFixed(3)}
|
|
77844
|
+
`);
|
|
77845
|
+
} catch {}
|
|
77846
|
+
});
|
|
77652
77847
|
if (addSpacers)
|
|
77653
77848
|
formatted = addParagraphSpacers(formatted);
|
|
77654
77849
|
text4 = formatted;
|
|
@@ -79094,7 +79289,12 @@ function normalizeOutboundBody2(rawText, site, redact2, opts = {}) {
|
|
|
79094
79289
|
text4 = normalizeParagraphBreaks(text4);
|
|
79095
79290
|
text4 = redact2(text4, site);
|
|
79096
79291
|
if (!literalText) {
|
|
79097
|
-
let formatted = stripExcessBold(normalizePunctuation(text4))
|
|
79292
|
+
let formatted = stripExcessBold(normalizePunctuation(text4), (d) => {
|
|
79293
|
+
try {
|
|
79294
|
+
process.stderr.write(`telegram gateway: strip-excess-bold: fired site=${site} rule=${d.rule} ratio=${d.ratio.toFixed(3)}
|
|
79295
|
+
`);
|
|
79296
|
+
} catch {}
|
|
79297
|
+
});
|
|
79098
79298
|
if (addSpacers)
|
|
79099
79299
|
formatted = addParagraphSpacers(formatted);
|
|
79100
79300
|
text4 = formatted;
|
|
@@ -83583,6 +83783,27 @@ var MODEL_ARG_RE2 = /^[A-Za-z0-9][A-Za-z0-9._/\[\]-]{0,99}$/;
|
|
|
83583
83783
|
function isValidModelArg2(arg) {
|
|
83584
83784
|
return MODEL_ARG_RE2.test(arg);
|
|
83585
83785
|
}
|
|
83786
|
+
var DEFAULT_SESSION_MODEL_RESOLUTION_TIMEOUT_MS = 180000;
|
|
83787
|
+
var SESSION_MODEL_RESOLUTION_POLL_MS = 250;
|
|
83788
|
+
function resolveSessionModelResolutionTimeoutMs(raw) {
|
|
83789
|
+
if (raw == null || raw.trim() === "")
|
|
83790
|
+
return DEFAULT_SESSION_MODEL_RESOLUTION_TIMEOUT_MS;
|
|
83791
|
+
const value = Number(raw);
|
|
83792
|
+
return Number.isFinite(value) && value >= 0 ? value : DEFAULT_SESSION_MODEL_RESOLUTION_TIMEOUT_MS;
|
|
83793
|
+
}
|
|
83794
|
+
async function waitForSessionModelResolution(input) {
|
|
83795
|
+
const pollMs = input.pollMs ?? SESSION_MODEL_RESOLUTION_POLL_MS;
|
|
83796
|
+
const sleep2 = input.sleep ?? ((ms) => new Promise((resolve7) => setTimeout(resolve7, ms)));
|
|
83797
|
+
const startedAt = Date.now();
|
|
83798
|
+
for (;; ) {
|
|
83799
|
+
if (input.barrierExists())
|
|
83800
|
+
return true;
|
|
83801
|
+
const remaining = input.timeoutMs - (Date.now() - startedAt);
|
|
83802
|
+
if (remaining <= 0)
|
|
83803
|
+
return false;
|
|
83804
|
+
await sleep2(Math.min(pollMs, remaining));
|
|
83805
|
+
}
|
|
83806
|
+
}
|
|
83586
83807
|
function isSrModel2(name) {
|
|
83587
83808
|
return name.startsWith("sr-");
|
|
83588
83809
|
}
|
|
@@ -88847,17 +89068,7 @@ function recordWebhookEvent(rec, deps = {}) {
|
|
|
88847
89068
|
init_format();
|
|
88848
89069
|
import { renameSync as renameSync17, unlinkSync as unlinkSync20, chmodSync as chmodSync11 } from "fs";
|
|
88849
89070
|
var MAX_BUFFER_SIZE = 1024 * 1024;
|
|
88850
|
-
var VALID_OPERATOR_KINDS = new Set(
|
|
88851
|
-
"credentials-expired",
|
|
88852
|
-
"credentials-invalid",
|
|
88853
|
-
"credit-exhausted",
|
|
88854
|
-
"quota-exhausted",
|
|
88855
|
-
"rate-limited",
|
|
88856
|
-
"agent-crashed",
|
|
88857
|
-
"agent-restarted-unexpectedly",
|
|
88858
|
-
"unknown-4xx",
|
|
88859
|
-
"unknown-5xx"
|
|
88860
|
-
]);
|
|
89071
|
+
var VALID_OPERATOR_KINDS = new Set(OPERATOR_EVENT_KINDS);
|
|
88861
89072
|
var AGENT_NAME_RE3 = /^[a-z0-9][a-z0-9_-]{0,50}$/;
|
|
88862
89073
|
var OPERATOR_EVENT_DETAIL_MAX = 1000;
|
|
88863
89074
|
function validateClientMessage(msg) {
|
|
@@ -99461,10 +99672,10 @@ function startOutboxSweep(deps) {
|
|
|
99461
99672
|
}
|
|
99462
99673
|
|
|
99463
99674
|
// ../src/build-info.ts
|
|
99464
|
-
var VERSION2 = "0.19.
|
|
99465
|
-
var COMMIT_SHA = "
|
|
99466
|
-
var COMMIT_DATE = "2026-07-
|
|
99467
|
-
var LATEST_PR =
|
|
99675
|
+
var VERSION2 = "0.19.37";
|
|
99676
|
+
var COMMIT_SHA = "5d2f9a17";
|
|
99677
|
+
var COMMIT_DATE = "2026-07-30T08:57:38Z";
|
|
99678
|
+
var LATEST_PR = 4023;
|
|
99468
99679
|
var COMMITS_AHEAD_OF_TAG = 0;
|
|
99469
99680
|
|
|
99470
99681
|
// gateway/boot-version.ts
|
|
@@ -112706,76 +112917,87 @@ async function startGateway() {
|
|
|
112706
112917
|
try {
|
|
112707
112918
|
const smAgentDir = resolveAgentDirFromEnv();
|
|
112708
112919
|
if (smAgentDir) {
|
|
112709
|
-
const
|
|
112710
|
-
|
|
112711
|
-
|
|
112712
|
-
|
|
112713
|
-
|
|
112714
|
-
|
|
112715
|
-
|
|
112716
|
-
|
|
112717
|
-
|
|
112718
|
-
|
|
112719
|
-
|
|
112720
|
-
|
|
112721
|
-
|
|
112722
|
-
|
|
112723
|
-
|
|
112724
|
-
|
|
112725
|
-
|
|
112726
|
-
|
|
112727
|
-
|
|
112728
|
-
|
|
112729
|
-
|
|
112730
|
-
|
|
112731
|
-
|
|
112732
|
-
|
|
112733
|
-
|
|
112734
|
-
|
|
112735
|
-
|
|
112736
|
-
|
|
112737
|
-
|
|
112738
|
-
|
|
112739
|
-
|
|
112740
|
-
|
|
112741
|
-
|
|
112742
|
-
|
|
112743
|
-
|
|
112920
|
+
const resolutionTimeoutMs = resolveSessionModelResolutionTimeoutMs(process.env.SWITCHROOM_SESSION_MODEL_RESOLUTION_TIMEOUT_MS);
|
|
112921
|
+
const resolved = await waitForSessionModelResolution({
|
|
112922
|
+
barrierExists: () => existsSync57(join68(smAgentDir, ".session-model-resolved")),
|
|
112923
|
+
timeoutMs: resolutionTimeoutMs
|
|
112924
|
+
});
|
|
112925
|
+
if (!resolved) {
|
|
112926
|
+
const target = modelSwitchReason == null ? "(none)" : parseModelSwitchTarget(modelSwitchReason) ?? "(unknown)";
|
|
112927
|
+
process.stderr.write(`telegram gateway: gw /model relaunch UNRESOLVED agent=${getMyAgentName()} target=${target} (barrier timeout after ${resolutionTimeoutMs}ms)
|
|
112928
|
+
`);
|
|
112929
|
+
} else {
|
|
112930
|
+
const activePath = join68(smAgentDir, ".active-session-model");
|
|
112931
|
+
if (existsSync57(activePath)) {
|
|
112932
|
+
try {
|
|
112933
|
+
const launched = readFileSync60(activePath, "utf8").trim();
|
|
112934
|
+
const configured = (() => {
|
|
112935
|
+
const d = switchroomExecJson(["agent", "list"]);
|
|
112936
|
+
const raw = d?.agents?.find((a) => a.name === getMyAgentName())?.model ?? null;
|
|
112937
|
+
return resolveMainModel(raw ?? undefined);
|
|
112938
|
+
})();
|
|
112939
|
+
const isApplyBoot = launched.length > 0 && launched !== configured;
|
|
112940
|
+
sessionModelSource.setOverride(isApplyBoot ? launched : null, { verify: true });
|
|
112941
|
+
const modelBootCardDeps = {
|
|
112942
|
+
agent: getMyAgentName(),
|
|
112943
|
+
chat: modelSwitchMarkerChat,
|
|
112944
|
+
log: (line) => process.stderr.write(line),
|
|
112945
|
+
sendCard: (chatId, body, opts) => lockedBot.api.sendMessage(chatId, body, opts)
|
|
112946
|
+
};
|
|
112947
|
+
if (isApplyBoot) {
|
|
112948
|
+
sessionModelSource.setDivergenceHandler(buildServedModelDivergenceHandler(modelBootCardDeps));
|
|
112949
|
+
}
|
|
112950
|
+
const confirmation = modelSwitchReason != null ? classifyModelSwitchConfirmation({
|
|
112951
|
+
reason: modelSwitchReason,
|
|
112952
|
+
launched,
|
|
112953
|
+
configured
|
|
112954
|
+
}) : null;
|
|
112955
|
+
process.stderr.write(formatModelRelaunchDiagLog({
|
|
112956
|
+
agent: getMyAgentName(),
|
|
112957
|
+
launched,
|
|
112958
|
+
configured,
|
|
112744
112959
|
confirmation,
|
|
112745
|
-
|
|
112746
|
-
});
|
|
112747
|
-
|
|
112748
|
-
|
|
112749
|
-
|
|
112750
|
-
|
|
112751
|
-
|
|
112752
|
-
|
|
112753
|
-
|
|
112754
|
-
|
|
112755
|
-
sessionEffortOverride = launchedEffort.length > 0 && launchedEffort !== configuredEffort ? launchedEffort : null;
|
|
112756
|
-
} catch {}
|
|
112757
|
-
}
|
|
112758
|
-
const alertPath = join68(smAgentDir, ".session-model-alert");
|
|
112759
|
-
if (existsSync57(alertPath)) {
|
|
112760
|
-
let alertText = null;
|
|
112761
|
-
try {
|
|
112762
|
-
alertText = readFileSync60(alertPath, "utf8").trim();
|
|
112763
|
-
} catch {
|
|
112764
|
-
alertText = null;
|
|
112960
|
+
isApplyBoot
|
|
112961
|
+
}));
|
|
112962
|
+
if (confirmation != null) {
|
|
112963
|
+
deliverModelSwitchBootNotice({
|
|
112964
|
+
...modelBootCardDeps,
|
|
112965
|
+
confirmation,
|
|
112966
|
+
hasSessionModelAlert: existsSync57(join68(smAgentDir, ".session-model-alert"))
|
|
112967
|
+
});
|
|
112968
|
+
}
|
|
112969
|
+
} catch {}
|
|
112765
112970
|
}
|
|
112766
|
-
|
|
112767
|
-
|
|
112768
|
-
|
|
112769
|
-
|
|
112770
|
-
|
|
112771
|
-
|
|
112772
|
-
|
|
112773
|
-
|
|
112774
|
-
|
|
112775
|
-
|
|
112971
|
+
const activeEffortPath = join68(smAgentDir, ".active-session-effort");
|
|
112972
|
+
if (existsSync57(activeEffortPath)) {
|
|
112973
|
+
try {
|
|
112974
|
+
const launchedEffort = readFileSync60(activeEffortPath, "utf8").trim();
|
|
112975
|
+
const configuredEffort = getConfiguredEffortForPersist();
|
|
112976
|
+
sessionEffortOverride = launchedEffort.length > 0 && launchedEffort !== configuredEffort ? launchedEffort : null;
|
|
112977
|
+
} catch {}
|
|
112978
|
+
}
|
|
112979
|
+
const alertPath = join68(smAgentDir, ".session-model-alert");
|
|
112980
|
+
if (existsSync57(alertPath)) {
|
|
112981
|
+
let alertText = null;
|
|
112982
|
+
try {
|
|
112983
|
+
alertText = readFileSync60(alertPath, "utf8").trim();
|
|
112984
|
+
} catch {
|
|
112985
|
+
alertText = null;
|
|
112776
112986
|
}
|
|
112777
|
-
|
|
112987
|
+
try {
|
|
112988
|
+
unlinkSync30(alertPath);
|
|
112989
|
+
} catch {}
|
|
112990
|
+
if (alertText && alertText.length > 0) {
|
|
112991
|
+
const operators = loadAccess().allowFrom;
|
|
112992
|
+
for (const operator of operators) {
|
|
112993
|
+
if (!operator)
|
|
112994
|
+
continue;
|
|
112995
|
+
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)}
|
|
112996
|
+
`));
|
|
112997
|
+
}
|
|
112998
|
+
process.stderr.write(`telegram gateway: session-model: LiteLLM-down override drop \u2014 ${alertText}
|
|
112778
112999
|
`);
|
|
113000
|
+
}
|
|
112779
113001
|
}
|
|
112780
113002
|
}
|
|
112781
113003
|
}
|
|
@@ -24827,7 +24827,7 @@ var init_bridge = __esm(async () => {
|
|
|
24827
24827
|
TOOL_SCHEMAS = [
|
|
24828
24828
|
{
|
|
24829
24829
|
name: "reply",
|
|
24830
|
-
description: "Reply on Telegram. Pass chat_id from the inbound message. By default the reply is a quote-reply to the latest inbound user message in this chat+thread \u2014 pass quote:false to opt out, or pass an explicit reply_to to thread under a specific earlier message. files (absolute paths) attach images or documents. inline_keyboard adds tappable buttons (URL or callback) under the message \u2014 single-tap actions beat asking the user to type YES. " + "FORUM TOPICS: a reply is auto-routed back to the topic the question came from, so do NOT pass message_thread_id on a normal reply \u2014 pass the inbound's origin_turn_id instead, so the answer lands in the right topic even if a message from another topic arrived while you were working. message_thread_id is only for deliberately posting into a topic that is not the one you were asked in. " + 'FORMAT:
|
|
24830
|
+
description: "Reply on Telegram. Pass chat_id from the inbound message. By default the reply is a quote-reply to the latest inbound user message in this chat+thread \u2014 pass quote:false to opt out, or pass an explicit reply_to to thread under a specific earlier message. files (absolute paths) attach images or documents. inline_keyboard adds tappable buttons (URL or callback) under the message \u2014 single-tap actions beat asking the user to type YES. " + "FORUM TOPICS: a reply is auto-routed back to the topic the question came from, so do NOT pass message_thread_id on a normal reply \u2014 pass the inbound's origin_turn_id instead, so the answer lands in the right topic even if a message from another topic arrived while you were working. message_thread_id is only for deliberately posting into a topic that is not the one you were asked in. " + 'FORMAT: by default your text is rendered as rich GFM markdown (Bot API 10.1) \u2014 write natural markdown (bold, italic, code, links, code blocks, lists, tables) and it renders as-is. Pass format: "text" to send plain text verbatim with no rendering. "html" and "markdownv2" are accepted legacy aliases that route through the exact same single rich GFM path \u2014 there is no separate HTML engine and no auto-escaping.',
|
|
24831
24831
|
inputSchema: {
|
|
24832
24832
|
type: "object",
|
|
24833
24833
|
properties: {
|
|
@@ -24838,7 +24838,7 @@ var init_bridge = __esm(async () => {
|
|
|
24838
24838
|
message_thread_id: { type: "string", description: "Forum topic thread ID. Auto-applied from the last inbound message in the same chat if not specified." },
|
|
24839
24839
|
origin_turn_id: { type: "string", description: "In a forum supergroup, pass back the origin_turn_id attribute from the <channel> message you are answering. It pins the reply to that message's topic even if another topic's turn started meanwhile. Omit in DMs / single-topic chats." },
|
|
24840
24840
|
files: { type: "array", items: { type: "string" }, description: "Absolute file paths to attach. Images send as photos; other types as documents. Max 50MB each. Telegram rejects photos with extreme dimensions (aspect ratio over ~10:1, width+height over 10000px, or over 10MB) \u2014 very tall images like full-page screenshots are auto-rerouted as documents; crop or split them first if the user should see them inline as photos." },
|
|
24841
|
-
format: { type: "string", enum: ["html", "markdownv2", "text"], description: "Rendering mode. 'html'
|
|
24841
|
+
format: { type: "string", enum: ["html", "markdownv2", "text"], description: "Rendering mode. Default renders your text as rich GFM markdown (Bot API 10.1). 'text' sends plain text verbatim. 'html' and 'markdownv2' are legacy aliases that route through the same single rich GFM path \u2014 no separate HTML engine, no auto-escaping." },
|
|
24842
24842
|
disable_web_page_preview: { type: "boolean", description: "Disable link preview thumbnails. Default: true." },
|
|
24843
24843
|
protect_content: { type: "boolean", description: "When true, Telegram prevents the message from being forwarded or saved." },
|
|
24844
24844
|
quote_text: { type: "string", description: "Surgical quote: specific text to highlight from the reply_to message. Requires reply_to." },
|
|
@@ -24912,7 +24912,7 @@ var init_bridge = __esm(async () => {
|
|
|
24912
24912
|
chat_id: { type: "string" },
|
|
24913
24913
|
message_id: { type: "string" },
|
|
24914
24914
|
text: { type: "string" },
|
|
24915
|
-
format: { type: "string", enum: ["html", "markdownv2", "text"], description: "Rendering mode. 'html'
|
|
24915
|
+
format: { type: "string", enum: ["html", "markdownv2", "text"], description: "Rendering mode. Default renders your text as rich GFM markdown (Bot API 10.1). 'text' sends plain text verbatim. 'html' and 'markdownv2' are legacy aliases that route through the same single rich GFM path \u2014 no separate HTML engine, no auto-escaping." }
|
|
24916
24916
|
},
|
|
24917
24917
|
required: ["chat_id", "message_id", "text"]
|
|
24918
24918
|
}
|
|
@@ -25069,7 +25069,7 @@ var init_bridge = __esm(async () => {
|
|
|
25069
25069
|
type: "object",
|
|
25070
25070
|
properties: {
|
|
25071
25071
|
chat_id: { type: "string", description: "Chat that should receive the question. Pass from inbound meta." },
|
|
25072
|
-
question: { type: "string", description: "The question text. Plain text or
|
|
25072
|
+
question: { type: "string", description: "The question text. Plain text or GFM markdown (rendered through the same rich path as replies). Keep it short \u2014 buttons render below." },
|
|
25073
25073
|
options: {
|
|
25074
25074
|
type: "array",
|
|
25075
25075
|
items: { type: "string" },
|