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
@@ -2120,7 +2120,7 @@ var init_esm = __esm(() => {
2120
2120
  });
2121
2121
 
2122
2122
  // src/build-info.ts
2123
- var VERSION = "0.19.34", COMMIT_SHA = "73a1b218";
2123
+ var VERSION = "0.19.36", COMMIT_SHA = "ba505b15";
2124
2124
 
2125
2125
  // src/cli/resolve-version.ts
2126
2126
  import { existsSync, readFileSync } from "node:fs";
@@ -15552,16 +15552,56 @@ var init_users = __esm(() => {
15552
15552
  init_merge();
15553
15553
  });
15554
15554
 
15555
+ // src/config/thinking-effort-risk.ts
15556
+ function isRiskyThinkingEffort(effort) {
15557
+ if (!effort)
15558
+ return false;
15559
+ return RISKY_EFFORTS.has(effort.trim().toLowerCase());
15560
+ }
15561
+ function emitsAdaptiveThinking(model) {
15562
+ if (!model)
15563
+ return false;
15564
+ const m = model.trim().toLowerCase();
15565
+ return m === "opus" || m.startsWith("claude-opus-");
15566
+ }
15567
+ function isAdaptiveThinkingOpus(model) {
15568
+ if (!model)
15569
+ return false;
15570
+ const m = model.trim().toLowerCase();
15571
+ return m.startsWith("claude-opus-4");
15572
+ }
15573
+ function assessThinkingEffortRisk(model, effort) {
15574
+ if (!isRiskyThinkingEffort(effort))
15575
+ return { risky: false };
15576
+ if (!isAdaptiveThinkingOpus(model))
15577
+ return { risky: false };
15578
+ return {
15579
+ risky: true,
15580
+ 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.`
15581
+ };
15582
+ }
15583
+ var CLAUDE_CLI_THINKING_MERGE_FIX_VERSION = "2.1.156", RISKY_EFFORTS;
15584
+ var init_thinking_effort_risk = __esm(() => {
15585
+ RISKY_EFFORTS = new Set(["medium", "high", "xhigh", "max"]);
15586
+ });
15587
+
15555
15588
  // telegram-plugin/gateway/model-command.ts
15556
15589
  function isClaudeModel(name) {
15557
15590
  const lower = name.toLowerCase();
15558
15591
  if (MODEL_ALIASES.includes(lower))
15559
15592
  return true;
15593
+ if (lower in CLAUDE_MODEL_ALIASES)
15594
+ return true;
15560
15595
  return lower.startsWith("claude-");
15561
15596
  }
15562
- var MODEL_ALIASES;
15597
+ var MODEL_ALIASES, CLAUDE_MODEL_ALIASES;
15563
15598
  var init_model_command = __esm(() => {
15599
+ init_thinking_effort_risk();
15564
15600
  MODEL_ALIASES = ["opus", "sonnet", "haiku", "fable", "default"];
15601
+ CLAUDE_MODEL_ALIASES = {
15602
+ opus48: "claude-opus-4-8",
15603
+ "opus-4-8": "claude-opus-4-8"
15604
+ };
15565
15605
  });
15566
15606
 
15567
15607
  // src/litellm/timeout-budget.ts
@@ -15790,7 +15830,7 @@ function hindsightPgEnv(overrides = new Map) {
15790
15830
  }
15791
15831
  return out;
15792
15832
  }
15793
- var HINDSIGHT_PG_MEM_LIMIT_MIB_FOR_DERIVATION, HINDSIGHT_PG_APP_ANON_MIB = 2560, HINDSIGHT_PG_PAGE_CACHE_FLOOR_MIB = 2048, HINDSIGHT_PG_SHARED_BUFFERS_BUDGET_MIB, HINDSIGHT_PG_DEFAULT_SHARED_BUFFERS_MIB = 3072, HINDSIGHT_PG_DEFAULT_EFFECTIVE_CACHE_SIZE_MIB = 7168, HINDSIGHT_PG_EFFECTIVE_CACHE_SIZE_ENV = "SWITCHROOM_HINDSIGHT_PG_EFFECTIVE_CACHE_SIZE", HINDSIGHT_PG_SHARED_BUFFERS_ENV = "SWITCHROOM_HINDSIGHT_PG_SHARED_BUFFERS", HINDSIGHT_PG_DEFAULTS, HINDSIGHT_PG_ENV_KEYS;
15833
+ var HINDSIGHT_PG_MEM_LIMIT_MIB_FOR_DERIVATION, HINDSIGHT_PG_APP_ANON_MIB = 2560, HINDSIGHT_PG_PAGE_CACHE_FLOOR_MIB = 2048, HINDSIGHT_PG_SHARED_BUFFERS_BUDGET_MIB, HINDSIGHT_PG_DEFAULT_SHARED_BUFFERS_MIB = 3072, HINDSIGHT_PG_DEFAULT_EFFECTIVE_CACHE_SIZE_MIB = 7168, HINDSIGHT_PG_EFFECTIVE_CACHE_SIZE_ENV = "SWITCHROOM_HINDSIGHT_PG_EFFECTIVE_CACHE_SIZE", HINDSIGHT_PG_SHARED_BUFFERS_ENV = "SWITCHROOM_HINDSIGHT_PG_SHARED_BUFFERS", HINDSIGHT_PG_FSYNC_ENV = "SWITCHROOM_HINDSIGHT_PG_FSYNC", HINDSIGHT_PG_DEFAULT_FSYNC = "on", HINDSIGHT_PG_DEFAULTS, HINDSIGHT_PG_ENV_KEYS;
15794
15834
  var init_hindsight_pg_defaults = __esm(() => {
15795
15835
  HINDSIGHT_PG_MEM_LIMIT_MIB_FOR_DERIVATION = 16 * 1024;
15796
15836
  HINDSIGHT_PG_SHARED_BUFFERS_BUDGET_MIB = HINDSIGHT_PG_MEM_LIMIT_MIB_FOR_DERIVATION - HINDSIGHT_PG_APP_ANON_MIB - HINDSIGHT_PG_PAGE_CACHE_FLOOR_MIB;
@@ -15799,7 +15839,8 @@ var init_hindsight_pg_defaults = __esm(() => {
15799
15839
  HINDSIGHT_PG_EFFECTIVE_CACHE_SIZE_ENV,
15800
15840
  pgMib(HINDSIGHT_PG_DEFAULT_EFFECTIVE_CACHE_SIZE_MIB)
15801
15841
  ],
15802
- [HINDSIGHT_PG_SHARED_BUFFERS_ENV, pgMib(HINDSIGHT_PG_DEFAULT_SHARED_BUFFERS_MIB)]
15842
+ [HINDSIGHT_PG_SHARED_BUFFERS_ENV, pgMib(HINDSIGHT_PG_DEFAULT_SHARED_BUFFERS_MIB)],
15843
+ [HINDSIGHT_PG_FSYNC_ENV, HINDSIGHT_PG_DEFAULT_FSYNC]
15803
15844
  ];
15804
15845
  HINDSIGHT_PG_ENV_KEYS = new Set(HINDSIGHT_PG_DEFAULTS.map(([k]) => k));
15805
15846
  });
@@ -35491,7 +35532,7 @@ class AuthBrokerClient {
35491
35532
  sock.once("error", onError);
35492
35533
  sock.once("connect", () => {
35493
35534
  sock.removeListener("error", onError);
35494
- sock.on("data", (chunk) => this.onData(chunk));
35535
+ sock.on("data", (chunk2) => this.onData(chunk2));
35495
35536
  sock.on("error", (err) => this.onSocketError(err));
35496
35537
  sock.on("close", () => this.onSocketClose());
35497
35538
  this.socket = sock;
@@ -35505,8 +35546,8 @@ class AuthBrokerClient {
35505
35546
  this.connecting = null;
35506
35547
  }
35507
35548
  }
35508
- onData(chunk) {
35509
- this.buffer += chunk.toString("utf8");
35549
+ onData(chunk2) {
35550
+ this.buffer += chunk2.toString("utf8");
35510
35551
  let idx;
35511
35552
  while ((idx = this.buffer.indexOf(`
35512
35553
  `)) !== -1) {
@@ -39484,11 +39525,11 @@ function readAuditRaw(logPath, opts = {}) {
39484
39525
  const gen = `${logPath}.${n}`;
39485
39526
  if (!existsSync51(gen))
39486
39527
  break;
39487
- const chunk = tailBytes(gen, budget, strict);
39488
- if (chunk.length === 0)
39528
+ const chunk2 = tailBytes(gen, budget, strict);
39529
+ if (chunk2.length === 0)
39489
39530
  continue;
39490
- chunks.push(chunk);
39491
- budget -= Buffer.byteLength(chunk, "utf-8");
39531
+ chunks.push(chunk2);
39532
+ budget -= Buffer.byteLength(chunk2, "utf-8");
39492
39533
  }
39493
39534
  return chunks.reverse().filter((c) => c.length > 0).map((c) => c.endsWith(`
39494
39535
  `) ? c : c + `
@@ -44410,8 +44451,8 @@ async function hostdRequest(opts, req) {
44410
44451
  reject(err);
44411
44452
  }
44412
44453
  });
44413
- socket.on("data", (chunk) => {
44414
- buf += chunk.toString("utf8");
44454
+ socket.on("data", (chunk2) => {
44455
+ buf += chunk2.toString("utf8");
44415
44456
  if (Buffer.byteLength(buf, "utf8") > MAX_FRAME_BYTES3 * 2) {
44416
44457
  if (settled)
44417
44458
  return;
@@ -44826,39 +44867,6 @@ var init_doctor_status = __esm(() => {
44826
44867
  init_source();
44827
44868
  });
44828
44869
 
44829
- // src/config/thinking-effort-risk.ts
44830
- function isRiskyThinkingEffort(effort) {
44831
- if (!effort)
44832
- return false;
44833
- return RISKY_EFFORTS.has(effort.trim().toLowerCase());
44834
- }
44835
- function emitsAdaptiveThinking(model) {
44836
- if (!model)
44837
- return false;
44838
- const m = model.trim().toLowerCase();
44839
- return m === "opus" || m.startsWith("claude-opus-");
44840
- }
44841
- function isAdaptiveThinkingOpus(model) {
44842
- if (!model)
44843
- return false;
44844
- const m = model.trim().toLowerCase();
44845
- return m.startsWith("claude-opus-4");
44846
- }
44847
- function assessThinkingEffortRisk(model, effort) {
44848
- if (!isRiskyThinkingEffort(effort))
44849
- return { risky: false };
44850
- if (!isAdaptiveThinkingOpus(model))
44851
- return { risky: false };
44852
- return {
44853
- risky: true,
44854
- 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.`
44855
- };
44856
- }
44857
- var CLAUDE_CLI_THINKING_MERGE_FIX_VERSION = "2.1.156", RISKY_EFFORTS;
44858
- var init_thinking_effort_risk = __esm(() => {
44859
- RISKY_EFFORTS = new Set(["medium", "high", "xhigh", "max"]);
44860
- });
44861
-
44862
44870
  // src/manifest.ts
44863
44871
  import {
44864
44872
  existsSync as existsSync61,
@@ -45983,7 +45991,7 @@ var init_install_cron = () => {};
45983
45991
  function inParts(memories) {
45984
45992
  return Math.round(memories * PARTS_PER_MEMORY);
45985
45993
  }
45986
- var RING_MAX = 8, MAX_SAMPLE_AGE_MS, MIN_RETAIN_SAMPLES = 20, RETAIN_FAILURE_RATE = 0.1, PARTS_PER_MEMORY = 6, QUEUE_FLOOR_MEMORIES = 100, QUEUE_FLOOR, QUEUE_GROWTH_FRACTION = 0.1, QUEUE_GROWTH_MIN_ABS_MEMORIES = 20, QUEUE_GROWTH_MIN_ABS, BREACHES_TO_FIRE_LEVEL = 2, BREACHES_TO_FIRE_EDGE = 1, CLEARS_TO_RESOLVE = 2, RENOTIFY_MS, DEFAULT_METRICS_URL = "http://127.0.0.1:18888/metrics", DEFAULT_CONTAINER = "switchroom-hindsight", METRICS_MAX_BYTES, METRICS_TIMEOUT_MS = 1e4, RECALL_OWN_BANK_TIMEOUT_WARN = 0.05, RECALL_OWN_BANK_TIMEOUT_PAGE = 0.15, RECALL_ZERO_MEMORY_WARN = 0.25, RECALL_ZERO_MEMORY_PAGE = 0.4, RECALL_POOL_MEDIAN_WARN = 8, RECALL_POOL_MEDIAN_PAGE = 3, RECALL_SCORE_P50_WARN = 0.02, RECALL_SCORE_P50_PAGE = 0.01, CONSOLIDATION_AGE_WARN_S, CONSOLIDATION_AGE_PAGE_S, RECALL_MIN_SAMPLES = 30, LLM_FAILURE_RATE_WARN = 0.02, LLM_FAILURE_RATE_PAGE = 0.1, LLM_MIN_SAMPLES = 20;
45994
+ var RING_MAX = 8, MAX_SAMPLE_AGE_MS, MIN_RETAIN_SAMPLES = 20, RETAIN_FAILURE_RATE = 0.1, PARTS_PER_MEMORY = 6, QUEUE_FLOOR_MEMORIES = 100, QUEUE_FLOOR, QUEUE_GROWTH_FRACTION = 0.1, QUEUE_GROWTH_MIN_ABS_MEMORIES = 20, QUEUE_GROWTH_MIN_ABS, BREACHES_TO_FIRE_LEVEL = 2, BREACHES_TO_FIRE_EDGE = 1, CLEARS_TO_RESOLVE = 2, RENOTIFY_MS, DEFAULT_METRICS_URL = "http://127.0.0.1:18888/metrics", DEFAULT_CONTAINER = "switchroom-hindsight", METRICS_MAX_BYTES, METRICS_TIMEOUT_MS = 1e4, RECALL_OWN_BANK_TIMEOUT_WARN = 0.05, RECALL_OWN_BANK_TIMEOUT_PAGE = 0.15, RECALL_ZERO_MEMORY_WARN = 0.25, RECALL_ZERO_MEMORY_PAGE = 0.4, RECALL_POOL_MEDIAN_WARN = 8, RECALL_POOL_MEDIAN_PAGE = 3, RECALL_SCORE_P50_WARN = 0.02, RECALL_SCORE_P50_PAGE = 0.01, CONSOLIDATION_AGE_WARN_S, CONSOLIDATION_AGE_PAGE_S, CONSOLIDATION_FAILURE_STREAK_WARN = 3, CONSOLIDATION_FAILURE_STREAK_PAGE = 10, CONSOLIDATION_STREAK_RECENCY_S, PENDING_CONSOLIDATION_FLOOR = 500, PENDING_CONSOLIDATION_GROWTH_FRACTION = 0.5, PENDING_CONSOLIDATION_GROWTH_WARN_ABS = 500, PENDING_CONSOLIDATION_GROWTH_PAGE_ABS = 5000, VECTOR_INDEX_SAMPLE_ROWS = 3, RECALL_MIN_SAMPLES = 30, LLM_FAILURE_RATE_WARN = 0.02, LLM_FAILURE_RATE_PAGE = 0.1, LLM_MIN_SAMPLES = 20;
45987
45995
  var init_thresholds = __esm(() => {
45988
45996
  MAX_SAMPLE_AGE_MS = 3 * 60 * 60000;
45989
45997
  QUEUE_FLOOR = inParts(QUEUE_FLOOR_MEMORIES);
@@ -45992,6 +46000,7 @@ var init_thresholds = __esm(() => {
45992
46000
  METRICS_MAX_BYTES = 16 * 1024 * 1024;
45993
46001
  CONSOLIDATION_AGE_WARN_S = 2 * 60 * 60;
45994
46002
  CONSOLIDATION_AGE_PAGE_S = 12 * 60 * 60;
46003
+ CONSOLIDATION_STREAK_RECENCY_S = 2 * 60 * 60;
45995
46004
  });
45996
46005
 
45997
46006
  // src/hindsight-watch/types.ts
@@ -46022,7 +46031,9 @@ function loadState(path6) {
46022
46031
  const ring = s.ring.filter(isSample).map((sample) => ({
46023
46032
  ...sample,
46024
46033
  recall: normalizeRecallSample(sample.recall),
46025
- consolidation: normalizeConsolidationSample(sample.consolidation)
46034
+ consolidation: normalizeConsolidationSample(sample.consolidation),
46035
+ banks: normalizeBankSample(sample.banks),
46036
+ vectorIndex: normalizeVectorIndexSample(sample.vectorIndex)
46026
46037
  })).slice(-RING_MAX);
46027
46038
  const signals = typeof s.signals === "object" && s.signals !== null ? s.signals : {};
46028
46039
  return { v: 1, ring, signals };
@@ -46066,6 +46077,36 @@ function normalizeConsolidationSample(x) {
46066
46077
  return null;
46067
46078
  return x;
46068
46079
  }
46080
+ function normalizeBankSample(x) {
46081
+ if (x === null || typeof x !== "object")
46082
+ return null;
46083
+ const b = x;
46084
+ if (!Array.isArray(b.streaks) || !Array.isArray(b.pending))
46085
+ return null;
46086
+ const streaks = b.streaks.filter((r) => {
46087
+ if (typeof r !== "object" || r === null)
46088
+ return false;
46089
+ const s = r;
46090
+ return typeof s.bank === "string" && typeof s.operationType === "string" && isCount(s.streak) && isCount(s.newestFailureAgeS);
46091
+ });
46092
+ const pending = b.pending.filter((r) => {
46093
+ if (typeof r !== "object" || r === null)
46094
+ return false;
46095
+ const p = r;
46096
+ return typeof p.bank === "string" && isCount(p.pending);
46097
+ });
46098
+ return { streaks, pending };
46099
+ }
46100
+ function normalizeVectorIndexSample(x) {
46101
+ if (x === null || typeof x !== "object")
46102
+ return null;
46103
+ const v = x;
46104
+ if (!isCount(v.probed))
46105
+ return null;
46106
+ if (!Array.isArray(v.corrupt) || !v.corrupt.every((s) => typeof s === "string"))
46107
+ return null;
46108
+ return { probed: v.probed, corrupt: v.corrupt };
46109
+ }
46069
46110
  function isSample(x) {
46070
46111
  if (typeof x !== "object" || x === null)
46071
46112
  return false;
@@ -71102,9 +71143,600 @@ import { execFileSync as execFileSync11 } from "node:child_process";
71102
71143
 
71103
71144
  // src/agents/handoff-summarizer.ts
71104
71145
  init_atomic();
71105
- init_observation_scopes();
71106
71146
  import { readFileSync as readFileSync19, writeFileSync as writeFileSync9, renameSync as renameSync7, mkdirSync as mkdirSync16, existsSync as existsSync23, statSync as statSync11, readdirSync as readdirSync10 } from "node:fs";
71107
71147
  import { join as join18 } from "node:path";
71148
+
71149
+ // telegram-plugin/secret-detect/patterns.ts
71150
+ var ANCHORED_PATTERNS = [
71151
+ { rule_id: "anthropic_api_key", regex: /\b(sk-ant-[A-Za-z0-9_-]{8,})\b/g, captureIndex: 1, slugHint: "anthropic_api_key" },
71152
+ { rule_id: "anthropic_oauth_code", regex: /(?:^|\s)([A-Za-z0-9_-]{20,}#[A-Za-z0-9_-]{20,})(?=\s|$)/gm, captureIndex: 1, slugHint: "anthropic_oauth_code" },
71153
+ { rule_id: "openai_api_key", regex: /\b(sk-[A-Za-z0-9_-]{20,})\b/g, captureIndex: 1, slugHint: "openai_api_key" },
71154
+ { rule_id: "github_pat_classic", regex: /\b(ghp_[A-Za-z0-9]{20,})\b/g, captureIndex: 1, slugHint: "github_pat" },
71155
+ { rule_id: "github_pat_fine_grained", regex: /\b(github_pat_[A-Za-z0-9_]{20,})\b/g, captureIndex: 1, slugHint: "github_pat" },
71156
+ { rule_id: "slack_token", regex: /\b(xox[baprs]-[A-Za-z0-9-]{10,})\b/g, captureIndex: 1, slugHint: "slack_token" },
71157
+ { rule_id: "slack_app_token", regex: /\b(xapp-[A-Za-z0-9-]{10,})\b/g, captureIndex: 1, slugHint: "slack_app_token" },
71158
+ { rule_id: "groq_api_key", regex: /\b(gsk_[A-Za-z0-9_-]{10,})\b/g, captureIndex: 1, slugHint: "groq_api_key" },
71159
+ { rule_id: "google_api_key", regex: /\b(AIza[0-9A-Za-z\-_]{20,})\b/g, captureIndex: 1, slugHint: "google_api_key" },
71160
+ { rule_id: "perplexity_api_key", regex: /\b(pplx-[A-Za-z0-9_-]{10,})\b/g, captureIndex: 1, slugHint: "perplexity_api_key" },
71161
+ { rule_id: "npm_token", regex: /\b(npm_[A-Za-z0-9]{10,})\b/g, captureIndex: 1, slugHint: "npm_token" },
71162
+ { rule_id: "telegram_bot_token_prefixed", regex: /\bbot(\d{6,}:[A-Za-z0-9_-]{20,})\b/g, captureIndex: 1, slugHint: "telegram_bot_token" },
71163
+ { rule_id: "telegram_bot_token", regex: /\b(\d{6,}:[A-Za-z0-9_-]{20,})\b/g, captureIndex: 1, slugHint: "telegram_bot_token" },
71164
+ { rule_id: "laravel_sanctum_token", regex: /\b(\d+\|[A-Za-z0-9]{40,})\b/g, captureIndex: 1, slugHint: "api_token" },
71165
+ { rule_id: "aws_access_key", regex: /\b(AKIA[0-9A-Z]{16})\b/g, captureIndex: 1, slugHint: "aws_access_key" },
71166
+ { rule_id: "jwt", regex: /\b(eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,})\b/g, captureIndex: 1, slugHint: "jwt" }
71167
+ ];
71168
+ var STRUCTURED_PATTERNS = [
71169
+ {
71170
+ rule_id: "env_key_value",
71171
+ regex: /\b([A-Z0-9_]*(?:KEY|TOKEN|SECRET|PASSWORD|PASSWD))\b\s*[=:]\s*(["']?)([^\s"'\\]+)\2/g,
71172
+ captureIndex: 3,
71173
+ slugHint: "env"
71174
+ },
71175
+ {
71176
+ rule_id: "json_secret_field",
71177
+ regex: /"(?:apiKey|token|secret|password|passwd|accessToken|refreshToken)"\s*:\s*"([^"]+)"/g,
71178
+ captureIndex: 1,
71179
+ slugHint: "json_secret"
71180
+ },
71181
+ {
71182
+ rule_id: "cli_flag",
71183
+ regex: /--(?:api[-_]?key|hook[-_]?token|token|secret|password|passwd)\s+(["']?)([^\s"']+)\1/g,
71184
+ captureIndex: 2,
71185
+ slugHint: "cli_flag"
71186
+ },
71187
+ {
71188
+ rule_id: "bearer_auth_header",
71189
+ regex: /Authorization\s*[:=]\s*Bearer\s+([A-Za-z0-9._\-+=]+)/gi,
71190
+ captureIndex: 1,
71191
+ slugHint: "bearer_token"
71192
+ },
71193
+ {
71194
+ rule_id: "bearer_loose",
71195
+ regex: /\bBearer\s+([A-Za-z0-9._\-+=]{18,})\b/gi,
71196
+ captureIndex: 1,
71197
+ slugHint: "bearer_token"
71198
+ },
71199
+ {
71200
+ rule_id: "basic_auth_header",
71201
+ regex: /Authorization\s*[:=]\s*Basic\s+([A-Za-z0-9+/=]{8,})/gi,
71202
+ captureIndex: 1,
71203
+ slugHint: "basic_auth"
71204
+ },
71205
+ {
71206
+ rule_id: "pem_private_key",
71207
+ regex: /-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]+?-----END [A-Z ]*PRIVATE KEY-----/g,
71208
+ captureIndex: 0,
71209
+ slugHint: "pem_private_key"
71210
+ }
71211
+ ];
71212
+ var PROVIDER_PATTERNS = [
71213
+ { rule_id: "slack_webhook", regex: /(https:\/\/hooks\.slack\.com\/services\/[A-Za-z0-9_/]+)/g, captureIndex: 1, slugHint: "slack_webhook" },
71214
+ { rule_id: "stripe_live_secret", regex: /\b(sk_live_[A-Za-z0-9]{24,})\b/g, captureIndex: 1, slugHint: "stripe_key" },
71215
+ { rule_id: "stripe_restricted", regex: /\b(rk_live_[A-Za-z0-9]{24,})\b/g, captureIndex: 1, slugHint: "stripe_key" },
71216
+ { rule_id: "sendgrid_api_key", regex: /\b(SG\.[A-Za-z0-9_-]{22}\.[A-Za-z0-9_-]{43})\b/g, captureIndex: 1, slugHint: "sendgrid_key" },
71217
+ { rule_id: "gitlab_pat", regex: /\b(glpat-[A-Za-z0-9_-]{20})\b/g, captureIndex: 1, slugHint: "gitlab_pat" },
71218
+ { rule_id: "huggingface_token", regex: /\b(hf_[A-Za-z0-9]{34,})\b/g, captureIndex: 1, slugHint: "huggingface_token" },
71219
+ { rule_id: "twilio_api_key", regex: /\b(SK[0-9a-f]{32})\b/g, captureIndex: 1, slugHint: "twilio_api_key" },
71220
+ { rule_id: "mailgun_key", regex: /\b(key-[0-9a-f]{32})\b/g, captureIndex: 1, slugHint: "mailgun_key" },
71221
+ { rule_id: "digitalocean_pat", regex: /\b(dop_v1_[a-f0-9]{64})\b/g, captureIndex: 1, slugHint: "digitalocean_token" },
71222
+ { rule_id: "digitalocean_oauth", regex: /\b(doo_v1_[a-f0-9]{64})\b/g, captureIndex: 1, slugHint: "digitalocean_token" },
71223
+ { rule_id: "digitalocean_refresh", regex: /\b(dor_v1_[a-f0-9]{64})\b/g, captureIndex: 1, slugHint: "digitalocean_token" },
71224
+ { rule_id: "doppler_token", regex: /\b(dp\.(?:pt|st|ct|sa|scim|audit)\.[A-Za-z0-9]{40,44})\b/g, captureIndex: 1, slugHint: "doppler_token" },
71225
+ { rule_id: "linear_api_key", regex: /\b(lin_api_[A-Za-z0-9]{40})\b/g, captureIndex: 1, slugHint: "linear_api_key" },
71226
+ { rule_id: "shopify_access_token", regex: /\b(shpat_[a-fA-F0-9]{32})\b/g, captureIndex: 1, slugHint: "shopify_token" },
71227
+ { rule_id: "shopify_shared_secret", regex: /\b(shpss_[a-fA-F0-9]{32})\b/g, captureIndex: 1, slugHint: "shopify_token" },
71228
+ { rule_id: "shopify_private_app", regex: /\b(shppa_[a-fA-F0-9]{32})\b/g, captureIndex: 1, slugHint: "shopify_token" },
71229
+ { rule_id: "square_access_token", regex: /\b(sq0atp-[A-Za-z0-9_-]{22})\b/g, captureIndex: 1, slugHint: "square_token" },
71230
+ { rule_id: "square_oauth_secret", regex: /\b(sq0csp-[A-Za-z0-9_-]{43})\b/g, captureIndex: 1, slugHint: "square_token" },
71231
+ { rule_id: "newrelic_key", regex: /\b(NRAK-[A-Z0-9]{27})\b/g, captureIndex: 1, slugHint: "newrelic_key" },
71232
+ { rule_id: "notion_token", regex: /\b(ntn_[A-Za-z0-9]{46})\b/g, captureIndex: 1, slugHint: "notion_token" },
71233
+ { rule_id: "planetscale_password", regex: /\b(pscale_pw_[A-Za-z0-9_.-]{43})\b/g, captureIndex: 1, slugHint: "planetscale_token" },
71234
+ { rule_id: "planetscale_token", regex: /\b(pscale_tkn_[A-Za-z0-9_.-]{43})\b/g, captureIndex: 1, slugHint: "planetscale_token" },
71235
+ { rule_id: "supabase_service_key", regex: /\b(sbp_[a-f0-9]{40})\b/g, captureIndex: 1, slugHint: "supabase_key" },
71236
+ { rule_id: "atlassian_token", regex: /\b(ATATT[A-Za-z0-9_\-=]{20,})\b/g, captureIndex: 1, slugHint: "atlassian_token" },
71237
+ { rule_id: "dropbox_token", regex: /\b(sl\.[A-Za-z0-9_-]{130,})/g, captureIndex: 1, slugHint: "dropbox_token" },
71238
+ { rule_id: "databricks_token", regex: /\b(dapi[a-f0-9]{32})\b/g, captureIndex: 1, slugHint: "databricks_token" },
71239
+ { rule_id: "grafana_service_account", regex: /\b(glsa_[A-Za-z0-9]{32}_[a-fA-F0-9]{8})\b/g, captureIndex: 1, slugHint: "grafana_token" },
71240
+ { rule_id: "pypi_token", regex: /\b(pypi-AgEIcHlwaS[A-Za-z0-9_-]{50,})/g, captureIndex: 1, slugHint: "pypi_token" },
71241
+ { rule_id: "aws_temp_access_key", regex: /\b(ASIA[0-9A-Z]{16})\b/g, captureIndex: 1, slugHint: "aws_access_key" },
71242
+ { rule_id: "gcp_oauth_token", regex: /\b(ya29\.[A-Za-z0-9_-]{30,})/g, captureIndex: 1, slugHint: "gcp_oauth_token" }
71243
+ ];
71244
+ var ALL_PATTERNS = [...ANCHORED_PATTERNS, ...PROVIDER_PATTERNS, ...STRUCTURED_PATTERNS];
71245
+
71246
+ // telegram-plugin/secret-detect/entropy.ts
71247
+ function shannonEntropy(s) {
71248
+ if (s.length === 0)
71249
+ return 0;
71250
+ const counts = new Map;
71251
+ for (const ch of s) {
71252
+ counts.set(ch, (counts.get(ch) ?? 0) + 1);
71253
+ }
71254
+ let h = 0;
71255
+ const len = s.length;
71256
+ for (const c of counts.values()) {
71257
+ const p = c / len;
71258
+ h -= p * Math.log2(p);
71259
+ }
71260
+ return h;
71261
+ }
71262
+
71263
+ // telegram-plugin/secret-detect/inert-values.ts
71264
+ var INERT_VALUE_RE = [
71265
+ /^\[REDACTED(?::[A-Za-z0-9_]+)?\]$/i,
71266
+ /^\$\{?[A-Za-z_][A-Za-z0-9_]*\}?$/,
71267
+ /^\{\{[^\n\r\u2028\u2029]*\}\}$/,
71268
+ /^<[^<>\n\r\u2028\u2029]*>$/,
71269
+ /^%[A-Za-z_][A-Za-z0-9_]*%$/,
71270
+ /^vault:[A-Za-z0-9_./-]*$/i,
71271
+ /^[*x\u2022.]+$/i,
71272
+ /^(?:process\.env|import\.meta\.env|os\.environ)(?:\.[A-Za-z_$][A-Za-z0-9_$]*|\[["']?[A-Za-z_$][A-Za-z0-9_$]*["']?\])*$/,
71273
+ /^(?: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,
71274
+ /^[a-z]+(?: [a-z]+){2,}$/
71275
+ ];
71276
+ var CREDENTIAL_RUN_RE = /[A-Za-z0-9]{12,}/g;
71277
+ var MIXED_CLASS_RUN_MIN = 12;
71278
+ var SINGLE_CLASS_RUN_MIN = 16;
71279
+ function runIsCredentialShaped(run) {
71280
+ let classes = 0;
71281
+ if (/[a-z]/.test(run))
71282
+ classes++;
71283
+ if (/[A-Z]/.test(run))
71284
+ classes++;
71285
+ if (/[0-9]/.test(run))
71286
+ classes++;
71287
+ return run.length >= (classes >= 2 ? MIXED_CLASS_RUN_MIN : SINGLE_CLASS_RUN_MIN);
71288
+ }
71289
+ function hasCredentialShapedRun(value) {
71290
+ for (const run of value.match(CREDENTIAL_RUN_RE) ?? []) {
71291
+ if (runIsCredentialShaped(run))
71292
+ return true;
71293
+ }
71294
+ return false;
71295
+ }
71296
+ function isInertValue(value) {
71297
+ if (!INERT_VALUE_RE.some((re) => re.test(value)))
71298
+ return false;
71299
+ return !hasCredentialShapedRun(value);
71300
+ }
71301
+ var INERT_GATED_RULES = new Set([
71302
+ "env_key_value",
71303
+ "json_secret_field",
71304
+ "cli_flag"
71305
+ ]);
71306
+ var TRAILING_PUNCT_RE = /[.,;:!?)\]}]+$/;
71307
+ function stripTrailingPunctuation(value) {
71308
+ return value.replace(TRAILING_PUNCT_RE, "");
71309
+ }
71310
+
71311
+ // telegram-plugin/secret-detect/kv-scanner.ts
71312
+ var KV_RE = /\b([A-Za-z_][A-Za-z0-9_-]*(?:password|passwd|token|secret|key|api[_-]?key))\s*[:=]\s*["']?([^\s"'\\]{8,})["']?/gi;
71313
+ var KV_ENTROPY_THRESHOLD = 4;
71314
+ var MEMORABLE_PW_RE = /\b([A-Za-z0-9_-]*(?:password|passwd|passphrase|pwd))\b\s*(?:[:=]\s*|\s+is\s+)(["']?)([^\s"']{8,64})\2/gi;
71315
+ var MEMORABLE_PW_RULE_ID = "memorable_password";
71316
+ var MEMORABLE_PW_MIN_CLASSES = 2;
71317
+ function charClassCount(value) {
71318
+ let n = 0;
71319
+ if (/[a-z]/.test(value))
71320
+ n++;
71321
+ if (/[A-Z]/.test(value))
71322
+ n++;
71323
+ if (/[0-9]/.test(value))
71324
+ n++;
71325
+ if (/[^A-Za-z0-9]/.test(value))
71326
+ n++;
71327
+ return n;
71328
+ }
71329
+ function looksLikeMemorablePassword(value) {
71330
+ if (isInertValue(value))
71331
+ return false;
71332
+ const core = stripTrailingPunctuation(value);
71333
+ if (core.length < 8 || core.length > 64)
71334
+ return false;
71335
+ if (isInertValue(core))
71336
+ return false;
71337
+ if (new Set(core).size < 4)
71338
+ return false;
71339
+ return charClassCount(core) >= MEMORABLE_PW_MIN_CLASSES;
71340
+ }
71341
+ function scanMemorablePasswords(text) {
71342
+ const hits = [];
71343
+ MEMORABLE_PW_RE.lastIndex = 0;
71344
+ let m;
71345
+ while ((m = MEMORABLE_PW_RE.exec(text)) !== null) {
71346
+ const keyName = m[1];
71347
+ const value = m[3];
71348
+ if (!value || !looksLikeMemorablePassword(value))
71349
+ continue;
71350
+ const valueOffsetInMatch = m[0].indexOf(value, keyName.length);
71351
+ if (valueOffsetInMatch < 0)
71352
+ continue;
71353
+ const start = m.index + valueOffsetInMatch;
71354
+ hits.push({
71355
+ rule_id: MEMORABLE_PW_RULE_ID,
71356
+ start,
71357
+ end: start + value.length,
71358
+ matched_text: value,
71359
+ key_name: keyName,
71360
+ confidence: "ambiguous"
71361
+ });
71362
+ }
71363
+ return hits;
71364
+ }
71365
+ function scanKeyValue(text) {
71366
+ const hits = [];
71367
+ KV_RE.lastIndex = 0;
71368
+ let m;
71369
+ while ((m = KV_RE.exec(text)) !== null) {
71370
+ const [, keyName, value] = m;
71371
+ if (!value)
71372
+ continue;
71373
+ if (isInertValue(value))
71374
+ continue;
71375
+ const h = shannonEntropy(value);
71376
+ if (h < KV_ENTROPY_THRESHOLD)
71377
+ continue;
71378
+ const valueOffsetInMatch = m[0].indexOf(value, keyName.length);
71379
+ if (valueOffsetInMatch < 0)
71380
+ continue;
71381
+ const start = m.index + valueOffsetInMatch;
71382
+ const end = start + value.length;
71383
+ hits.push({
71384
+ rule_id: "kv_entropy",
71385
+ start,
71386
+ end,
71387
+ matched_text: value,
71388
+ key_name: keyName,
71389
+ confidence: "ambiguous"
71390
+ });
71391
+ }
71392
+ return hits;
71393
+ }
71394
+
71395
+ // telegram-plugin/secret-detect/db-uri.ts
71396
+ var DB_URI_RE = /\b([a-zA-Z][a-zA-Z0-9+.-]+):\/\/([^\s:/@]+):([^\s/?#]+)@([^\s/@?#]+)/g;
71397
+ var DB_URI_RULE_ID = "db_uri_password";
71398
+ function isInertPassword(value) {
71399
+ if (value.startsWith("[REDACTED"))
71400
+ return true;
71401
+ return /^[*x]+$/i.test(value);
71402
+ }
71403
+ function scanDbUris(text) {
71404
+ const hits = [];
71405
+ DB_URI_RE.lastIndex = 0;
71406
+ let m;
71407
+ while ((m = DB_URI_RE.exec(text)) !== null) {
71408
+ const scheme = m[1];
71409
+ const user = m[2];
71410
+ const password = m[3];
71411
+ if (isInertPassword(password))
71412
+ continue;
71413
+ const start = m.index + scheme.length + 3 + user.length + 1;
71414
+ hits.push({
71415
+ rule_id: DB_URI_RULE_ID,
71416
+ start,
71417
+ end: start + password.length,
71418
+ matched_text: password,
71419
+ key_name: `${scheme}_password`,
71420
+ confidence: "ambiguous"
71421
+ });
71422
+ }
71423
+ return hits;
71424
+ }
71425
+
71426
+ // telegram-plugin/secret-detect/generic-entropy.ts
71427
+ var CANDIDATE_RE = /[A-Za-z0-9]{28,}/g;
71428
+ var GENERIC_MIN_DISTINCT = 18;
71429
+ var MAX_GENERIC_HITS = 20;
71430
+ function hasDistinctChars(tok, n) {
71431
+ const seen = new Uint8Array(128);
71432
+ let distinct = 0;
71433
+ for (let i = 0;i < tok.length; i++) {
71434
+ const c = tok.charCodeAt(i);
71435
+ if (seen[c] === 0) {
71436
+ seen[c] = 1;
71437
+ if (++distinct >= n)
71438
+ return true;
71439
+ }
71440
+ }
71441
+ return false;
71442
+ }
71443
+ function hasDigit(tok) {
71444
+ for (let i = 0;i < tok.length; i++) {
71445
+ const c = tok.charCodeAt(i);
71446
+ if (c >= 48 && c <= 57)
71447
+ return true;
71448
+ }
71449
+ return false;
71450
+ }
71451
+ function scanGenericSecrets(text) {
71452
+ const hits = [];
71453
+ CANDIDATE_RE.lastIndex = 0;
71454
+ let m;
71455
+ while ((m = CANDIDATE_RE.exec(text)) !== null) {
71456
+ if (hits.length >= MAX_GENERIC_HITS)
71457
+ break;
71458
+ const tok = m[0];
71459
+ if (!hasDigit(tok))
71460
+ continue;
71461
+ if (!hasDistinctChars(tok, GENERIC_MIN_DISTINCT))
71462
+ continue;
71463
+ hits.push({
71464
+ rule_id: "generic_high_entropy",
71465
+ start: m.index,
71466
+ end: m.index + tok.length,
71467
+ matched_text: tok,
71468
+ confidence: "ambiguous"
71469
+ });
71470
+ }
71471
+ return hits;
71472
+ }
71473
+
71474
+ // telegram-plugin/secret-detect/chunker.ts
71475
+ var CHUNK_THRESHOLD = 32 * 1024;
71476
+ var WINDOW_SIZE = 16 * 1024;
71477
+ var OVERLAP = 8 * 1024;
71478
+ function chunk(text) {
71479
+ if (text.length <= CHUNK_THRESHOLD) {
71480
+ return [{ offset: 0, text }];
71481
+ }
71482
+ const out = [];
71483
+ let offset = 0;
71484
+ while (offset < text.length) {
71485
+ const end = Math.min(offset + WINDOW_SIZE, text.length);
71486
+ out.push({ offset, text: text.slice(offset, end) });
71487
+ if (end >= text.length)
71488
+ break;
71489
+ offset = end - OVERLAP;
71490
+ }
71491
+ return out;
71492
+ }
71493
+
71494
+ // telegram-plugin/secret-detect/suppressor.ts
71495
+ var MARKERS = ["test", "mock", "example", "fixture", "dummy"];
71496
+ var WINDOW = 40;
71497
+ var MARKER_RE = new RegExp(`\\b(?:${MARKERS.join("|")})\\b`, "i");
71498
+ function isSuppressed(text, start, end) {
71499
+ const left = Math.max(0, start - WINDOW);
71500
+ const right = Math.min(text.length, end + WINDOW);
71501
+ const context = text.slice(left, start) + text.slice(end, right);
71502
+ return MARKER_RE.test(context);
71503
+ }
71504
+
71505
+ // telegram-plugin/secret-detect/slug.ts
71506
+ function sanitizeKeyName(raw) {
71507
+ const up = raw.toUpperCase();
71508
+ const cleaned = up.replace(/[^A-Z0-9_]+/g, "_").replace(/_+/g, "_").replace(/^_+|_+$/g, "");
71509
+ return cleaned.length > 0 ? cleaned : "SECRET";
71510
+ }
71511
+ function datePart(now = new Date) {
71512
+ const y = now.getUTCFullYear();
71513
+ const m = String(now.getUTCMonth() + 1).padStart(2, "0");
71514
+ const d = String(now.getUTCDate()).padStart(2, "0");
71515
+ return `${y}${m}${d}`;
71516
+ }
71517
+ function deriveSlug(inputs, existing) {
71518
+ let base;
71519
+ if (inputs.key_name && inputs.key_name.trim().length > 0) {
71520
+ base = sanitizeKeyName(inputs.key_name);
71521
+ } else {
71522
+ base = `${inputs.rule_id}_${datePart(inputs.now)}`;
71523
+ }
71524
+ if (!existing.has(base))
71525
+ return base;
71526
+ let n = 2;
71527
+ while (existing.has(`${base}_${n}`))
71528
+ n++;
71529
+ return `${base}_${n}`;
71530
+ }
71531
+ // telegram-plugin/secret-detect/url-redact.ts
71532
+ var SENSITIVE_PARAMS = new Set([
71533
+ "token",
71534
+ "key",
71535
+ "api_key",
71536
+ "apikey",
71537
+ "secret",
71538
+ "access_token",
71539
+ "password",
71540
+ "pass",
71541
+ "auth",
71542
+ "client_secret",
71543
+ "refresh_token",
71544
+ "signature"
71545
+ ]);
71546
+ var URL_RE = /\b(?:https?|wss?|ftp):\/\/[^\s<>"']+/gi;
71547
+ function redactUrls(text) {
71548
+ return text.replace(URL_RE, (m) => {
71549
+ const redacted = redactOne(m);
71550
+ return redacted ?? m;
71551
+ });
71552
+ }
71553
+ function redactOne(raw) {
71554
+ let u;
71555
+ try {
71556
+ u = new URL(raw);
71557
+ } catch {
71558
+ return null;
71559
+ }
71560
+ let changed = false;
71561
+ if (u.username || u.password) {
71562
+ u.username = "***";
71563
+ u.password = "";
71564
+ changed = true;
71565
+ }
71566
+ for (const [key] of u.searchParams) {
71567
+ if (SENSITIVE_PARAMS.has(key.toLowerCase())) {
71568
+ u.searchParams.set(key, "***");
71569
+ changed = true;
71570
+ }
71571
+ }
71572
+ return changed ? u.toString() : null;
71573
+ }
71574
+
71575
+ // telegram-plugin/secret-detect/index.ts
71576
+ function detectSecrets(text) {
71577
+ if (!text || text.length === 0)
71578
+ return [];
71579
+ const windows = chunk(text);
71580
+ const raw = [];
71581
+ for (const win of windows) {
71582
+ for (const p of ALL_PATTERNS) {
71583
+ const re = new RegExp(p.regex.source, p.regex.flags.includes("g") ? p.regex.flags : p.regex.flags + "g");
71584
+ let m;
71585
+ while ((m = re.exec(win.text)) !== null) {
71586
+ if (m[0].length === 0) {
71587
+ re.lastIndex++;
71588
+ continue;
71589
+ }
71590
+ const cap = p.captureIndex === 0 ? m[0] : m[p.captureIndex];
71591
+ if (!cap)
71592
+ continue;
71593
+ const matchStart = p.captureIndex === 0 ? m.index : m.index + m[0].indexOf(cap);
71594
+ if (matchStart < 0)
71595
+ continue;
71596
+ const globalStart = win.offset + matchStart;
71597
+ const globalEnd = globalStart + cap.length;
71598
+ const keyName = p.rule_id === "env_key_value" ? m[1] : undefined;
71599
+ if (INERT_GATED_RULES.has(p.rule_id) && isInertValue(cap))
71600
+ continue;
71601
+ if (p.rule_id === "env_key_value") {
71602
+ const ENV_KV_MIN_LEN = 12;
71603
+ const ENV_KV_MIN_ENTROPY = 3.5;
71604
+ if (cap.length < ENV_KV_MIN_LEN)
71605
+ continue;
71606
+ if (shannonEntropy(cap) < ENV_KV_MIN_ENTROPY)
71607
+ continue;
71608
+ }
71609
+ raw.push({
71610
+ rule_id: p.rule_id,
71611
+ start: globalStart,
71612
+ end: globalEnd,
71613
+ matched_text: cap,
71614
+ key_name: keyName,
71615
+ confidence: "high"
71616
+ });
71617
+ }
71618
+ }
71619
+ const kvHits = scanKeyValue(win.text);
71620
+ for (const h of kvHits) {
71621
+ raw.push({ ...h, start: h.start + win.offset, end: h.end + win.offset });
71622
+ }
71623
+ for (const h of scanDbUris(win.text)) {
71624
+ raw.push({ ...h, start: h.start + win.offset, end: h.end + win.offset });
71625
+ }
71626
+ for (const h of scanMemorablePasswords(win.text)) {
71627
+ raw.push({ ...h, start: h.start + win.offset, end: h.end + win.offset });
71628
+ }
71629
+ const genHits = scanGenericSecrets(win.text);
71630
+ for (const h of genHits) {
71631
+ raw.push({ ...h, start: h.start + win.offset, end: h.end + win.offset });
71632
+ }
71633
+ }
71634
+ const deduped = dedupeRaw(raw);
71635
+ const final = dropOverlaps(deduped);
71636
+ const existing = new Set;
71637
+ const out = [];
71638
+ for (const h of final) {
71639
+ const suggested_slug = deriveSlug({ key_name: h.key_name, rule_id: h.rule_id }, existing);
71640
+ existing.add(suggested_slug);
71641
+ out.push({
71642
+ rule_id: h.rule_id,
71643
+ matched_text: h.matched_text,
71644
+ start: h.start,
71645
+ end: h.end,
71646
+ confidence: h.confidence,
71647
+ suppressed: isSuppressed(text, h.start, h.end),
71648
+ suggested_slug,
71649
+ key_name: h.key_name
71650
+ });
71651
+ }
71652
+ out.sort((a, b) => a.start - b.start);
71653
+ return out;
71654
+ }
71655
+ function dedupeRaw(raw) {
71656
+ const seen = new Map;
71657
+ for (const h of raw) {
71658
+ const key = `${h.start}:${h.end}`;
71659
+ const existing = seen.get(key);
71660
+ if (!existing) {
71661
+ seen.set(key, h);
71662
+ continue;
71663
+ }
71664
+ if (existing.confidence === "ambiguous" && h.confidence === "high") {
71665
+ seen.set(key, h);
71666
+ }
71667
+ }
71668
+ return Array.from(seen.values());
71669
+ }
71670
+ function dropOverlaps(hits) {
71671
+ const out = hits.filter((h) => !(h.confidence === "ambiguous" && hits.some((o) => o !== h && o.start <= h.start && o.end >= h.end && !(o.start === h.start && o.end === h.end))));
71672
+ out.sort((a, b) => a.start - b.start || a.end - b.end);
71673
+ return out;
71674
+ }
71675
+
71676
+ // telegram-plugin/secret-detect/redact.ts
71677
+ var REDACTED_MARKER = "[REDACTED]";
71678
+ function redact(text) {
71679
+ if (!text || text.length === 0)
71680
+ return text;
71681
+ const urlScrubbed = redactUrls(text);
71682
+ const hits = detectSecrets(urlScrubbed).filter((h) => h.rule_id !== "generic_high_entropy");
71683
+ if (hits.length === 0)
71684
+ return urlScrubbed;
71685
+ const sorted = [...hits].sort((a, b) => b.start - a.start);
71686
+ let out = urlScrubbed;
71687
+ for (const h of sorted) {
71688
+ out = out.slice(0, h.start) + redactedMarker(h.rule_id) + out.slice(h.end);
71689
+ }
71690
+ return out;
71691
+ }
71692
+ function redactedMarker(ruleId) {
71693
+ const trimmed = ruleId.replace(/^(kv|env)_/, "");
71694
+ if (!trimmed || trimmed === "key_value" || trimmed === "kv_entropy") {
71695
+ return REDACTED_MARKER;
71696
+ }
71697
+ return `[REDACTED:${trimmed}]`;
71698
+ }
71699
+ // src/memory/hindsight-write-redaction.ts
71700
+ function redactJsonStrings(value) {
71701
+ if (typeof value === "string")
71702
+ return redact(value);
71703
+ if (Array.isArray(value))
71704
+ return value.map(redactJsonStrings);
71705
+ if (value && typeof value === "object") {
71706
+ const out = {};
71707
+ for (const [k, v] of Object.entries(value)) {
71708
+ out[k] = redactJsonStrings(v);
71709
+ }
71710
+ return out;
71711
+ }
71712
+ return value;
71713
+ }
71714
+ var REDACTED_ITEM_FIELDS = ["content", "context"];
71715
+ function redactMemoryWriteBody(body) {
71716
+ if (!body || typeof body !== "object" || Array.isArray(body))
71717
+ return body;
71718
+ const src = body;
71719
+ if (!Array.isArray(src.items))
71720
+ return body;
71721
+ const items = src.items.map((item) => {
71722
+ if (!item || typeof item !== "object" || Array.isArray(item))
71723
+ return item;
71724
+ const row = { ...item };
71725
+ for (const field of REDACTED_ITEM_FIELDS) {
71726
+ if (typeof row[field] === "string") {
71727
+ row[field] = redact(row[field]);
71728
+ }
71729
+ }
71730
+ if (row.metadata && typeof row.metadata === "object") {
71731
+ row.metadata = redactJsonStrings(row.metadata);
71732
+ }
71733
+ return row;
71734
+ });
71735
+ return { ...src, items };
71736
+ }
71737
+
71738
+ // src/agents/handoff-summarizer.ts
71739
+ init_observation_scopes();
71108
71740
  var HANDOFF_STATUS_MIRROR_SKIPPED = "mirror-skipped-invalid-scope";
71109
71741
  var DEFAULT_MAX_TURNS = 50;
71110
71742
  var TOPIC_MAX_CHARS = 117;
@@ -71291,7 +71923,7 @@ async function mirrorToHindsight(briefing, opts) {
71291
71923
  `);
71292
71924
  return false;
71293
71925
  }
71294
- const body = {
71926
+ const body = redactMemoryWriteBody({
71295
71927
  items: [
71296
71928
  {
71297
71929
  content: briefing,
@@ -71301,7 +71933,7 @@ async function mirrorToHindsight(briefing, opts) {
71301
71933
  }
71302
71934
  ],
71303
71935
  async: true
71304
- };
71936
+ });
71305
71937
  try {
71306
71938
  await fetchFn(endpoint, {
71307
71939
  method: "POST",
@@ -74307,8 +74939,8 @@ Bootstrapping agent: ${name}
74307
74939
  const code = await new Promise((resolve24) => {
74308
74940
  process.stdin.setEncoding("utf-8");
74309
74941
  let buf = "";
74310
- process.stdin.on("data", (chunk) => {
74311
- buf += chunk.toString();
74942
+ process.stdin.on("data", (chunk2) => {
74943
+ buf += chunk2.toString();
74312
74944
  const newlineIdx = buf.indexOf(`
74313
74945
  `);
74314
74946
  if (newlineIdx !== -1) {
@@ -74390,8 +75022,8 @@ switchroom agent add: ${name}
74390
75022
  return new Promise((resolveLine) => {
74391
75023
  process.stdin.setEncoding("utf-8");
74392
75024
  let buf = "";
74393
- const onData = (chunk) => {
74394
- buf += chunk.toString();
75025
+ const onData = (chunk2) => {
75026
+ buf += chunk2.toString();
74395
75027
  const newlineIdx = buf.indexOf(`
74396
75028
  `);
74397
75029
  if (newlineIdx !== -1) {
@@ -76422,8 +77054,8 @@ async function readHiddenLine2(prompt) {
76422
77054
  stdin.setRawMode(true);
76423
77055
  }
76424
77056
  let line = "";
76425
- const onData = (chunk) => {
76426
- const s = chunk.toString("utf8");
77057
+ const onData = (chunk2) => {
77058
+ const s = chunk2.toString("utf8");
76427
77059
  for (const ch of s) {
76428
77060
  if (ch === `
76429
77061
  ` || ch === "\r") {
@@ -77352,7 +77984,7 @@ function getVaultPath2(configPath) {
77352
77984
  return resolvePath("~/.switchroom/vault.enc");
77353
77985
  }
77354
77986
  }
77355
- function maskToken(s) {
77987
+ function maskToken2(s) {
77356
77988
  if (s.length >= 18)
77357
77989
  return `${s.slice(0, 6)}...${s.slice(-4)}`;
77358
77990
  return "***";
@@ -77640,7 +78272,7 @@ function registerVaultSweep(vault, program3) {
77640
78272
  console.log("");
77641
78273
  console.log(source_default.dim(`masked values for display only:`));
77642
78274
  for (const v of values) {
77643
- console.log(` vault:${v.key} \u2192 ${maskToken(v.value)}`);
78275
+ console.log(` vault:${v.key} \u2192 ${maskToken2(v.value)}`);
77644
78276
  }
77645
78277
  if (opts.dryRun) {
77646
78278
  console.log(source_default.yellow("dry-run \u2014 no files modified. Rerun without --dry-run to apply."));
@@ -80613,8 +81245,8 @@ class VaultBroker {
80613
81245
  peer = this.testOpts._testIdentify ? this.testOpts._testIdentify(listenerSocketPath, socket) : identify(listenerSocketPath, socket);
80614
81246
  }
80615
81247
  let buffer = "";
80616
- socket.on("data", (chunk) => {
80617
- buffer += chunk.toString("utf8");
81248
+ socket.on("data", (chunk2) => {
81249
+ buffer += chunk2.toString("utf8");
80618
81250
  if (Buffer.byteLength(buffer, "utf8") > MAX_FRAME_BYTES) {
80619
81251
  const resp = encodeResponse(errorResponse("BAD_REQUEST", "Frame exceeds 64 KiB limit"));
80620
81252
  socket.end(resp);
@@ -81723,8 +82355,8 @@ class VaultBroker {
81723
82355
  const auditCaller = isOperator ? "operator" : unlockPeer !== null ? callerFromPeer(unlockPeer) : `pid:${process.pid}`;
81724
82356
  const auditCgroup = isOperator ? undefined : unlockPeer?.systemdUnit ?? undefined;
81725
82357
  let buffer = "";
81726
- socket.on("data", (chunk) => {
81727
- buffer += chunk.toString("utf8");
82358
+ socket.on("data", (chunk2) => {
82359
+ buffer += chunk2.toString("utf8");
81728
82360
  const newlineIdx = buffer.indexOf(`
81729
82361
  `);
81730
82362
  if (newlineIdx === -1) {
@@ -83291,7 +83923,7 @@ function promptLine2(prompt, hidden = false) {
83291
83923
  function readStdinToEnd() {
83292
83924
  return new Promise((resolve30, reject) => {
83293
83925
  const chunks = [];
83294
- process.stdin.on("data", (chunk) => chunks.push(chunk));
83926
+ process.stdin.on("data", (chunk2) => chunks.push(chunk2));
83295
83927
  process.stdin.on("end", () => {
83296
83928
  resolve30(Buffer.concat(chunks).toString("utf8"));
83297
83929
  });
@@ -85443,9 +86075,9 @@ Repairing vector index coverage for ${target}${opts.dryRun ? source_default.yell
85443
86075
  `));
85444
86076
  const child = spawn5("docker", argv, { stdio: ["ignore", "pipe", "inherit"] });
85445
86077
  let captured = "";
85446
- child.stdout?.on("data", (chunk) => {
85447
- captured += chunk.toString();
85448
- process.stdout.write(chunk);
86078
+ child.stdout?.on("data", (chunk2) => {
86079
+ captured += chunk2.toString();
86080
+ process.stdout.write(chunk2);
85449
86081
  });
85450
86082
  const childExit = await new Promise((resolve30) => {
85451
86083
  child.on("error", () => resolve30(127));
@@ -85546,14 +86178,21 @@ Demoting memory ${source_default.cyan(memoryId)}`));
85546
86178
  const timeoutMs = Math.max(1000, parseInt(opts.timeout, 10) || 60000);
85547
86179
  const ctrl = new AbortController;
85548
86180
  const t = setTimeout(() => ctrl.abort(), timeoutMs);
86181
+ const payload = redactMemoryWriteBody({
86182
+ items: [{ content, tags: ["operator-authored", "profile"] }],
86183
+ async: false
86184
+ });
86185
+ const stored = payload.items[0].content;
86186
+ if (stored !== content) {
86187
+ console.error(source_default.yellow(`\u26a0 Credential-shaped text in this fact was replaced with a
86188
+ ` + ` [REDACTED] marker before it was stored. Put secrets in the
86189
+ ` + " vault (`switchroom vault set <key>`) and reference them as\n" + " `vault:<key>` instead."));
86190
+ }
85549
86191
  try {
85550
86192
  const res = await fetch(url, {
85551
86193
  method: "POST",
85552
86194
  headers: { "Content-Type": "application/json" },
85553
- body: JSON.stringify({
85554
- items: [{ content, tags: ["operator-authored", "profile"] }],
85555
- async: false
85556
- }),
86195
+ body: JSON.stringify(payload),
85557
86196
  signal: ctrl.signal
85558
86197
  });
85559
86198
  if (!res.ok) {
@@ -85561,7 +86200,7 @@ Demoting memory ${source_default.cyan(memoryId)}`));
85561
86200
  process.exit(1);
85562
86201
  }
85563
86202
  console.log(source_default.green(`\u2713 Added to profile bank "${bank}"`));
85564
- console.log(source_default.gray(` ${content}`));
86203
+ console.log(source_default.gray(` ${stored}`));
85565
86204
  console.log(source_default.gray(" (fact extraction runs in the background \u2014 `profile list` may lag a few seconds)"));
85566
86205
  console.log(source_default.gray(`
85567
86206
  Wire it into agents via memory.recall.additional_banks: ["${bank}"] in switchroom.yaml,
@@ -87854,8 +88493,8 @@ function forwardToGateway(socketPath, req, opts = {}) {
87854
88493
  conn.write(JSON.stringify(req) + `
87855
88494
  `);
87856
88495
  });
87857
- conn.on("data", (chunk) => {
87858
- buf += chunk;
88496
+ conn.on("data", (chunk2) => {
88497
+ buf += chunk2;
87859
88498
  const nl = buf.indexOf(`
87860
88499
  `);
87861
88500
  if (nl === -1)
@@ -94034,410 +94673,6 @@ function computeFingerprint(source, code) {
94034
94673
  return `${source}::${code}`;
94035
94674
  }
94036
94675
 
94037
- // telegram-plugin/secret-detect/patterns.ts
94038
- var ANCHORED_PATTERNS = [
94039
- { rule_id: "anthropic_api_key", regex: /\b(sk-ant-[A-Za-z0-9_-]{8,})\b/g, captureIndex: 1, slugHint: "anthropic_api_key" },
94040
- { rule_id: "anthropic_oauth_code", regex: /(?:^|\s)([A-Za-z0-9_-]{20,}#[A-Za-z0-9_-]{20,})(?=\s|$)/gm, captureIndex: 1, slugHint: "anthropic_oauth_code" },
94041
- { rule_id: "openai_api_key", regex: /\b(sk-[A-Za-z0-9_-]{20,})\b/g, captureIndex: 1, slugHint: "openai_api_key" },
94042
- { rule_id: "github_pat_classic", regex: /\b(ghp_[A-Za-z0-9]{20,})\b/g, captureIndex: 1, slugHint: "github_pat" },
94043
- { rule_id: "github_pat_fine_grained", regex: /\b(github_pat_[A-Za-z0-9_]{20,})\b/g, captureIndex: 1, slugHint: "github_pat" },
94044
- { rule_id: "slack_token", regex: /\b(xox[baprs]-[A-Za-z0-9-]{10,})\b/g, captureIndex: 1, slugHint: "slack_token" },
94045
- { rule_id: "slack_app_token", regex: /\b(xapp-[A-Za-z0-9-]{10,})\b/g, captureIndex: 1, slugHint: "slack_app_token" },
94046
- { rule_id: "groq_api_key", regex: /\b(gsk_[A-Za-z0-9_-]{10,})\b/g, captureIndex: 1, slugHint: "groq_api_key" },
94047
- { rule_id: "google_api_key", regex: /\b(AIza[0-9A-Za-z\-_]{20,})\b/g, captureIndex: 1, slugHint: "google_api_key" },
94048
- { rule_id: "perplexity_api_key", regex: /\b(pplx-[A-Za-z0-9_-]{10,})\b/g, captureIndex: 1, slugHint: "perplexity_api_key" },
94049
- { rule_id: "npm_token", regex: /\b(npm_[A-Za-z0-9]{10,})\b/g, captureIndex: 1, slugHint: "npm_token" },
94050
- { rule_id: "telegram_bot_token_prefixed", regex: /\bbot(\d{6,}:[A-Za-z0-9_-]{20,})\b/g, captureIndex: 1, slugHint: "telegram_bot_token" },
94051
- { rule_id: "telegram_bot_token", regex: /\b(\d{6,}:[A-Za-z0-9_-]{20,})\b/g, captureIndex: 1, slugHint: "telegram_bot_token" },
94052
- { rule_id: "laravel_sanctum_token", regex: /\b(\d+\|[A-Za-z0-9]{40,})\b/g, captureIndex: 1, slugHint: "api_token" },
94053
- { rule_id: "aws_access_key", regex: /\b(AKIA[0-9A-Z]{16})\b/g, captureIndex: 1, slugHint: "aws_access_key" },
94054
- { rule_id: "jwt", regex: /\b(eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,})\b/g, captureIndex: 1, slugHint: "jwt" }
94055
- ];
94056
- var STRUCTURED_PATTERNS = [
94057
- {
94058
- rule_id: "env_key_value",
94059
- regex: /\b([A-Z0-9_]*(?:KEY|TOKEN|SECRET|PASSWORD|PASSWD))\b\s*[=:]\s*(["']?)([^\s"'\\]+)\2/g,
94060
- captureIndex: 3,
94061
- slugHint: "env"
94062
- },
94063
- {
94064
- rule_id: "json_secret_field",
94065
- regex: /"(?:apiKey|token|secret|password|passwd|accessToken|refreshToken)"\s*:\s*"([^"]+)"/g,
94066
- captureIndex: 1,
94067
- slugHint: "json_secret"
94068
- },
94069
- {
94070
- rule_id: "cli_flag",
94071
- regex: /--(?:api[-_]?key|hook[-_]?token|token|secret|password|passwd)\s+(["']?)([^\s"']+)\1/g,
94072
- captureIndex: 2,
94073
- slugHint: "cli_flag"
94074
- },
94075
- {
94076
- rule_id: "bearer_auth_header",
94077
- regex: /Authorization\s*[:=]\s*Bearer\s+([A-Za-z0-9._\-+=]+)/g,
94078
- captureIndex: 1,
94079
- slugHint: "bearer_token"
94080
- },
94081
- {
94082
- rule_id: "bearer_loose",
94083
- regex: /\bBearer\s+([A-Za-z0-9._\-+=]{18,})\b/g,
94084
- captureIndex: 1,
94085
- slugHint: "bearer_token"
94086
- },
94087
- {
94088
- rule_id: "pem_private_key",
94089
- regex: /-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]+?-----END [A-Z ]*PRIVATE KEY-----/g,
94090
- captureIndex: 0,
94091
- slugHint: "pem_private_key"
94092
- }
94093
- ];
94094
- var PROVIDER_PATTERNS = [
94095
- { rule_id: "slack_webhook", regex: /(https:\/\/hooks\.slack\.com\/services\/[A-Za-z0-9_/]+)/g, captureIndex: 1, slugHint: "slack_webhook" },
94096
- { rule_id: "stripe_live_secret", regex: /\b(sk_live_[A-Za-z0-9]{24,})\b/g, captureIndex: 1, slugHint: "stripe_key" },
94097
- { rule_id: "stripe_restricted", regex: /\b(rk_live_[A-Za-z0-9]{24,})\b/g, captureIndex: 1, slugHint: "stripe_key" },
94098
- { rule_id: "sendgrid_api_key", regex: /\b(SG\.[A-Za-z0-9_-]{22}\.[A-Za-z0-9_-]{43})\b/g, captureIndex: 1, slugHint: "sendgrid_key" },
94099
- { rule_id: "gitlab_pat", regex: /\b(glpat-[A-Za-z0-9_-]{20})\b/g, captureIndex: 1, slugHint: "gitlab_pat" },
94100
- { rule_id: "huggingface_token", regex: /\b(hf_[A-Za-z0-9]{34,})\b/g, captureIndex: 1, slugHint: "huggingface_token" },
94101
- { rule_id: "twilio_api_key", regex: /\b(SK[0-9a-f]{32})\b/g, captureIndex: 1, slugHint: "twilio_api_key" },
94102
- { rule_id: "mailgun_key", regex: /\b(key-[0-9a-f]{32})\b/g, captureIndex: 1, slugHint: "mailgun_key" },
94103
- { rule_id: "digitalocean_pat", regex: /\b(dop_v1_[a-f0-9]{64})\b/g, captureIndex: 1, slugHint: "digitalocean_token" },
94104
- { rule_id: "digitalocean_oauth", regex: /\b(doo_v1_[a-f0-9]{64})\b/g, captureIndex: 1, slugHint: "digitalocean_token" },
94105
- { rule_id: "digitalocean_refresh", regex: /\b(dor_v1_[a-f0-9]{64})\b/g, captureIndex: 1, slugHint: "digitalocean_token" },
94106
- { rule_id: "doppler_token", regex: /\b(dp\.(?:pt|st|ct|sa|scim|audit)\.[A-Za-z0-9]{40,44})\b/g, captureIndex: 1, slugHint: "doppler_token" },
94107
- { rule_id: "linear_api_key", regex: /\b(lin_api_[A-Za-z0-9]{40})\b/g, captureIndex: 1, slugHint: "linear_api_key" },
94108
- { rule_id: "shopify_access_token", regex: /\b(shpat_[a-fA-F0-9]{32})\b/g, captureIndex: 1, slugHint: "shopify_token" },
94109
- { rule_id: "shopify_shared_secret", regex: /\b(shpss_[a-fA-F0-9]{32})\b/g, captureIndex: 1, slugHint: "shopify_token" },
94110
- { rule_id: "shopify_private_app", regex: /\b(shppa_[a-fA-F0-9]{32})\b/g, captureIndex: 1, slugHint: "shopify_token" },
94111
- { rule_id: "square_access_token", regex: /\b(sq0atp-[A-Za-z0-9_-]{22})\b/g, captureIndex: 1, slugHint: "square_token" },
94112
- { rule_id: "square_oauth_secret", regex: /\b(sq0csp-[A-Za-z0-9_-]{43})\b/g, captureIndex: 1, slugHint: "square_token" },
94113
- { rule_id: "newrelic_key", regex: /\b(NRAK-[A-Z0-9]{27})\b/g, captureIndex: 1, slugHint: "newrelic_key" },
94114
- { rule_id: "notion_token", regex: /\b(ntn_[A-Za-z0-9]{46})\b/g, captureIndex: 1, slugHint: "notion_token" },
94115
- { rule_id: "planetscale_password", regex: /\b(pscale_pw_[A-Za-z0-9_.-]{43})\b/g, captureIndex: 1, slugHint: "planetscale_token" },
94116
- { rule_id: "planetscale_token", regex: /\b(pscale_tkn_[A-Za-z0-9_.-]{43})\b/g, captureIndex: 1, slugHint: "planetscale_token" },
94117
- { rule_id: "supabase_service_key", regex: /\b(sbp_[a-f0-9]{40})\b/g, captureIndex: 1, slugHint: "supabase_key" },
94118
- { rule_id: "atlassian_token", regex: /\b(ATATT[A-Za-z0-9_\-=]{20,})\b/g, captureIndex: 1, slugHint: "atlassian_token" },
94119
- { rule_id: "dropbox_token", regex: /\b(sl\.[A-Za-z0-9_-]{130,})/g, captureIndex: 1, slugHint: "dropbox_token" },
94120
- { rule_id: "databricks_token", regex: /\b(dapi[a-f0-9]{32})\b/g, captureIndex: 1, slugHint: "databricks_token" },
94121
- { rule_id: "grafana_service_account", regex: /\b(glsa_[A-Za-z0-9]{32}_[a-fA-F0-9]{8})\b/g, captureIndex: 1, slugHint: "grafana_token" },
94122
- { rule_id: "pypi_token", regex: /\b(pypi-AgEIcHlwaS[A-Za-z0-9_-]{50,})/g, captureIndex: 1, slugHint: "pypi_token" },
94123
- { rule_id: "aws_temp_access_key", regex: /\b(ASIA[0-9A-Z]{16})\b/g, captureIndex: 1, slugHint: "aws_access_key" },
94124
- { rule_id: "gcp_oauth_token", regex: /\b(ya29\.[A-Za-z0-9_-]{30,})/g, captureIndex: 1, slugHint: "gcp_oauth_token" }
94125
- ];
94126
- var ALL_PATTERNS = [...ANCHORED_PATTERNS, ...PROVIDER_PATTERNS, ...STRUCTURED_PATTERNS];
94127
-
94128
- // telegram-plugin/secret-detect/entropy.ts
94129
- function shannonEntropy(s) {
94130
- if (s.length === 0)
94131
- return 0;
94132
- const counts = new Map;
94133
- for (const ch of s) {
94134
- counts.set(ch, (counts.get(ch) ?? 0) + 1);
94135
- }
94136
- let h = 0;
94137
- const len = s.length;
94138
- for (const c of counts.values()) {
94139
- const p = c / len;
94140
- h -= p * Math.log2(p);
94141
- }
94142
- return h;
94143
- }
94144
-
94145
- // telegram-plugin/secret-detect/kv-scanner.ts
94146
- var KV_RE = /\b([A-Za-z_][A-Za-z0-9_-]*(?:password|passwd|token|secret|key|api[_-]?key))\s*[:=]\s*["']?([^\s"'\\]{8,})["']?/gi;
94147
- var KV_ENTROPY_THRESHOLD = 4;
94148
- function scanKeyValue(text) {
94149
- const hits = [];
94150
- KV_RE.lastIndex = 0;
94151
- let m;
94152
- while ((m = KV_RE.exec(text)) !== null) {
94153
- const [, keyName, value] = m;
94154
- if (!value)
94155
- continue;
94156
- const h = shannonEntropy(value);
94157
- if (h < KV_ENTROPY_THRESHOLD)
94158
- continue;
94159
- const valueOffsetInMatch = m[0].indexOf(value, keyName.length);
94160
- if (valueOffsetInMatch < 0)
94161
- continue;
94162
- const start = m.index + valueOffsetInMatch;
94163
- const end = start + value.length;
94164
- hits.push({
94165
- rule_id: "kv_entropy",
94166
- start,
94167
- end,
94168
- matched_text: value,
94169
- key_name: keyName,
94170
- confidence: "ambiguous"
94171
- });
94172
- }
94173
- return hits;
94174
- }
94175
-
94176
- // telegram-plugin/secret-detect/generic-entropy.ts
94177
- var CANDIDATE_RE = /[A-Za-z0-9]{28,}/g;
94178
- var GENERIC_MIN_DISTINCT = 18;
94179
- var MAX_GENERIC_HITS = 20;
94180
- function hasDistinctChars(tok, n) {
94181
- const seen = new Uint8Array(128);
94182
- let distinct = 0;
94183
- for (let i = 0;i < tok.length; i++) {
94184
- const c = tok.charCodeAt(i);
94185
- if (seen[c] === 0) {
94186
- seen[c] = 1;
94187
- if (++distinct >= n)
94188
- return true;
94189
- }
94190
- }
94191
- return false;
94192
- }
94193
- function hasDigit(tok) {
94194
- for (let i = 0;i < tok.length; i++) {
94195
- const c = tok.charCodeAt(i);
94196
- if (c >= 48 && c <= 57)
94197
- return true;
94198
- }
94199
- return false;
94200
- }
94201
- function scanGenericSecrets(text) {
94202
- const hits = [];
94203
- CANDIDATE_RE.lastIndex = 0;
94204
- let m;
94205
- while ((m = CANDIDATE_RE.exec(text)) !== null) {
94206
- if (hits.length >= MAX_GENERIC_HITS)
94207
- break;
94208
- const tok = m[0];
94209
- if (!hasDigit(tok))
94210
- continue;
94211
- if (!hasDistinctChars(tok, GENERIC_MIN_DISTINCT))
94212
- continue;
94213
- hits.push({
94214
- rule_id: "generic_high_entropy",
94215
- start: m.index,
94216
- end: m.index + tok.length,
94217
- matched_text: tok,
94218
- confidence: "ambiguous"
94219
- });
94220
- }
94221
- return hits;
94222
- }
94223
-
94224
- // telegram-plugin/secret-detect/chunker.ts
94225
- var CHUNK_THRESHOLD = 32 * 1024;
94226
- var WINDOW_SIZE = 16 * 1024;
94227
- var OVERLAP = 8 * 1024;
94228
- function chunk(text) {
94229
- if (text.length <= CHUNK_THRESHOLD) {
94230
- return [{ offset: 0, text }];
94231
- }
94232
- const out = [];
94233
- let offset = 0;
94234
- while (offset < text.length) {
94235
- const end = Math.min(offset + WINDOW_SIZE, text.length);
94236
- out.push({ offset, text: text.slice(offset, end) });
94237
- if (end >= text.length)
94238
- break;
94239
- offset = end - OVERLAP;
94240
- }
94241
- return out;
94242
- }
94243
-
94244
- // telegram-plugin/secret-detect/suppressor.ts
94245
- var MARKERS = ["test", "mock", "example", "fixture", "dummy"];
94246
- var WINDOW = 40;
94247
- var MARKER_RE = new RegExp(`\\b(?:${MARKERS.join("|")})\\b`, "i");
94248
- function isSuppressed(text, start, end) {
94249
- const left = Math.max(0, start - WINDOW);
94250
- const right = Math.min(text.length, end + WINDOW);
94251
- const context = text.slice(left, start) + text.slice(end, right);
94252
- return MARKER_RE.test(context);
94253
- }
94254
-
94255
- // telegram-plugin/secret-detect/slug.ts
94256
- function sanitizeKeyName(raw) {
94257
- const up = raw.toUpperCase();
94258
- const cleaned = up.replace(/[^A-Z0-9_]+/g, "_").replace(/_+/g, "_").replace(/^_+|_+$/g, "");
94259
- return cleaned.length > 0 ? cleaned : "SECRET";
94260
- }
94261
- function datePart(now = new Date) {
94262
- const y = now.getUTCFullYear();
94263
- const m = String(now.getUTCMonth() + 1).padStart(2, "0");
94264
- const d = String(now.getUTCDate()).padStart(2, "0");
94265
- return `${y}${m}${d}`;
94266
- }
94267
- function deriveSlug(inputs, existing) {
94268
- let base;
94269
- if (inputs.key_name && inputs.key_name.trim().length > 0) {
94270
- base = sanitizeKeyName(inputs.key_name);
94271
- } else {
94272
- base = `${inputs.rule_id}_${datePart(inputs.now)}`;
94273
- }
94274
- if (!existing.has(base))
94275
- return base;
94276
- let n = 2;
94277
- while (existing.has(`${base}_${n}`))
94278
- n++;
94279
- return `${base}_${n}`;
94280
- }
94281
- // telegram-plugin/secret-detect/url-redact.ts
94282
- var SENSITIVE_PARAMS = new Set([
94283
- "token",
94284
- "key",
94285
- "api_key",
94286
- "apikey",
94287
- "secret",
94288
- "access_token",
94289
- "password",
94290
- "pass",
94291
- "auth",
94292
- "client_secret",
94293
- "refresh_token",
94294
- "signature"
94295
- ]);
94296
- var URL_RE = /\b(?:https?|wss?|ftp):\/\/[^\s<>"']+/gi;
94297
- function redactUrls(text) {
94298
- return text.replace(URL_RE, (m) => {
94299
- const redacted = redactOne(m);
94300
- return redacted ?? m;
94301
- });
94302
- }
94303
- function redactOne(raw) {
94304
- let u;
94305
- try {
94306
- u = new URL(raw);
94307
- } catch {
94308
- return null;
94309
- }
94310
- let changed = false;
94311
- if (u.username || u.password) {
94312
- u.username = "***";
94313
- u.password = "";
94314
- changed = true;
94315
- }
94316
- for (const [key] of u.searchParams) {
94317
- if (SENSITIVE_PARAMS.has(key.toLowerCase())) {
94318
- u.searchParams.set(key, "***");
94319
- changed = true;
94320
- }
94321
- }
94322
- return changed ? u.toString() : null;
94323
- }
94324
-
94325
- // telegram-plugin/secret-detect/index.ts
94326
- function detectSecrets(text) {
94327
- if (!text || text.length === 0)
94328
- return [];
94329
- const windows = chunk(text);
94330
- const raw = [];
94331
- for (const win of windows) {
94332
- for (const p of ALL_PATTERNS) {
94333
- const re = new RegExp(p.regex.source, p.regex.flags.includes("g") ? p.regex.flags : p.regex.flags + "g");
94334
- let m;
94335
- while ((m = re.exec(win.text)) !== null) {
94336
- if (m[0].length === 0) {
94337
- re.lastIndex++;
94338
- continue;
94339
- }
94340
- const cap = p.captureIndex === 0 ? m[0] : m[p.captureIndex];
94341
- if (!cap)
94342
- continue;
94343
- const matchStart = p.captureIndex === 0 ? m.index : m.index + m[0].indexOf(cap);
94344
- if (matchStart < 0)
94345
- continue;
94346
- const globalStart = win.offset + matchStart;
94347
- const globalEnd = globalStart + cap.length;
94348
- const keyName = p.rule_id === "env_key_value" ? m[1] : undefined;
94349
- if (p.rule_id === "env_key_value") {
94350
- const ENV_KV_MIN_LEN = 12;
94351
- const ENV_KV_MIN_ENTROPY = 3.5;
94352
- if (cap.length < ENV_KV_MIN_LEN)
94353
- continue;
94354
- if (shannonEntropy(cap) < ENV_KV_MIN_ENTROPY)
94355
- continue;
94356
- }
94357
- raw.push({
94358
- rule_id: p.rule_id,
94359
- start: globalStart,
94360
- end: globalEnd,
94361
- matched_text: cap,
94362
- key_name: keyName,
94363
- confidence: "high"
94364
- });
94365
- }
94366
- }
94367
- const kvHits = scanKeyValue(win.text);
94368
- for (const h of kvHits) {
94369
- raw.push({ ...h, start: h.start + win.offset, end: h.end + win.offset });
94370
- }
94371
- const genHits = scanGenericSecrets(win.text);
94372
- for (const h of genHits) {
94373
- raw.push({ ...h, start: h.start + win.offset, end: h.end + win.offset });
94374
- }
94375
- }
94376
- const deduped = dedupeRaw(raw);
94377
- const final = dropOverlaps(deduped);
94378
- const existing = new Set;
94379
- const out = [];
94380
- for (const h of final) {
94381
- const suggested_slug = deriveSlug({ key_name: h.key_name, rule_id: h.rule_id }, existing);
94382
- existing.add(suggested_slug);
94383
- out.push({
94384
- rule_id: h.rule_id,
94385
- matched_text: h.matched_text,
94386
- start: h.start,
94387
- end: h.end,
94388
- confidence: h.confidence,
94389
- suppressed: isSuppressed(text, h.start, h.end),
94390
- suggested_slug,
94391
- key_name: h.key_name
94392
- });
94393
- }
94394
- out.sort((a, b) => a.start - b.start);
94395
- return out;
94396
- }
94397
- function dedupeRaw(raw) {
94398
- const seen = new Map;
94399
- for (const h of raw) {
94400
- const key = `${h.start}:${h.end}`;
94401
- const existing = seen.get(key);
94402
- if (!existing) {
94403
- seen.set(key, h);
94404
- continue;
94405
- }
94406
- if (existing.confidence === "ambiguous" && h.confidence === "high") {
94407
- seen.set(key, h);
94408
- }
94409
- }
94410
- return Array.from(seen.values());
94411
- }
94412
- function dropOverlaps(hits) {
94413
- const out = hits.filter((h) => !(h.confidence === "ambiguous" && hits.some((o) => o !== h && o.start <= h.start && o.end >= h.end && !(o.start === h.start && o.end === h.end))));
94414
- out.sort((a, b) => a.start - b.start || a.end - b.end);
94415
- return out;
94416
- }
94417
-
94418
- // telegram-plugin/secret-detect/redact.ts
94419
- var REDACTED_MARKER = "[REDACTED]";
94420
- function redact(text) {
94421
- if (!text || text.length === 0)
94422
- return text;
94423
- const urlScrubbed = redactUrls(text);
94424
- const hits = detectSecrets(urlScrubbed).filter((h) => h.rule_id !== "generic_high_entropy");
94425
- if (hits.length === 0)
94426
- return urlScrubbed;
94427
- const sorted = [...hits].sort((a, b) => b.start - a.start);
94428
- let out = urlScrubbed;
94429
- for (const h of sorted) {
94430
- out = out.slice(0, h.start) + redactedMarker(h.rule_id) + out.slice(h.end);
94431
- }
94432
- return out;
94433
- }
94434
- function redactedMarker(ruleId) {
94435
- const trimmed = ruleId.replace(/^(kv|env)_/, "");
94436
- if (!trimmed || trimmed === "key_value" || trimmed === "kv_entropy") {
94437
- return REDACTED_MARKER;
94438
- }
94439
- return `[REDACTED:${trimmed}]`;
94440
- }
94441
94676
  // src/issues/store.ts
94442
94677
  var ISSUES_FILE = "issues.jsonl";
94443
94678
  var ISSUES_LOCK = "issues.lock";
@@ -98387,6 +98622,42 @@ function withSynthesizedTools(result) {
98387
98622
  ]
98388
98623
  };
98389
98624
  }
98625
+ var READ_ONLY_TOOL_NAMES = new Set([
98626
+ "recall",
98627
+ "reflect",
98628
+ "search",
98629
+ "list_memories",
98630
+ "list_directives",
98631
+ "list_mental_models",
98632
+ "get_mental_model",
98633
+ "get_memory",
98634
+ "get_bank"
98635
+ ]);
98636
+ function redactToolArguments(value) {
98637
+ if (typeof value === "string")
98638
+ return redact(value);
98639
+ if (Array.isArray(value))
98640
+ return value.map(redactToolArguments);
98641
+ if (value && typeof value === "object") {
98642
+ const out = {};
98643
+ for (const [k, v] of Object.entries(value)) {
98644
+ out[k] = redactToolArguments(v);
98645
+ }
98646
+ return out;
98647
+ }
98648
+ return value;
98649
+ }
98650
+ function redactToolCallParams(params) {
98651
+ const called = params;
98652
+ if (!called || typeof called !== "object")
98653
+ return params;
98654
+ if (called.arguments === undefined || called.arguments === null)
98655
+ return params;
98656
+ if (typeof called.name === "string" && READ_ONLY_TOOL_NAMES.has(called.name)) {
98657
+ return params;
98658
+ }
98659
+ return { ...called, arguments: redactToolArguments(called.arguments) };
98660
+ }
98390
98661
  function buildFallbackToolsList() {
98391
98662
  const tools = Object.entries(FALLBACK_TOOL_TABLE).map(([name, [required, props]]) => ({
98392
98663
  name,
@@ -98713,8 +98984,9 @@ class HindsightShim {
98713
98984
  if (called?.name && SYNTHESIZED_TOOL_NAMES.includes(called.name)) {
98714
98985
  return this.synthesizedCall(called.name, called.arguments);
98715
98986
  }
98987
+ const forwarded = redactToolCallParams(params);
98716
98988
  try {
98717
- const res = await this.upstream.request("tools/call", params, this.opts.toolsCallTimeoutMs ?? TOOLS_CALL_TIMEOUT_MS);
98989
+ const res = await this.upstream.request("tools/call", forwarded, this.opts.toolsCallTimeoutMs ?? TOOLS_CALL_TIMEOUT_MS);
98718
98990
  this.notifyIfRecovered();
98719
98991
  if (res.error) {
98720
98992
  return {
@@ -104653,7 +104925,7 @@ function probeSpool(agentsDir = resolveAgentsDir2()) {
104653
104925
  }
104654
104926
  return { pending, dead, evicted, drops, agents };
104655
104927
  }
104656
- var CONSOLIDATION_QUEUE_SH = `
104928
+ var PSQL_BOOTSTRAP_SH = `
104657
104929
  set -e
104658
104930
  B="$(ls -d /home/hindsight/.pg0/installation/*/bin 2>/dev/null | head -1)"
104659
104931
  PSQL="$(command -v psql 2>/dev/null || echo "\${B:-/nonexistent}/psql")"
@@ -104670,9 +104942,63 @@ d=json.load(open(sys.argv[1]))
104670
104942
  q=lambda k,dflt: shlex.quote(str(d.get(k) or dflt))
104671
104943
  print("U=%s DB=%s P=%s PW=%s"%(q("username","hindsight"),q("database","hindsight"),q("port",5432),q("password","")))' "$D")"
104672
104944
  [ -n "$PW" ] || exit 0
104945
+ `;
104946
+ var CONSOLIDATION_QUEUE_SH = `${PSQL_BOOTSTRAP_SH}
104673
104947
  PGPASSWORD="$PW" "$PSQL" -U "$U" -h /tmp -p "$P" -d "$DB" -tAc \\
104674
104948
  "SELECT count(*)||'|'||coalesce(max(extract(epoch from (now()-created_at)))::bigint,0) FROM async_operations WHERE status IN ('pending','processing')"
104675
104949
  `;
104950
+ var BANK_CONSOLIDATION_SH = `${PSQL_BOOTSTRAP_SH}
104951
+ PGPASSWORD="$PW" "$PSQL" -U "$U" -h /tmp -p "$P" -d "$DB" -tAc \\
104952
+ "SELECT 'S|'||a.bank_id||'|'||a.operation_type||'|'||count(*)||'|'||extract(epoch from (now()-max(a.created_at)))::bigint
104953
+ FROM async_operations a
104954
+ WHERE a.status = 'failed'
104955
+ AND a.created_at > coalesce((SELECT max(b.created_at) FROM async_operations b
104956
+ WHERE b.bank_id = a.bank_id
104957
+ AND b.operation_type = a.operation_type
104958
+ AND b.status = 'completed'), '-infinity'::timestamptz)
104959
+ GROUP BY a.bank_id, a.operation_type
104960
+ UNION ALL
104961
+ SELECT 'P|'||m.bank_id||'|'||count(*) FILTER (WHERE m.consolidated_at IS NULL AND m.fact_type IN ('experience','world'))
104962
+ FROM memory_units m
104963
+ GROUP BY m.bank_id"
104964
+ `;
104965
+ function vectorIndexSh(sampleRows) {
104966
+ const limit = Math.max(1, Math.floor(Number.isFinite(sampleRows) ? sampleRows : 1));
104967
+ return `${PSQL_BOOTSTRAP_SH}
104968
+ PGPASSWORD="$PW" PGOPTIONS='-c client_min_messages=notice' \\
104969
+ "$PSQL" -U "$U" -h /tmp -p "$P" -d "$DB" -tAc "
104970
+ DO \\$\\$
104971
+ DECLARE r record; bad text := ''; n int := 0; probed int := 0;
104972
+ BEGIN
104973
+ FOR r IN
104974
+ SELECT c.relname AS idx,
104975
+ i.indrelid::regclass::text AS tbl,
104976
+ coalesce(pg_get_expr(i.indpred, i.indrelid), 'true') AS pred
104977
+ FROM pg_index i
104978
+ JOIN pg_class c ON c.oid = i.indexrelid
104979
+ JOIN pg_am am ON am.oid = c.relam
104980
+ WHERE am.amname = 'hnsw'
104981
+ ORDER BY c.relname
104982
+ LOOP
104983
+ BEGIN
104984
+ EXECUTE format(
104985
+ 'SELECT count(nn) FROM (SELECT (SELECT m2.ctid FROM %1\\$s m2 WHERE %2\\$s ORDER BY m2.embedding <=> s.embedding LIMIT 1) AS nn FROM (SELECT embedding FROM %1\\$s s0 WHERE %2\\$s AND embedding IS NOT NULL LIMIT %3\\$s) s) t',
104986
+ r.tbl, r.pred, ${limit});
104987
+ probed := probed + 1;
104988
+ EXCEPTION WHEN others THEN
104989
+ n := n + 1;
104990
+ -- Both delimiters are stripped from the message BEFORE it is appended.
104991
+ -- ';' separates entries and a newline would split the NOTICE line, so an
104992
+ -- SQLERRM containing either would desynchronise the parsed list from n,
104993
+ -- and probeVectorIndexes rejects a mismatched block as unreadable \u2014
104994
+ -- turning a real corruption into a silent no-data.
104995
+ bad := bad || r.idx || '=' || translate(SQLERRM, ';' || chr(10) || chr(13), ', ') || ';';
104996
+ END;
104997
+ END LOOP;
104998
+ RAISE NOTICE 'VECIDX|%|%|%', probed, n, bad;
104999
+ END \\$\\$;" 2>&1
105000
+ `;
105001
+ }
104676
105002
  function probeConsolidationQueue(container = DEFAULT_CONTAINER, run = defaultRunner4) {
104677
105003
  const r = run("docker", ["exec", container, "sh", "-c", CONSOLIDATION_QUEUE_SH]);
104678
105004
  if (r.status !== 0)
@@ -104686,6 +105012,61 @@ function probeConsolidationQueue(container = DEFAULT_CONTAINER, run = defaultRun
104686
105012
  return null;
104687
105013
  return { pending, oldestAgeS };
104688
105014
  }
105015
+ function probeBankConsolidation(container = DEFAULT_CONTAINER, run = defaultRunner4) {
105016
+ const r = run("docker", ["exec", container, "sh", "-c", BANK_CONSOLIDATION_SH]);
105017
+ if (r.status !== 0)
105018
+ return null;
105019
+ const streaks = [];
105020
+ const pending = [];
105021
+ let parsedAny = false;
105022
+ for (const line of r.stdout.split(`
105023
+ `)) {
105024
+ const t = line.trim();
105025
+ if (t === "")
105026
+ continue;
105027
+ const f = t.split("|");
105028
+ if (f[0] === "S" && f.length === 5) {
105029
+ const streak = Number(f[3]);
105030
+ const age = Number(f[4]);
105031
+ if (!Number.isFinite(streak) || !Number.isFinite(age) || streak < 0 || age < 0)
105032
+ continue;
105033
+ streaks.push({ bank: f[1], operationType: f[2], streak, newestFailureAgeS: age });
105034
+ parsedAny = true;
105035
+ } else if (f[0] === "P" && f.length === 3) {
105036
+ const n = Number(f[2]);
105037
+ if (!Number.isFinite(n) || n < 0)
105038
+ continue;
105039
+ pending.push({ bank: f[1], pending: n });
105040
+ parsedAny = true;
105041
+ }
105042
+ }
105043
+ if (!parsedAny)
105044
+ return null;
105045
+ return { streaks, pending };
105046
+ }
105047
+ function probeVectorIndexes(container = DEFAULT_CONTAINER, run = defaultRunner4, sampleRows = VECTOR_INDEX_SAMPLE_ROWS) {
105048
+ const sh = vectorIndexSh(sampleRows);
105049
+ if (!sh.includes("count(nn)"))
105050
+ return null;
105051
+ const r = run("docker", ["exec", container, "sh", "-c", sh]);
105052
+ if (r.status !== 0)
105053
+ return null;
105054
+ const line = r.stdout.split(`
105055
+ `).find((l) => l.includes("VECIDX|"));
105056
+ if (line === undefined)
105057
+ return null;
105058
+ const f = line.slice(line.indexOf("VECIDX|")).trim().split("|");
105059
+ if (f.length < 4)
105060
+ return null;
105061
+ const probed = Number(f[1]);
105062
+ const count = Number(f[2]);
105063
+ if (!Number.isFinite(probed) || !Number.isFinite(count) || probed < 0 || count < 0)
105064
+ return null;
105065
+ const corrupt = f.slice(3).join("|").split(";").map((s) => s.trim()).filter((s) => s !== "");
105066
+ if (corrupt.length !== count)
105067
+ return null;
105068
+ return { probed, corrupt };
105069
+ }
104689
105070
  async function probeOnce(opts = {}) {
104690
105071
  const now = opts.now ?? Date.now;
104691
105072
  const ts = now();
@@ -104706,6 +105087,8 @@ async function probeOnce(opts = {}) {
104706
105087
  throw new ProbeError(`reading recall logs: ${e.message}`, "spool");
104707
105088
  }
104708
105089
  const consolidation = probeConsolidationQueue(opts.container, opts.run);
105090
+ const banks = probeBankConsolidation(opts.container, opts.run);
105091
+ const vectorIndex = probeVectorIndexes(opts.container, opts.run);
104709
105092
  return {
104710
105093
  sample: {
104711
105094
  ts,
@@ -104721,7 +105104,9 @@ async function probeOnce(opts = {}) {
104721
105104
  startedAt: container.startedAt,
104722
105105
  health: container.health,
104723
105106
  recall,
104724
- consolidation
105107
+ consolidation,
105108
+ banks,
105109
+ vectorIndex
104725
105110
  },
104726
105111
  spool,
104727
105112
  recall
@@ -105051,6 +105436,136 @@ function evaluateConsolidationQueueAge(ring) {
105051
105436
  measured: { pending: c.pending, oldestAgeS: c.oldestAgeS }
105052
105437
  };
105053
105438
  }
105439
+ function evaluateConsolidationFailureStreak(ring) {
105440
+ const signal = "consolidation-failure-streak";
105441
+ if (ring.length === 0)
105442
+ return { signal, state: "no-data", detail: "no samples" };
105443
+ const banks = ring[ring.length - 1].banks ?? null;
105444
+ if (banks === null) {
105445
+ return {
105446
+ signal,
105447
+ state: "no-data",
105448
+ detail: "per-bank consolidation not probed this tick (no psql client, no pg descriptor, " + "or the container is down) \u2014 the `container` signal covers the last of those"
105449
+ };
105450
+ }
105451
+ const live = banks.streaks.filter((s) => s.newestFailureAgeS <= CONSOLIDATION_STREAK_RECENCY_S);
105452
+ let worst = null;
105453
+ for (const s of live)
105454
+ if (worst === null || s.streak > worst.streak)
105455
+ worst = s;
105456
+ if (worst === null || worst.streak < CONSOLIDATION_FAILURE_STREAK_WARN) {
105457
+ const observed = worst?.streak ?? 0;
105458
+ return {
105459
+ signal,
105460
+ state: "ok",
105461
+ detail: `longest live consolidation-failure streak ${observed} across ` + `${banks.streaks.length} bank/op pair(s) \u2014 warn \u2265${CONSOLIDATION_FAILURE_STREAK_WARN}, ` + `page \u2265${CONSOLIDATION_FAILURE_STREAK_PAGE}`,
105462
+ measured: { streak: observed, pairs: banks.streaks.length }
105463
+ };
105464
+ }
105465
+ const { state, severity } = scoreHigherIsWorse(worst.streak, CONSOLIDATION_FAILURE_STREAK_WARN, CONSOLIDATION_FAILURE_STREAK_PAGE);
105466
+ const breaching = live.filter((s) => s.streak >= CONSOLIDATION_FAILURE_STREAK_WARN);
105467
+ const others = breaching.filter((s) => s !== worst).map((s) => `${s.bank}/${s.operationType}\u00d7${s.streak}`);
105468
+ return {
105469
+ signal,
105470
+ state,
105471
+ severity,
105472
+ detail: `bank "${worst.bank}": ${worst.streak} consecutive FAILED ${worst.operationType} ` + `operation(s) with no completion in between, newest ` + `${Math.round(worst.newestFailureAgeS / 60)}m ago \u2014 ` + `warn \u2265${CONSOLIDATION_FAILURE_STREAK_WARN}, page \u2265${CONSOLIDATION_FAILURE_STREAK_PAGE}. ` + "The pending/processing queue reads EMPTY while this holds, so " + "`consolidation-queue-age` cannot see it." + (others.length > 0 ? `
105473
+ also breaching: ${others.join(", ")}` : ""),
105474
+ measured: {
105475
+ streak: worst.streak,
105476
+ bank: worst.bank,
105477
+ operationType: worst.operationType,
105478
+ newestFailureAgeS: worst.newestFailureAgeS,
105479
+ breachingPairs: breaching.length
105480
+ }
105481
+ };
105482
+ }
105483
+ function evaluatePendingConsolidationDepth(ring) {
105484
+ const signal = "pending-consolidation-depth";
105485
+ const withBanks = ring.filter((s) => (s.banks ?? null) !== null);
105486
+ if (withBanks.length < 2) {
105487
+ return {
105488
+ signal,
105489
+ state: "no-data",
105490
+ detail: `only ${withBanks.length} tick(s) carried a per-bank block \u2014 need two to see a trend`,
105491
+ measured: { samples: withBanks.length }
105492
+ };
105493
+ }
105494
+ const first = new Map(withBanks[0].banks.pending.map((p) => [p.bank, p.pending]));
105495
+ const last = withBanks[withBanks.length - 1].banks.pending;
105496
+ const mins = windowMinutes(withBanks);
105497
+ let worst = null;
105498
+ for (const p of last) {
105499
+ const from = first.get(p.bank);
105500
+ if (from === undefined)
105501
+ continue;
105502
+ const growth = p.pending - from;
105503
+ const need = Math.max(PENDING_CONSOLIDATION_GROWTH_WARN_ABS, Math.ceil(PENDING_CONSOLIDATION_GROWTH_FRACTION * from));
105504
+ if (p.pending < PENDING_CONSOLIDATION_FLOOR || growth < need)
105505
+ continue;
105506
+ if (worst === null || growth > worst.growth) {
105507
+ worst = { bank: p.bank, from, to: p.pending, growth, need };
105508
+ }
105509
+ }
105510
+ if (worst === null) {
105511
+ return {
105512
+ signal,
105513
+ state: "ok",
105514
+ detail: `no bank's unconsolidated depth is both \u2265${PENDING_CONSOLIDATION_FLOOR} and rising ` + `over ${mins}m (${last.length} bank(s))`,
105515
+ measured: { banks: last.length, windowMinutes: mins }
105516
+ };
105517
+ }
105518
+ const { state, severity } = scoreHigherIsWorse(worst.growth, PENDING_CONSOLIDATION_GROWTH_WARN_ABS, PENDING_CONSOLIDATION_GROWTH_PAGE_ABS);
105519
+ const perHour = mins > 0 ? Math.round(worst.growth * 60 / mins) : worst.growth;
105520
+ return {
105521
+ signal,
105522
+ state,
105523
+ severity,
105524
+ detail: `bank "${worst.bank}": unconsolidated memories ${worst.from} \u2192 ${worst.to} ` + `(+${worst.growth}, \u2248${perHour}/h) over ${mins}m \u2014 fires at ` + `\u2265${PENDING_CONSOLIDATION_FLOOR} deep AND +${worst.need} growth, pages at ` + `+${PENDING_CONSOLIDATION_GROWTH_PAGE_ABS}. Rising depth with no failed operations ` + `means consolidation is not being attempted at all.`,
105525
+ measured: {
105526
+ bank: worst.bank,
105527
+ pending: worst.to,
105528
+ growth: worst.growth,
105529
+ growthPerHour: perHour,
105530
+ growthNeeded: worst.need,
105531
+ windowMinutes: mins
105532
+ }
105533
+ };
105534
+ }
105535
+ function evaluateVectorIndexCorruption(ring) {
105536
+ const signal = "vector-index-corruption";
105537
+ if (ring.length === 0)
105538
+ return { signal, state: "no-data", detail: "no samples" };
105539
+ const v = ring[ring.length - 1].vectorIndex ?? null;
105540
+ if (v === null) {
105541
+ return {
105542
+ signal,
105543
+ state: "no-data",
105544
+ detail: "vector-index canary did not run this tick (no psql client, no pg descriptor, " + "or the container is down) \u2014 the `container` signal covers the last of those"
105545
+ };
105546
+ }
105547
+ if (v.corrupt.length === 0) {
105548
+ return {
105549
+ signal,
105550
+ state: "ok",
105551
+ detail: `${v.probed} HNSW index(es) answered a nearest-neighbour probe cleanly`,
105552
+ measured: { probed: v.probed, corrupt: 0 }
105553
+ };
105554
+ }
105555
+ const shown = v.corrupt.slice(0, 5).join(`
105556
+ `);
105557
+ const more = v.corrupt.length > 5 ? `
105558
+ \u2026and ${v.corrupt.length - 5} more` : "";
105559
+ return {
105560
+ signal,
105561
+ state: "breach",
105562
+ severity: "page",
105563
+ detail: `${v.corrupt.length} of ${v.probed + v.corrupt.length} HNSW index(es) RAISED on a ` + `nearest-neighbour probe \u2014 the index is corrupt, and every consolidation or recall ` + `that touches it fails:
105564
+ ${shown}${more}
105565
+ ` + `Fix: REINDEX the named index(es). amcheck cannot verify HNSW, so this probe is the ` + `only detector.`,
105566
+ measured: { probed: v.probed, corrupt: v.corrupt.length, first: v.corrupt[0] }
105567
+ };
105568
+ }
105054
105569
  function evaluateLlmFallback(ring) {
105055
105570
  const signal = "llm-fallback-ineffective";
105056
105571
  if (ring.length < 2) {
@@ -105099,6 +105614,9 @@ function evaluateAll(ring) {
105099
105614
  evaluateRecallInjectedScore(ring),
105100
105615
  evaluateRecallZeroMemory(ring),
105101
105616
  evaluateConsolidationQueueAge(ring),
105617
+ evaluateConsolidationFailureStreak(ring),
105618
+ evaluatePendingConsolidationDepth(ring),
105619
+ evaluateVectorIndexCorruption(ring),
105102
105620
  evaluateLlmFallback(ring)
105103
105621
  ];
105104
105622
  }
@@ -105107,7 +105625,12 @@ function evaluateAll(ring) {
105107
105625
  init_state2();
105108
105626
  init_thresholds();
105109
105627
  init_types5();
105110
- var EDGE_SIGNALS = new Set(["retain-loss", "container"]);
105628
+ var EDGE_SIGNALS = new Set([
105629
+ "retain-loss",
105630
+ "container",
105631
+ "consolidation-failure-streak",
105632
+ "vector-index-corruption"
105633
+ ]);
105111
105634
  function breachesToFire(signal) {
105112
105635
  return EDGE_SIGNALS.has(signal) ? BREACHES_TO_FIRE_EDGE : BREACHES_TO_FIRE_LEVEL;
105113
105636
  }