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.
Files changed (35) hide show
  1. package/dist/auth-broker/index.js +7 -1
  2. package/dist/cli/skill-validate-pretool.mjs +15 -2
  3. package/dist/cli/switchroom.js +1005 -482
  4. package/dist/host-control/main.js +156 -4
  5. package/dist/vault/approvals/kernel-server.js +7 -1
  6. package/dist/vault/broker/server.js +7 -1
  7. package/package.json +4 -2
  8. package/profiles/_base/start.sh.hbs +37 -12
  9. package/telegram-plugin/dist/gateway/gateway.js +476 -116
  10. package/telegram-plugin/format.ts +70 -15
  11. package/telegram-plugin/gateway/gateway.ts +27 -31
  12. package/telegram-plugin/gateway/ipc-server.ts +18 -15
  13. package/telegram-plugin/gateway/model-command.ts +279 -23
  14. package/telegram-plugin/gateway/outbound-send-path.ts +12 -1
  15. package/telegram-plugin/operator-events.ts +40 -16
  16. package/telegram-plugin/secret-detect/db-uri.ts +90 -0
  17. package/telegram-plugin/secret-detect/index.ts +24 -1
  18. package/telegram-plugin/secret-detect/inert-values.ts +147 -0
  19. package/telegram-plugin/secret-detect/kv-scanner.ts +108 -0
  20. package/telegram-plugin/secret-detect/patterns.ts +24 -4
  21. package/telegram-plugin/tests/format-consistency.test.ts +93 -0
  22. package/telegram-plugin/tests/gateway-session-model-relaunch.test.ts +40 -2
  23. package/telegram-plugin/tests/ipc-server-validate-operator.test.ts +20 -11
  24. package/telegram-plugin/tests/model-command.test.ts +415 -4
  25. package/telegram-plugin/tests/outbound-send-path.test.ts +1 -1
  26. package/telegram-plugin/tests/secret-detect-cross-engine.test.ts +263 -0
  27. package/telegram-plugin/tests/secret-detect-write-path.test.ts +403 -0
  28. package/telegram-plugin/tests/turn-flush-safety.test.ts +2 -2
  29. package/vendor/hindsight-memory/scripts/lib/client.py +58 -0
  30. package/vendor/hindsight-memory/scripts/lib/secret_patterns.json +431 -0
  31. package/vendor/hindsight-memory/scripts/lib/secret_redact.py +563 -0
  32. package/vendor/hindsight-memory/scripts/lib/secret_redaction_vectors.json +397 -0
  33. package/vendor/hindsight-memory/scripts/subagent_retain.py +103 -7
  34. package/vendor/hindsight-memory/scripts/tests/test_secret_redact.py +522 -0
  35. 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
- if (boldChars / visible.length > 0.3) {
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 (lines.length === 1 && lines[0].trim().length <= 48)
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 restore(out);
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._\-+=]+)/g,
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/g,
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: expandSrAlias(parsed.model) };
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 = expandSrAlias(parsed.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
- if (!model.trim().toLowerCase().startsWith("claude-"))
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: [switchingLine(deps, model), ...caveat ? [caveat] : [], PERSIST_NOTE].join(`
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 EXTRA_CLAUDE_ALIASES = [
45329
- { alias: "fable", label: "Fable" }
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 { alias, label } of EXTRA_CLAUDE_ALIASES) {
45449
- const already = claudeOptions.some((o) => o.label.toLowerCase() === label.toLowerCase() || o.label.toLowerCase() === alias.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}${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 = expandSrAlias2(parsed.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
- if (!model.trim().toLowerCase().startsWith("claude-"))
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: [switchingLine2(deps, model), ...caveat ? [caveat] : [], PERSIST_NOTE3].join(`
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 EXTRA_CLAUDE_ALIASES2 = [
83735
- { alias: "fable", label: "Fable" }
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
- return Object.values(SR_MODEL_ALIASES2).includes(lower);
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 { alias, label } of EXTRA_CLAUDE_ALIASES2) {
83874
- const already = claudeOptions.some((o) => o.label.toLowerCase() === label.toLowerCase() || o.label.toLowerCase() === alias.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}${alias}` }]);
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.34";
99324
- var COMMIT_SHA = "73a1b218";
99325
- var COMMIT_DATE = "2026-07-29T05:33:45Z";
99326
- var LATEST_PR = 3981;
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, expandSrAlias2(action.arg), configured);
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 activePath = join68(smAgentDir, ".active-session-model");
112568
- if (existsSync57(activePath)) {
112569
- try {
112570
- const launched = readFileSync60(activePath, "utf8").trim();
112571
- const configured = (() => {
112572
- const d = switchroomExecJson(["agent", "list"]);
112573
- const raw = d?.agents?.find((a) => a.name === getMyAgentName())?.model ?? null;
112574
- return resolveMainModel(raw ?? undefined);
112575
- })();
112576
- const isApplyBoot = launched.length > 0 && launched !== configured;
112577
- sessionModelSource.setOverride(isApplyBoot ? launched : null, { verify: true });
112578
- const modelBootCardDeps = {
112579
- agent: getMyAgentName(),
112580
- chat: modelSwitchMarkerChat,
112581
- log: (line) => process.stderr.write(line),
112582
- sendCard: (chatId, body, opts) => lockedBot.api.sendMessage(chatId, body, opts)
112583
- };
112584
- if (isApplyBoot) {
112585
- sessionModelSource.setDivergenceHandler(buildServedModelDivergenceHandler(modelBootCardDeps));
112586
- }
112587
- const confirmation = modelSwitchReason != null ? classifyModelSwitchConfirmation({
112588
- reason: modelSwitchReason,
112589
- launched,
112590
- configured
112591
- }) : null;
112592
- process.stderr.write(formatModelRelaunchDiagLog({
112593
- agent: getMyAgentName(),
112594
- launched,
112595
- configured,
112596
- confirmation,
112597
- isApplyBoot
112598
- }));
112599
- if (confirmation != null) {
112600
- deliverModelSwitchBootNotice({
112601
- ...modelBootCardDeps,
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
- hasSessionModelAlert: existsSync57(join68(smAgentDir, ".session-model-alert"))
112604
- });
112605
- }
112606
- } catch {}
112607
- }
112608
- const activeEffortPath = join68(smAgentDir, ".active-session-effort");
112609
- if (existsSync57(activeEffortPath)) {
112610
- try {
112611
- const launchedEffort = readFileSync60(activeEffortPath, "utf8").trim();
112612
- const configuredEffort = getConfiguredEffortForPersist();
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
- try {
112625
- unlinkSync30(alertPath);
112626
- } catch {}
112627
- if (alertText && alertText.length > 0) {
112628
- const operators = loadAccess().allowFrom;
112629
- for (const operator of operators) {
112630
- if (!operator)
112631
- continue;
112632
- 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)}
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
- process.stderr.write(`telegram gateway: session-model: LiteLLM-down override drop \u2014 ${alertText}
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
  }