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
|
@@ -21311,7 +21311,7 @@ function allocateAgentUid(name) {
|
|
|
21311
21311
|
}
|
|
21312
21312
|
|
|
21313
21313
|
// src/build-info.ts
|
|
21314
|
-
var VERSION = "0.19.
|
|
21314
|
+
var VERSION = "0.19.37";
|
|
21315
21315
|
|
|
21316
21316
|
// src/setup/hindsight-recall-tunables.ts
|
|
21317
21317
|
var RECALL_DEADLINE_HEADROOM_SECONDS = 2;
|
|
@@ -27290,16 +27290,22 @@ var STRUCTURED_PATTERNS = [
|
|
|
27290
27290
|
},
|
|
27291
27291
|
{
|
|
27292
27292
|
rule_id: "bearer_auth_header",
|
|
27293
|
-
regex: /Authorization\s*[:=]\s*Bearer\s+([A-Za-z0-9._\-+=]+)/
|
|
27293
|
+
regex: /Authorization\s*[:=]\s*Bearer\s+([A-Za-z0-9._\-+=]+)/gi,
|
|
27294
27294
|
captureIndex: 1,
|
|
27295
27295
|
slugHint: "bearer_token"
|
|
27296
27296
|
},
|
|
27297
27297
|
{
|
|
27298
27298
|
rule_id: "bearer_loose",
|
|
27299
|
-
regex: /\bBearer\s+([A-Za-z0-9._\-+=]{18,})\b/
|
|
27299
|
+
regex: /\bBearer\s+([A-Za-z0-9._\-+=]{18,})\b/gi,
|
|
27300
27300
|
captureIndex: 1,
|
|
27301
27301
|
slugHint: "bearer_token"
|
|
27302
27302
|
},
|
|
27303
|
+
{
|
|
27304
|
+
rule_id: "basic_auth_header",
|
|
27305
|
+
regex: /Authorization\s*[:=]\s*Basic\s+([A-Za-z0-9+/=]{8,})/gi,
|
|
27306
|
+
captureIndex: 1,
|
|
27307
|
+
slugHint: "basic_auth"
|
|
27308
|
+
},
|
|
27303
27309
|
{
|
|
27304
27310
|
rule_id: "pem_private_key",
|
|
27305
27311
|
regex: /-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]+?-----END [A-Z ]*PRIVATE KEY-----/g,
|
|
@@ -27358,9 +27364,108 @@ function shannonEntropy(s) {
|
|
|
27358
27364
|
return h;
|
|
27359
27365
|
}
|
|
27360
27366
|
|
|
27367
|
+
// telegram-plugin/secret-detect/inert-values.ts
|
|
27368
|
+
var INERT_VALUE_RE = [
|
|
27369
|
+
/^\[REDACTED(?::[A-Za-z0-9_]+)?\]$/i,
|
|
27370
|
+
/^\$\{?[A-Za-z_][A-Za-z0-9_]*\}?$/,
|
|
27371
|
+
/^\{\{[^\n\r\u2028\u2029]*\}\}$/,
|
|
27372
|
+
/^<[^<>\n\r\u2028\u2029]*>$/,
|
|
27373
|
+
/^%[A-Za-z_][A-Za-z0-9_]*%$/,
|
|
27374
|
+
/^vault:[A-Za-z0-9_./-]*$/i,
|
|
27375
|
+
/^[*x•.]+$/i,
|
|
27376
|
+
/^(?:process\.env|import\.meta\.env|os\.environ)(?:\.[A-Za-z_$][A-Za-z0-9_$]*|\[["']?[A-Za-z_$][A-Za-z0-9_$]*["']?\])*$/,
|
|
27377
|
+
/^(?: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,
|
|
27378
|
+
/^[a-z]+(?: [a-z]+){2,}$/
|
|
27379
|
+
];
|
|
27380
|
+
var CREDENTIAL_RUN_RE = /[A-Za-z0-9]{12,}/g;
|
|
27381
|
+
var MIXED_CLASS_RUN_MIN = 12;
|
|
27382
|
+
var SINGLE_CLASS_RUN_MIN = 16;
|
|
27383
|
+
function runIsCredentialShaped(run) {
|
|
27384
|
+
let classes = 0;
|
|
27385
|
+
if (/[a-z]/.test(run))
|
|
27386
|
+
classes++;
|
|
27387
|
+
if (/[A-Z]/.test(run))
|
|
27388
|
+
classes++;
|
|
27389
|
+
if (/[0-9]/.test(run))
|
|
27390
|
+
classes++;
|
|
27391
|
+
return run.length >= (classes >= 2 ? MIXED_CLASS_RUN_MIN : SINGLE_CLASS_RUN_MIN);
|
|
27392
|
+
}
|
|
27393
|
+
function hasCredentialShapedRun(value) {
|
|
27394
|
+
for (const run of value.match(CREDENTIAL_RUN_RE) ?? []) {
|
|
27395
|
+
if (runIsCredentialShaped(run))
|
|
27396
|
+
return true;
|
|
27397
|
+
}
|
|
27398
|
+
return false;
|
|
27399
|
+
}
|
|
27400
|
+
function isInertValue(value) {
|
|
27401
|
+
if (!INERT_VALUE_RE.some((re) => re.test(value)))
|
|
27402
|
+
return false;
|
|
27403
|
+
return !hasCredentialShapedRun(value);
|
|
27404
|
+
}
|
|
27405
|
+
var INERT_GATED_RULES = new Set([
|
|
27406
|
+
"env_key_value",
|
|
27407
|
+
"json_secret_field",
|
|
27408
|
+
"cli_flag"
|
|
27409
|
+
]);
|
|
27410
|
+
var TRAILING_PUNCT_RE = /[.,;:!?)\]}]+$/;
|
|
27411
|
+
function stripTrailingPunctuation(value) {
|
|
27412
|
+
return value.replace(TRAILING_PUNCT_RE, "");
|
|
27413
|
+
}
|
|
27414
|
+
|
|
27361
27415
|
// telegram-plugin/secret-detect/kv-scanner.ts
|
|
27362
27416
|
var KV_RE = /\b([A-Za-z_][A-Za-z0-9_-]*(?:password|passwd|token|secret|key|api[_-]?key))\s*[:=]\s*["']?([^\s"'\\]{8,})["']?/gi;
|
|
27363
27417
|
var KV_ENTROPY_THRESHOLD = 4;
|
|
27418
|
+
var MEMORABLE_PW_RE = /\b([A-Za-z0-9_-]*(?:password|passwd|passphrase|pwd))\b\s*(?:[:=]\s*|\s+is\s+)(["']?)([^\s"']{8,64})\2/gi;
|
|
27419
|
+
var MEMORABLE_PW_RULE_ID = "memorable_password";
|
|
27420
|
+
var MEMORABLE_PW_MIN_CLASSES = 2;
|
|
27421
|
+
function charClassCount(value) {
|
|
27422
|
+
let n = 0;
|
|
27423
|
+
if (/[a-z]/.test(value))
|
|
27424
|
+
n++;
|
|
27425
|
+
if (/[A-Z]/.test(value))
|
|
27426
|
+
n++;
|
|
27427
|
+
if (/[0-9]/.test(value))
|
|
27428
|
+
n++;
|
|
27429
|
+
if (/[^A-Za-z0-9]/.test(value))
|
|
27430
|
+
n++;
|
|
27431
|
+
return n;
|
|
27432
|
+
}
|
|
27433
|
+
function looksLikeMemorablePassword(value) {
|
|
27434
|
+
if (isInertValue(value))
|
|
27435
|
+
return false;
|
|
27436
|
+
const core = stripTrailingPunctuation(value);
|
|
27437
|
+
if (core.length < 8 || core.length > 64)
|
|
27438
|
+
return false;
|
|
27439
|
+
if (isInertValue(core))
|
|
27440
|
+
return false;
|
|
27441
|
+
if (new Set(core).size < 4)
|
|
27442
|
+
return false;
|
|
27443
|
+
return charClassCount(core) >= MEMORABLE_PW_MIN_CLASSES;
|
|
27444
|
+
}
|
|
27445
|
+
function scanMemorablePasswords(text) {
|
|
27446
|
+
const hits = [];
|
|
27447
|
+
MEMORABLE_PW_RE.lastIndex = 0;
|
|
27448
|
+
let m;
|
|
27449
|
+
while ((m = MEMORABLE_PW_RE.exec(text)) !== null) {
|
|
27450
|
+
const keyName = m[1];
|
|
27451
|
+
const value = m[3];
|
|
27452
|
+
if (!value || !looksLikeMemorablePassword(value))
|
|
27453
|
+
continue;
|
|
27454
|
+
const valueOffsetInMatch = m[0].indexOf(value, keyName.length);
|
|
27455
|
+
if (valueOffsetInMatch < 0)
|
|
27456
|
+
continue;
|
|
27457
|
+
const start = m.index + valueOffsetInMatch;
|
|
27458
|
+
hits.push({
|
|
27459
|
+
rule_id: MEMORABLE_PW_RULE_ID,
|
|
27460
|
+
start,
|
|
27461
|
+
end: start + value.length,
|
|
27462
|
+
matched_text: value,
|
|
27463
|
+
key_name: keyName,
|
|
27464
|
+
confidence: "ambiguous"
|
|
27465
|
+
});
|
|
27466
|
+
}
|
|
27467
|
+
return hits;
|
|
27468
|
+
}
|
|
27364
27469
|
function scanKeyValue(text) {
|
|
27365
27470
|
const hits = [];
|
|
27366
27471
|
KV_RE.lastIndex = 0;
|
|
@@ -27369,6 +27474,8 @@ function scanKeyValue(text) {
|
|
|
27369
27474
|
const [, keyName, value] = m;
|
|
27370
27475
|
if (!value)
|
|
27371
27476
|
continue;
|
|
27477
|
+
if (isInertValue(value))
|
|
27478
|
+
continue;
|
|
27372
27479
|
const h = shannonEntropy(value);
|
|
27373
27480
|
if (h < KV_ENTROPY_THRESHOLD)
|
|
27374
27481
|
continue;
|
|
@@ -27389,6 +27496,37 @@ function scanKeyValue(text) {
|
|
|
27389
27496
|
return hits;
|
|
27390
27497
|
}
|
|
27391
27498
|
|
|
27499
|
+
// telegram-plugin/secret-detect/db-uri.ts
|
|
27500
|
+
var DB_URI_RE = /\b([a-zA-Z][a-zA-Z0-9+.-]+):\/\/([^\s:/@]+):([^\s/?#]+)@([^\s/@?#]+)/g;
|
|
27501
|
+
var DB_URI_RULE_ID = "db_uri_password";
|
|
27502
|
+
function isInertPassword(value) {
|
|
27503
|
+
if (value.startsWith("[REDACTED"))
|
|
27504
|
+
return true;
|
|
27505
|
+
return /^[*x]+$/i.test(value);
|
|
27506
|
+
}
|
|
27507
|
+
function scanDbUris(text) {
|
|
27508
|
+
const hits = [];
|
|
27509
|
+
DB_URI_RE.lastIndex = 0;
|
|
27510
|
+
let m;
|
|
27511
|
+
while ((m = DB_URI_RE.exec(text)) !== null) {
|
|
27512
|
+
const scheme = m[1];
|
|
27513
|
+
const user = m[2];
|
|
27514
|
+
const password = m[3];
|
|
27515
|
+
if (isInertPassword(password))
|
|
27516
|
+
continue;
|
|
27517
|
+
const start = m.index + scheme.length + 3 + user.length + 1;
|
|
27518
|
+
hits.push({
|
|
27519
|
+
rule_id: DB_URI_RULE_ID,
|
|
27520
|
+
start,
|
|
27521
|
+
end: start + password.length,
|
|
27522
|
+
matched_text: password,
|
|
27523
|
+
key_name: `${scheme}_password`,
|
|
27524
|
+
confidence: "ambiguous"
|
|
27525
|
+
});
|
|
27526
|
+
}
|
|
27527
|
+
return hits;
|
|
27528
|
+
}
|
|
27529
|
+
|
|
27392
27530
|
// telegram-plugin/secret-detect/generic-entropy.ts
|
|
27393
27531
|
var CANDIDATE_RE = /[A-Za-z0-9]{28,}/g;
|
|
27394
27532
|
var GENERIC_MIN_DISTINCT = 18;
|
|
@@ -27562,6 +27700,8 @@ function detectSecrets(text) {
|
|
|
27562
27700
|
const globalStart = win.offset + matchStart;
|
|
27563
27701
|
const globalEnd = globalStart + cap.length;
|
|
27564
27702
|
const keyName = p.rule_id === "env_key_value" ? m[1] : undefined;
|
|
27703
|
+
if (INERT_GATED_RULES.has(p.rule_id) && isInertValue(cap))
|
|
27704
|
+
continue;
|
|
27565
27705
|
if (p.rule_id === "env_key_value") {
|
|
27566
27706
|
const ENV_KV_MIN_LEN = 12;
|
|
27567
27707
|
const ENV_KV_MIN_ENTROPY = 3.5;
|
|
@@ -27584,6 +27724,12 @@ function detectSecrets(text) {
|
|
|
27584
27724
|
for (const h of kvHits) {
|
|
27585
27725
|
raw.push({ ...h, start: h.start + win.offset, end: h.end + win.offset });
|
|
27586
27726
|
}
|
|
27727
|
+
for (const h of scanDbUris(win.text)) {
|
|
27728
|
+
raw.push({ ...h, start: h.start + win.offset, end: h.end + win.offset });
|
|
27729
|
+
}
|
|
27730
|
+
for (const h of scanMemorablePasswords(win.text)) {
|
|
27731
|
+
raw.push({ ...h, start: h.start + win.offset, end: h.end + win.offset });
|
|
27732
|
+
}
|
|
27587
27733
|
const genHits = scanGenericSecrets(win.text);
|
|
27588
27734
|
for (const h of genHits) {
|
|
27589
27735
|
raw.push({ ...h, start: h.start + win.offset, end: h.end + win.offset });
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "switchroom",
|
|
3
3
|
"//version": "NOT the release version — source of truth is the git tag, resolved by scripts/build.mjs:resolveVersion() (see CLAUDE.md > Standard release process). This field is stale by design and only the Layer-4 dev/non-tag fallback for build.mjs + src/cli/resolve-version.ts; do NOT bump it expecting a release to pick it up. npm-pack tarball naming needs a real version — do that as an UNCOMMITTED pack-time bump (see release step 6), never a committed one.",
|
|
4
|
-
"version": "0.19.
|
|
4
|
+
"version": "0.19.37",
|
|
5
5
|
"description": "Run Claude Code 24/7 on your Claude Pro/Max subscription over Telegram. Open-source alternative to OpenClaw and NanoClaw — no API keys.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|
|
@@ -28,8 +28,10 @@
|
|
|
28
28
|
"test:vitest": "vitest run",
|
|
29
29
|
"test:bun": "bun test telegram-plugin/tests/agent-state-dir-preload.test.ts telegram-plugin/tests/catch-all-forwarded-history.test.ts src/vault/grants.test.ts src/vault/grants-db.test.ts src/vault/write-grants.test.ts src/vault/broker/server-grants.test.ts src/vault/broker/server-write-grants.test.ts src/vault/broker/server-scope-persist.test.ts src/vault/broker/server-tokenless-scope.test.ts src/vault/broker/server-mint-grant-passphrase-attest.test.ts src/vault/broker/server-passphrase-attest.test.ts src/vault/broker/server-mint-grant-posture-attest.test.ts src/vault/broker/server-admin-only-keys.test.ts src/vault/broker/client-token.test.ts src/vault/broker/server-unlock.test.ts src/vault/broker/auto-unlock.test.ts src/vault/broker/drift-detection.test.ts tests/vault-broker-passphrase.test.ts src/cli/vault-get-broker.test.ts src/vault/resolver-via-broker.test.ts src/vault/broker/scope.test.ts src/vault/broker/server.test.ts src/litellm/provision-apply-e2e.test.ts src/drive/disconnect.test.ts src/drive/grants.test.ts src/drive/oauth.test.ts src/drive/onboarding.test.ts src/drive/reconciler.test.ts src/drive/vault-slots.test.ts src/drive/wrapper.test.ts src/vault/approvals/kernel.test.ts src/vault/approvals/approval-origin.test.ts src/vault/approvals/self-approval-bypass.test.ts src/vault/approvals/schema-idempotent.test.ts src/vault/broker/server-approvals.test.ts telegram-plugin/tests/boot-probes.test.ts telegram-plugin/tests/boot-version-string.test.ts telegram-plugin/tests/history.test.ts telegram-plugin/tests/cross-turn-card-gate.test.ts telegram-plugin/tests/emission-authority-open-gate.test.ts telegram-plugin/tests/emission-authority-ping-gate.test.ts telegram-plugin/tests/emission-authority-card-drain-gate.test.ts telegram-plugin/tests/per-topic-current-turn.test.ts telegram-plugin/tests/history-reaper.test.ts telegram-plugin/tests/ipc-server-client.test.ts telegram-plugin/tests/ipc-server-race.test.ts telegram-plugin/tests/ipc-server-query-pending-permission.test.ts telegram-plugin/tests/ipc-server-check-pre-approved.test.ts telegram-plugin/tests/gateway-bridge.test.ts telegram-plugin/tests/gateway-startup-mutex.test.ts telegram-plugin/tests/gateway-clean-shutdown-marker.test.ts telegram-plugin/tests/boot-card-dedupe.test.ts telegram-plugin/tests/boot-card-reason.test.ts telegram-plugin/tests/progress-update.test.ts telegram-plugin/tests/quota-cache.test.ts telegram-plugin/tests/silent-reply-guard.test.ts telegram-plugin/tests/unhandled-rejection-policy.test.ts telegram-plugin/tests/registry-turns.test.ts telegram-plugin/registry/subagents.test.ts telegram-plugin/registry/subagents-bugs.test.ts telegram-plugin/tests/subagent-watcher-parent-turn-key.test.ts telegram-plugin/tests/subagent-nested-dispatch.test.ts telegram-plugin/tests/nested-worker-visibility-harness.test.ts telegram-plugin/tests/turns-writer.test.ts telegram-plugin/tests/resume-inbound-builder.test.ts telegram-plugin/tests/subagent-tracker-hooks.test.ts telegram-plugin/tests/resolve-calling-subagent.test.ts telegram-plugin/tests/gateway-update-placeholder-dispatch.test.ts telegram-plugin/tests/status-query-telemetry.test.ts telegram-plugin/tests/reaction-trigger.test.ts telegram-plugin/tests/reaction-trigger-flow.test.ts telegram-plugin/tests/subagent-watcher-workflow-visibility.test.ts telegram-plugin/uat/load-env.test.ts telegram-plugin/uat/feed-matcher.test.ts telegram-plugin/uat/uat-driver.test.ts telegram-plugin/gateway/webhook-ingest-server.test.ts telegram-plugin/tests/skill-proposal-card.test.ts",
|
|
30
30
|
"test:watch": "vitest",
|
|
31
|
-
"lint": "tsc --noEmit && node scripts/check-plugin-references.mjs && bash scripts/check-bot-api-wrapping.sh && node scripts/check-bun-test-imports.mjs && node scripts/check-test-runner-coverage.mjs && node scripts/check-bun-module-mock-scope.mjs && node scripts/check-no-pii-secrets.mjs && node scripts/check-vault-test-hermeticity.mjs && node scripts/check-auth-test-hermeticity.mjs && node scripts/check-agent-state-dir-hermeticity.mjs && node scripts/check-no-broadcast-delivery.mjs && node scripts/check-stale-tool-descriptions.mjs && node scripts/check-mcp-instructions-budget.mjs && node scripts/check-web-subscription-honest.mjs && node scripts/check-no-unpinned-npx-playwright.mjs && node scripts/check-gateway-line-ratchet.mjs && node scripts/check-retry-flood-hooks.mjs && node scripts/check-callback-ctx-wrapping.mjs && node scripts/check-status-pin-single-path.mjs && node scripts/check-litellm-config-guard.mjs && node scripts/check-release-asset-names.mjs && node scripts/check-agent-attribution-trailers.mjs",
|
|
31
|
+
"lint": "tsc --noEmit && node scripts/check-plugin-references.mjs && bash scripts/check-bot-api-wrapping.sh && node scripts/check-bun-test-imports.mjs && node scripts/check-test-runner-coverage.mjs && node scripts/check-bun-module-mock-scope.mjs && node scripts/check-no-pii-secrets.mjs && node scripts/check-vault-test-hermeticity.mjs && node scripts/check-auth-test-hermeticity.mjs && node scripts/check-agent-state-dir-hermeticity.mjs && node scripts/check-no-broadcast-delivery.mjs && node scripts/check-stale-tool-descriptions.mjs && node scripts/check-mcp-instructions-budget.mjs && node scripts/check-web-subscription-honest.mjs && node scripts/check-no-unpinned-npx-playwright.mjs && node scripts/check-gateway-line-ratchet.mjs && node scripts/check-retry-flood-hooks.mjs && node scripts/check-callback-ctx-wrapping.mjs && node scripts/check-status-pin-single-path.mjs && node scripts/check-litellm-config-guard.mjs && node scripts/check-release-asset-names.mjs && node scripts/check-agent-attribution-trailers.mjs && node scripts/check-hindsight-write-redaction.mjs && bun scripts/check-secret-pattern-parity.ts",
|
|
32
32
|
"lint:tsc": "tsc --noEmit",
|
|
33
|
+
"lint:hindsight-write-redaction": "node scripts/check-hindsight-write-redaction.mjs",
|
|
34
|
+
"lint:secret-pattern-parity": "bun scripts/check-secret-pattern-parity.ts",
|
|
33
35
|
"lint:plugin-references": "node scripts/check-plugin-references.mjs",
|
|
34
36
|
"lint:bot-api-wrapping": "bash scripts/check-bot-api-wrapping.sh",
|
|
35
37
|
"lint:bun-test-imports": "node scripts/check-bun-test-imports.mjs",
|
|
@@ -390,6 +390,10 @@ x-litellm-tags: agent:$SWITCHROOM_AGENT_NAME,profile:${SWITCHROOM_AGENT_PROFILE:
|
|
|
390
390
|
# ephemeral). Refresh-or-remove every boot so a stale snapshot can never
|
|
391
391
|
# re-apply a consumed override on a later restart.
|
|
392
392
|
rm -f "{{agentDir}}/.session-model.boot-snapshot" "{{agentDir}}/.session-model-boot-attempts.boot-snapshot"
|
|
393
|
+
# Resolution runs later, after the bounded LiteLLM probe. Remove the prior
|
|
394
|
+
# boot's completion barrier before forking the gateway so it cannot consume
|
|
395
|
+
# stale active-model/effort/alert files while this boot is still resolving.
|
|
396
|
+
rm -f "{{agentDir}}/.session-model-resolved"
|
|
393
397
|
if [ -f "{{agentDir}}/.session-model" ]; then
|
|
394
398
|
# A failed copy (ENOSPC, perms) must not be a SILENT drop — the resolution
|
|
395
399
|
# block still falls back to the live carrier, but the gateway may consume
|
|
@@ -1954,6 +1958,13 @@ rm -f "{{agentDir}}/.session-model.boot-snapshot" "{{agentDir}}/.session-model-b
|
|
|
1954
1958
|
# this guard covers only the configured default, which the live-config read
|
|
1955
1959
|
# above makes a mainline path (pinning `model: fable` in yaml is the normal
|
|
1956
1960
|
# way to run Fable).
|
|
1961
|
+
#
|
|
1962
|
+
# Snapshot the resolved model BEFORE this guard can rewrite it. _DECLARED_
|
|
1963
|
+
# ROUTING (below) must reflect this boot's actual INTENT — the resolved
|
|
1964
|
+
# override or configured default — not a post-demotion landing, or the exact
|
|
1965
|
+
# divergence this file exists to catch (missing key silently demoting
|
|
1966
|
+
# fable → opus) becomes invisible to the check built to catch it.
|
|
1967
|
+
_DECLARED_MODEL="$_EFFECTIVE_MODEL"
|
|
1957
1968
|
case "$_EFFECTIVE_MODEL" in
|
|
1958
1969
|
fable|claude-fable-5)
|
|
1959
1970
|
if [ -z "$_LITELLM_OK" ]; then
|
|
@@ -2080,23 +2091,27 @@ export SWITCHROOM_SESSION_MODEL="$_EFFECTIVE_MODEL"
|
|
|
2080
2091
|
#
|
|
2081
2092
|
# Deterministic record of WHERE this boot actually landed, written immediately
|
|
2082
2093
|
# before exec claude (overwrite-per-boot, one line). Consumers: the gateway
|
|
2083
|
-
# boot card's "routing" row and any operator shell. _DECLARED_ROUTING is
|
|
2084
|
-
#
|
|
2085
|
-
#
|
|
2086
|
-
#
|
|
2087
|
-
#
|
|
2094
|
+
# boot card's "routing" row and any operator shell. _DECLARED_ROUTING is derived
|
|
2095
|
+
# from $_DECLARED_MODEL (the resolved override/default snapshotted BEFORE the
|
|
2096
|
+
# missing-key opus demotion above), so a legitimate session override is
|
|
2097
|
+
# compared against its own POST-REPOINT intent rather than the configured
|
|
2098
|
+
# default's render-time model class — and a missing-key demotion still reads
|
|
2099
|
+
# as a genuine divergence instead of chasing its own landed model.
|
|
2088
2100
|
# Divergence (landed ≠ declared) appends a consume-once .session-model-alert
|
|
2089
2101
|
# line the gateway relays to the operator, but ONLY when the (landed, declared)
|
|
2090
2102
|
# PAIR differs from the PREVIOUS boot's pair — a persistently degraded agent
|
|
2091
2103
|
# alerts once, not on every restart, yet a declared-only flip (operator changes
|
|
2092
2104
|
# the configured model class while the agent stays stuck on the same wrong
|
|
2093
2105
|
# landed mode) still re-alerts.
|
|
2094
|
-
|
|
2095
|
-
#
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2106
|
+
if [ -z "${SWITCHROOM_LITELLM_DECLARED:-}" ]; then
|
|
2107
|
+
# No LiteLLM routing was configured for this agent at boot entry.
|
|
2108
|
+
_DECLARED_ROUTING="direct-oauth"
|
|
2109
|
+
else
|
|
2110
|
+
case "$_DECLARED_MODEL" in
|
|
2111
|
+
sr-*|fable|claude-fable-5) _DECLARED_ROUTING="router-root" ;;
|
|
2112
|
+
*) _DECLARED_ROUTING="passthrough" ;;
|
|
2113
|
+
esac
|
|
2114
|
+
fi
|
|
2100
2115
|
case "${ANTHROPIC_BASE_URL:-}" in
|
|
2101
2116
|
"") _ROUTING_MODE="direct-oauth" ;;
|
|
2102
2117
|
*/anthropic) _ROUTING_MODE="passthrough" ;;
|
|
@@ -2114,7 +2129,17 @@ echo "routing: $_routing_line" >&2
|
|
|
2114
2129
|
if [ "$_ROUTING_MODE" != "$_DECLARED_ROUTING" ] && { [ "$_ROUTING_MODE" != "$_prev_routing_mode" ] || [ "$_DECLARED_ROUTING" != "$_prev_declared_routing" ]; }; then
|
|
2115
2130
|
printf 'Routing divergence: this agent landed on `%s` (base `%s`, model `%s`) but its declared routing is `%s`. Check `%s/.routing-mode` and the boot log for the cause (LiteLLM key/provision or proxy reachability).\n' "$_ROUTING_MODE" "${ANTHROPIC_BASE_URL:-direct}" "$_EFFECTIVE_MODEL" "$_DECLARED_ROUTING" "{{agentDir}}" >> "{{agentDir}}/.session-model-alert" 2>/dev/null || true
|
|
2116
2131
|
fi
|
|
2117
|
-
unset _prev_routing_mode _prev_declared_routing _routing_line
|
|
2132
|
+
unset _prev_routing_mode _prev_declared_routing _routing_line _DECLARED_MODEL
|
|
2133
|
+
|
|
2134
|
+
# Publish completion only after every boot-resolution output is final: active
|
|
2135
|
+
# model, active effort, routing mode, and any alert append. Atomic rename keeps
|
|
2136
|
+
# the gateway from observing a partially-written barrier.
|
|
2137
|
+
_session_model_resolved_tmp="{{agentDir}}/.session-model-resolved.tmp.$$"
|
|
2138
|
+
if printf '%s\n' "$$" > "$_session_model_resolved_tmp" 2>/dev/null; then
|
|
2139
|
+
mv -f "$_session_model_resolved_tmp" "{{agentDir}}/.session-model-resolved" 2>/dev/null || true
|
|
2140
|
+
fi
|
|
2141
|
+
rm -f "$_session_model_resolved_tmp"
|
|
2142
|
+
unset _session_model_resolved_tmp
|
|
2118
2143
|
|
|
2119
2144
|
{{#if useSwitchroomPlugin}}
|
|
2120
2145
|
if [ -n "$APPEND_PROMPT" ]; then
|
|
@@ -94,7 +94,7 @@ const TOOL_SCHEMAS = [
|
|
|
94
94
|
// client and this detail was being silently truncated away.
|
|
95
95
|
'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 — 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. ' +
|
|
96
96
|
// Format modes: likewise moved out of the truncated instructions string.
|
|
97
|
-
'FORMAT:
|
|
97
|
+
'FORMAT: by default your text is rendered as rich GFM markdown (Bot API 10.1) — 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 — there is no separate HTML engine and no auto-escaping.',
|
|
98
98
|
inputSchema: {
|
|
99
99
|
type: 'object',
|
|
100
100
|
properties: {
|
|
@@ -105,7 +105,7 @@ const TOOL_SCHEMAS = [
|
|
|
105
105
|
message_thread_id: { type: 'string', description: 'Forum topic thread ID. Auto-applied from the last inbound message in the same chat if not specified.' },
|
|
106
106
|
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.' },
|
|
107
107
|
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) — 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.' },
|
|
108
|
-
format: { type: 'string', enum: ['html', 'markdownv2', 'text'], description: "Rendering mode. 'html'
|
|
108
|
+
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 — no separate HTML engine, no auto-escaping." },
|
|
109
109
|
disable_web_page_preview: { type: 'boolean', description: 'Disable link preview thumbnails. Default: true.' },
|
|
110
110
|
protect_content: { type: 'boolean', description: 'When true, Telegram prevents the message from being forwarded or saved.' },
|
|
111
111
|
quote_text: { type: 'string', description: 'Surgical quote: specific text to highlight from the reply_to message. Requires reply_to.' },
|
|
@@ -180,7 +180,7 @@ const TOOL_SCHEMAS = [
|
|
|
180
180
|
chat_id: { type: 'string' },
|
|
181
181
|
message_id: { type: 'string' },
|
|
182
182
|
text: { type: 'string' },
|
|
183
|
-
format: { type: 'string', enum: ['html', 'markdownv2', 'text'], description: "Rendering mode. 'html'
|
|
183
|
+
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 — no separate HTML engine, no auto-escaping." },
|
|
184
184
|
},
|
|
185
185
|
required: ['chat_id', 'message_id', 'text'],
|
|
186
186
|
},
|
|
@@ -341,7 +341,7 @@ const TOOL_SCHEMAS = [
|
|
|
341
341
|
type: 'object',
|
|
342
342
|
properties: {
|
|
343
343
|
chat_id: { type: 'string', description: 'Chat that should receive the question. Pass from inbound meta.' },
|
|
344
|
-
question: { type: 'string', description: 'The question text. Plain text or
|
|
344
|
+
question: { type: 'string', description: 'The question text. Plain text or GFM markdown (rendered through the same rich path as replies). Keep it short — buttons render below.' },
|
|
345
345
|
options: {
|
|
346
346
|
type: 'array',
|
|
347
347
|
items: { type: 'string' },
|
|
@@ -25061,7 +25061,7 @@ var mcp = new Server({ name: "telegram", version: "1.0.0" }, {
|
|
|
25061
25061
|
var TOOL_SCHEMAS = [
|
|
25062
25062
|
{
|
|
25063
25063
|
name: "reply",
|
|
25064
|
-
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:
|
|
25064
|
+
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.',
|
|
25065
25065
|
inputSchema: {
|
|
25066
25066
|
type: "object",
|
|
25067
25067
|
properties: {
|
|
@@ -25072,7 +25072,7 @@ var TOOL_SCHEMAS = [
|
|
|
25072
25072
|
message_thread_id: { type: "string", description: "Forum topic thread ID. Auto-applied from the last inbound message in the same chat if not specified." },
|
|
25073
25073
|
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." },
|
|
25074
25074
|
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." },
|
|
25075
|
-
format: { type: "string", enum: ["html", "markdownv2", "text"], description: "Rendering mode. 'html'
|
|
25075
|
+
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." },
|
|
25076
25076
|
disable_web_page_preview: { type: "boolean", description: "Disable link preview thumbnails. Default: true." },
|
|
25077
25077
|
protect_content: { type: "boolean", description: "When true, Telegram prevents the message from being forwarded or saved." },
|
|
25078
25078
|
quote_text: { type: "string", description: "Surgical quote: specific text to highlight from the reply_to message. Requires reply_to." },
|
|
@@ -25146,7 +25146,7 @@ var TOOL_SCHEMAS = [
|
|
|
25146
25146
|
chat_id: { type: "string" },
|
|
25147
25147
|
message_id: { type: "string" },
|
|
25148
25148
|
text: { type: "string" },
|
|
25149
|
-
format: { type: "string", enum: ["html", "markdownv2", "text"], description: "Rendering mode. 'html'
|
|
25149
|
+
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." }
|
|
25150
25150
|
},
|
|
25151
25151
|
required: ["chat_id", "message_id", "text"]
|
|
25152
25152
|
}
|
|
@@ -25303,7 +25303,7 @@ var TOOL_SCHEMAS = [
|
|
|
25303
25303
|
type: "object",
|
|
25304
25304
|
properties: {
|
|
25305
25305
|
chat_id: { type: "string", description: "Chat that should receive the question. Pass from inbound meta." },
|
|
25306
|
-
question: { type: "string", description: "The question text. Plain text or
|
|
25306
|
+
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." },
|
|
25307
25307
|
options: {
|
|
25308
25308
|
type: "array",
|
|
25309
25309
|
items: { type: "string" },
|