switchroom 0.19.35 → 0.19.36
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/skill-validate-pretool.mjs +15 -2
- package/dist/cli/switchroom.js +672 -441
- package/dist/host-control/main.js +149 -3
- package/package.json +4 -2
- package/profiles/_base/start.sh.hbs +37 -12
- package/telegram-plugin/dist/gateway/gateway.js +311 -93
- package/telegram-plugin/format.ts +70 -15
- 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/secret-detect/db-uri.ts +90 -0
- package/telegram-plugin/secret-detect/index.ts +24 -1
- package/telegram-plugin/secret-detect/inert-values.ts +147 -0
- package/telegram-plugin/secret-detect/kv-scanner.ts +108 -0
- package/telegram-plugin/secret-detect/patterns.ts +24 -4
- package/telegram-plugin/tests/format-consistency.test.ts +93 -0
- package/telegram-plugin/tests/gateway-session-model-relaunch.test.ts +40 -2
- package/telegram-plugin/tests/ipc-server-validate-operator.test.ts +20 -11
- package/telegram-plugin/tests/outbound-send-path.test.ts +1 -1
- package/telegram-plugin/tests/secret-detect-cross-engine.test.ts +263 -0
- package/telegram-plugin/tests/secret-detect-write-path.test.ts +403 -0
- package/telegram-plugin/tests/turn-flush-safety.test.ts +2 -2
- package/vendor/hindsight-memory/scripts/lib/client.py +58 -0
- package/vendor/hindsight-memory/scripts/lib/secret_patterns.json +431 -0
- package/vendor/hindsight-memory/scripts/lib/secret_redact.py +563 -0
- package/vendor/hindsight-memory/scripts/lib/secret_redaction_vectors.json +397 -0
- package/vendor/hindsight-memory/scripts/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.36";
|
|
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.36",
|
|
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
|