switchroom 0.19.34 → 0.19.35

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.
@@ -18787,6 +18787,9 @@ init_schema();
18787
18787
  // src/config/users.ts
18788
18788
  init_merge();
18789
18789
 
18790
+ // src/config/thinking-effort-risk.ts
18791
+ var RISKY_EFFORTS = new Set(["medium", "high", "xhigh", "max"]);
18792
+
18790
18793
  // src/agents/scaffold.ts
18791
18794
  init_merge();
18792
18795
  init_timezone();
@@ -18908,12 +18911,15 @@ function pgMib(mib) {
18908
18911
  }
18909
18912
  var HINDSIGHT_PG_EFFECTIVE_CACHE_SIZE_ENV = "SWITCHROOM_HINDSIGHT_PG_EFFECTIVE_CACHE_SIZE";
18910
18913
  var HINDSIGHT_PG_SHARED_BUFFERS_ENV = "SWITCHROOM_HINDSIGHT_PG_SHARED_BUFFERS";
18914
+ var HINDSIGHT_PG_FSYNC_ENV = "SWITCHROOM_HINDSIGHT_PG_FSYNC";
18915
+ var HINDSIGHT_PG_DEFAULT_FSYNC = "on";
18911
18916
  var HINDSIGHT_PG_DEFAULTS = [
18912
18917
  [
18913
18918
  HINDSIGHT_PG_EFFECTIVE_CACHE_SIZE_ENV,
18914
18919
  pgMib(HINDSIGHT_PG_DEFAULT_EFFECTIVE_CACHE_SIZE_MIB)
18915
18920
  ],
18916
- [HINDSIGHT_PG_SHARED_BUFFERS_ENV, pgMib(HINDSIGHT_PG_DEFAULT_SHARED_BUFFERS_MIB)]
18921
+ [HINDSIGHT_PG_SHARED_BUFFERS_ENV, pgMib(HINDSIGHT_PG_DEFAULT_SHARED_BUFFERS_MIB)],
18922
+ [HINDSIGHT_PG_FSYNC_ENV, HINDSIGHT_PG_DEFAULT_FSYNC]
18917
18923
  ];
18918
18924
  var HINDSIGHT_PG_ENV_KEYS = new Set(HINDSIGHT_PG_DEFAULTS.map(([k]) => k));
18919
18925
 
@@ -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.35", COMMIT_SHA = "76ae22ca";
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
  });
@@ -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;
@@ -104653,7 +104694,7 @@ function probeSpool(agentsDir = resolveAgentsDir2()) {
104653
104694
  }
104654
104695
  return { pending, dead, evicted, drops, agents };
104655
104696
  }
104656
- var CONSOLIDATION_QUEUE_SH = `
104697
+ var PSQL_BOOTSTRAP_SH = `
104657
104698
  set -e
104658
104699
  B="$(ls -d /home/hindsight/.pg0/installation/*/bin 2>/dev/null | head -1)"
104659
104700
  PSQL="$(command -v psql 2>/dev/null || echo "\${B:-/nonexistent}/psql")"
@@ -104670,9 +104711,63 @@ d=json.load(open(sys.argv[1]))
104670
104711
  q=lambda k,dflt: shlex.quote(str(d.get(k) or dflt))
104671
104712
  print("U=%s DB=%s P=%s PW=%s"%(q("username","hindsight"),q("database","hindsight"),q("port",5432),q("password","")))' "$D")"
104672
104713
  [ -n "$PW" ] || exit 0
104714
+ `;
104715
+ var CONSOLIDATION_QUEUE_SH = `${PSQL_BOOTSTRAP_SH}
104673
104716
  PGPASSWORD="$PW" "$PSQL" -U "$U" -h /tmp -p "$P" -d "$DB" -tAc \\
104674
104717
  "SELECT count(*)||'|'||coalesce(max(extract(epoch from (now()-created_at)))::bigint,0) FROM async_operations WHERE status IN ('pending','processing')"
104675
104718
  `;
104719
+ var BANK_CONSOLIDATION_SH = `${PSQL_BOOTSTRAP_SH}
104720
+ PGPASSWORD="$PW" "$PSQL" -U "$U" -h /tmp -p "$P" -d "$DB" -tAc \\
104721
+ "SELECT 'S|'||a.bank_id||'|'||a.operation_type||'|'||count(*)||'|'||extract(epoch from (now()-max(a.created_at)))::bigint
104722
+ FROM async_operations a
104723
+ WHERE a.status = 'failed'
104724
+ AND a.created_at > coalesce((SELECT max(b.created_at) FROM async_operations b
104725
+ WHERE b.bank_id = a.bank_id
104726
+ AND b.operation_type = a.operation_type
104727
+ AND b.status = 'completed'), '-infinity'::timestamptz)
104728
+ GROUP BY a.bank_id, a.operation_type
104729
+ UNION ALL
104730
+ SELECT 'P|'||m.bank_id||'|'||count(*) FILTER (WHERE m.consolidated_at IS NULL AND m.fact_type IN ('experience','world'))
104731
+ FROM memory_units m
104732
+ GROUP BY m.bank_id"
104733
+ `;
104734
+ function vectorIndexSh(sampleRows) {
104735
+ const limit = Math.max(1, Math.floor(Number.isFinite(sampleRows) ? sampleRows : 1));
104736
+ return `${PSQL_BOOTSTRAP_SH}
104737
+ PGPASSWORD="$PW" PGOPTIONS='-c client_min_messages=notice' \\
104738
+ "$PSQL" -U "$U" -h /tmp -p "$P" -d "$DB" -tAc "
104739
+ DO \\$\\$
104740
+ DECLARE r record; bad text := ''; n int := 0; probed int := 0;
104741
+ BEGIN
104742
+ FOR r IN
104743
+ SELECT c.relname AS idx,
104744
+ i.indrelid::regclass::text AS tbl,
104745
+ coalesce(pg_get_expr(i.indpred, i.indrelid), 'true') AS pred
104746
+ FROM pg_index i
104747
+ JOIN pg_class c ON c.oid = i.indexrelid
104748
+ JOIN pg_am am ON am.oid = c.relam
104749
+ WHERE am.amname = 'hnsw'
104750
+ ORDER BY c.relname
104751
+ LOOP
104752
+ BEGIN
104753
+ EXECUTE format(
104754
+ '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',
104755
+ r.tbl, r.pred, ${limit});
104756
+ probed := probed + 1;
104757
+ EXCEPTION WHEN others THEN
104758
+ n := n + 1;
104759
+ -- Both delimiters are stripped from the message BEFORE it is appended.
104760
+ -- ';' separates entries and a newline would split the NOTICE line, so an
104761
+ -- SQLERRM containing either would desynchronise the parsed list from n,
104762
+ -- and probeVectorIndexes rejects a mismatched block as unreadable \u2014
104763
+ -- turning a real corruption into a silent no-data.
104764
+ bad := bad || r.idx || '=' || translate(SQLERRM, ';' || chr(10) || chr(13), ', ') || ';';
104765
+ END;
104766
+ END LOOP;
104767
+ RAISE NOTICE 'VECIDX|%|%|%', probed, n, bad;
104768
+ END \\$\\$;" 2>&1
104769
+ `;
104770
+ }
104676
104771
  function probeConsolidationQueue(container = DEFAULT_CONTAINER, run = defaultRunner4) {
104677
104772
  const r = run("docker", ["exec", container, "sh", "-c", CONSOLIDATION_QUEUE_SH]);
104678
104773
  if (r.status !== 0)
@@ -104686,6 +104781,61 @@ function probeConsolidationQueue(container = DEFAULT_CONTAINER, run = defaultRun
104686
104781
  return null;
104687
104782
  return { pending, oldestAgeS };
104688
104783
  }
104784
+ function probeBankConsolidation(container = DEFAULT_CONTAINER, run = defaultRunner4) {
104785
+ const r = run("docker", ["exec", container, "sh", "-c", BANK_CONSOLIDATION_SH]);
104786
+ if (r.status !== 0)
104787
+ return null;
104788
+ const streaks = [];
104789
+ const pending = [];
104790
+ let parsedAny = false;
104791
+ for (const line of r.stdout.split(`
104792
+ `)) {
104793
+ const t = line.trim();
104794
+ if (t === "")
104795
+ continue;
104796
+ const f = t.split("|");
104797
+ if (f[0] === "S" && f.length === 5) {
104798
+ const streak = Number(f[3]);
104799
+ const age = Number(f[4]);
104800
+ if (!Number.isFinite(streak) || !Number.isFinite(age) || streak < 0 || age < 0)
104801
+ continue;
104802
+ streaks.push({ bank: f[1], operationType: f[2], streak, newestFailureAgeS: age });
104803
+ parsedAny = true;
104804
+ } else if (f[0] === "P" && f.length === 3) {
104805
+ const n = Number(f[2]);
104806
+ if (!Number.isFinite(n) || n < 0)
104807
+ continue;
104808
+ pending.push({ bank: f[1], pending: n });
104809
+ parsedAny = true;
104810
+ }
104811
+ }
104812
+ if (!parsedAny)
104813
+ return null;
104814
+ return { streaks, pending };
104815
+ }
104816
+ function probeVectorIndexes(container = DEFAULT_CONTAINER, run = defaultRunner4, sampleRows = VECTOR_INDEX_SAMPLE_ROWS) {
104817
+ const sh = vectorIndexSh(sampleRows);
104818
+ if (!sh.includes("count(nn)"))
104819
+ return null;
104820
+ const r = run("docker", ["exec", container, "sh", "-c", sh]);
104821
+ if (r.status !== 0)
104822
+ return null;
104823
+ const line = r.stdout.split(`
104824
+ `).find((l) => l.includes("VECIDX|"));
104825
+ if (line === undefined)
104826
+ return null;
104827
+ const f = line.slice(line.indexOf("VECIDX|")).trim().split("|");
104828
+ if (f.length < 4)
104829
+ return null;
104830
+ const probed = Number(f[1]);
104831
+ const count = Number(f[2]);
104832
+ if (!Number.isFinite(probed) || !Number.isFinite(count) || probed < 0 || count < 0)
104833
+ return null;
104834
+ const corrupt = f.slice(3).join("|").split(";").map((s) => s.trim()).filter((s) => s !== "");
104835
+ if (corrupt.length !== count)
104836
+ return null;
104837
+ return { probed, corrupt };
104838
+ }
104689
104839
  async function probeOnce(opts = {}) {
104690
104840
  const now = opts.now ?? Date.now;
104691
104841
  const ts = now();
@@ -104706,6 +104856,8 @@ async function probeOnce(opts = {}) {
104706
104856
  throw new ProbeError(`reading recall logs: ${e.message}`, "spool");
104707
104857
  }
104708
104858
  const consolidation = probeConsolidationQueue(opts.container, opts.run);
104859
+ const banks = probeBankConsolidation(opts.container, opts.run);
104860
+ const vectorIndex = probeVectorIndexes(opts.container, opts.run);
104709
104861
  return {
104710
104862
  sample: {
104711
104863
  ts,
@@ -104721,7 +104873,9 @@ async function probeOnce(opts = {}) {
104721
104873
  startedAt: container.startedAt,
104722
104874
  health: container.health,
104723
104875
  recall,
104724
- consolidation
104876
+ consolidation,
104877
+ banks,
104878
+ vectorIndex
104725
104879
  },
104726
104880
  spool,
104727
104881
  recall
@@ -105051,6 +105205,136 @@ function evaluateConsolidationQueueAge(ring) {
105051
105205
  measured: { pending: c.pending, oldestAgeS: c.oldestAgeS }
105052
105206
  };
105053
105207
  }
105208
+ function evaluateConsolidationFailureStreak(ring) {
105209
+ const signal = "consolidation-failure-streak";
105210
+ if (ring.length === 0)
105211
+ return { signal, state: "no-data", detail: "no samples" };
105212
+ const banks = ring[ring.length - 1].banks ?? null;
105213
+ if (banks === null) {
105214
+ return {
105215
+ signal,
105216
+ state: "no-data",
105217
+ 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"
105218
+ };
105219
+ }
105220
+ const live = banks.streaks.filter((s) => s.newestFailureAgeS <= CONSOLIDATION_STREAK_RECENCY_S);
105221
+ let worst = null;
105222
+ for (const s of live)
105223
+ if (worst === null || s.streak > worst.streak)
105224
+ worst = s;
105225
+ if (worst === null || worst.streak < CONSOLIDATION_FAILURE_STREAK_WARN) {
105226
+ const observed = worst?.streak ?? 0;
105227
+ return {
105228
+ signal,
105229
+ state: "ok",
105230
+ 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}`,
105231
+ measured: { streak: observed, pairs: banks.streaks.length }
105232
+ };
105233
+ }
105234
+ const { state, severity } = scoreHigherIsWorse(worst.streak, CONSOLIDATION_FAILURE_STREAK_WARN, CONSOLIDATION_FAILURE_STREAK_PAGE);
105235
+ const breaching = live.filter((s) => s.streak >= CONSOLIDATION_FAILURE_STREAK_WARN);
105236
+ const others = breaching.filter((s) => s !== worst).map((s) => `${s.bank}/${s.operationType}\u00d7${s.streak}`);
105237
+ return {
105238
+ signal,
105239
+ state,
105240
+ severity,
105241
+ 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 ? `
105242
+ also breaching: ${others.join(", ")}` : ""),
105243
+ measured: {
105244
+ streak: worst.streak,
105245
+ bank: worst.bank,
105246
+ operationType: worst.operationType,
105247
+ newestFailureAgeS: worst.newestFailureAgeS,
105248
+ breachingPairs: breaching.length
105249
+ }
105250
+ };
105251
+ }
105252
+ function evaluatePendingConsolidationDepth(ring) {
105253
+ const signal = "pending-consolidation-depth";
105254
+ const withBanks = ring.filter((s) => (s.banks ?? null) !== null);
105255
+ if (withBanks.length < 2) {
105256
+ return {
105257
+ signal,
105258
+ state: "no-data",
105259
+ detail: `only ${withBanks.length} tick(s) carried a per-bank block \u2014 need two to see a trend`,
105260
+ measured: { samples: withBanks.length }
105261
+ };
105262
+ }
105263
+ const first = new Map(withBanks[0].banks.pending.map((p) => [p.bank, p.pending]));
105264
+ const last = withBanks[withBanks.length - 1].banks.pending;
105265
+ const mins = windowMinutes(withBanks);
105266
+ let worst = null;
105267
+ for (const p of last) {
105268
+ const from = first.get(p.bank);
105269
+ if (from === undefined)
105270
+ continue;
105271
+ const growth = p.pending - from;
105272
+ const need = Math.max(PENDING_CONSOLIDATION_GROWTH_WARN_ABS, Math.ceil(PENDING_CONSOLIDATION_GROWTH_FRACTION * from));
105273
+ if (p.pending < PENDING_CONSOLIDATION_FLOOR || growth < need)
105274
+ continue;
105275
+ if (worst === null || growth > worst.growth) {
105276
+ worst = { bank: p.bank, from, to: p.pending, growth, need };
105277
+ }
105278
+ }
105279
+ if (worst === null) {
105280
+ return {
105281
+ signal,
105282
+ state: "ok",
105283
+ detail: `no bank's unconsolidated depth is both \u2265${PENDING_CONSOLIDATION_FLOOR} and rising ` + `over ${mins}m (${last.length} bank(s))`,
105284
+ measured: { banks: last.length, windowMinutes: mins }
105285
+ };
105286
+ }
105287
+ const { state, severity } = scoreHigherIsWorse(worst.growth, PENDING_CONSOLIDATION_GROWTH_WARN_ABS, PENDING_CONSOLIDATION_GROWTH_PAGE_ABS);
105288
+ const perHour = mins > 0 ? Math.round(worst.growth * 60 / mins) : worst.growth;
105289
+ return {
105290
+ signal,
105291
+ state,
105292
+ severity,
105293
+ 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.`,
105294
+ measured: {
105295
+ bank: worst.bank,
105296
+ pending: worst.to,
105297
+ growth: worst.growth,
105298
+ growthPerHour: perHour,
105299
+ growthNeeded: worst.need,
105300
+ windowMinutes: mins
105301
+ }
105302
+ };
105303
+ }
105304
+ function evaluateVectorIndexCorruption(ring) {
105305
+ const signal = "vector-index-corruption";
105306
+ if (ring.length === 0)
105307
+ return { signal, state: "no-data", detail: "no samples" };
105308
+ const v = ring[ring.length - 1].vectorIndex ?? null;
105309
+ if (v === null) {
105310
+ return {
105311
+ signal,
105312
+ state: "no-data",
105313
+ 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"
105314
+ };
105315
+ }
105316
+ if (v.corrupt.length === 0) {
105317
+ return {
105318
+ signal,
105319
+ state: "ok",
105320
+ detail: `${v.probed} HNSW index(es) answered a nearest-neighbour probe cleanly`,
105321
+ measured: { probed: v.probed, corrupt: 0 }
105322
+ };
105323
+ }
105324
+ const shown = v.corrupt.slice(0, 5).join(`
105325
+ `);
105326
+ const more = v.corrupt.length > 5 ? `
105327
+ \u2026and ${v.corrupt.length - 5} more` : "";
105328
+ return {
105329
+ signal,
105330
+ state: "breach",
105331
+ severity: "page",
105332
+ 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:
105333
+ ${shown}${more}
105334
+ ` + `Fix: REINDEX the named index(es). amcheck cannot verify HNSW, so this probe is the ` + `only detector.`,
105335
+ measured: { probed: v.probed, corrupt: v.corrupt.length, first: v.corrupt[0] }
105336
+ };
105337
+ }
105054
105338
  function evaluateLlmFallback(ring) {
105055
105339
  const signal = "llm-fallback-ineffective";
105056
105340
  if (ring.length < 2) {
@@ -105099,6 +105383,9 @@ function evaluateAll(ring) {
105099
105383
  evaluateRecallInjectedScore(ring),
105100
105384
  evaluateRecallZeroMemory(ring),
105101
105385
  evaluateConsolidationQueueAge(ring),
105386
+ evaluateConsolidationFailureStreak(ring),
105387
+ evaluatePendingConsolidationDepth(ring),
105388
+ evaluateVectorIndexCorruption(ring),
105102
105389
  evaluateLlmFallback(ring)
105103
105390
  ];
105104
105391
  }
@@ -105107,7 +105394,12 @@ function evaluateAll(ring) {
105107
105394
  init_state2();
105108
105395
  init_thresholds();
105109
105396
  init_types5();
105110
- var EDGE_SIGNALS = new Set(["retain-loss", "container"]);
105397
+ var EDGE_SIGNALS = new Set([
105398
+ "retain-loss",
105399
+ "container",
105400
+ "consolidation-failure-streak",
105401
+ "vector-index-corruption"
105402
+ ]);
105111
105403
  function breachesToFire(signal) {
105112
105404
  return EDGE_SIGNALS.has(signal) ? BREACHES_TO_FIRE_EDGE : BREACHES_TO_FIRE_LEVEL;
105113
105405
  }
@@ -21311,7 +21311,7 @@ function allocateAgentUid(name) {
21311
21311
  }
21312
21312
 
21313
21313
  // src/build-info.ts
21314
- var VERSION = "0.19.34";
21314
+ var VERSION = "0.19.35";
21315
21315
 
21316
21316
  // src/setup/hindsight-recall-tunables.ts
21317
21317
  var RECALL_DEADLINE_HEADROOM_SECONDS = 2;
@@ -21323,6 +21323,9 @@ init_schema();
21323
21323
  // src/config/users.ts
21324
21324
  init_merge();
21325
21325
 
21326
+ // src/config/thinking-effort-risk.ts
21327
+ var RISKY_EFFORTS = new Set(["medium", "high", "xhigh", "max"]);
21328
+
21326
21329
  // src/agents/scaffold.ts
21327
21330
  init_merge();
21328
21331
  init_timezone();
@@ -21444,12 +21447,15 @@ function pgMib(mib) {
21444
21447
  }
21445
21448
  var HINDSIGHT_PG_EFFECTIVE_CACHE_SIZE_ENV = "SWITCHROOM_HINDSIGHT_PG_EFFECTIVE_CACHE_SIZE";
21446
21449
  var HINDSIGHT_PG_SHARED_BUFFERS_ENV = "SWITCHROOM_HINDSIGHT_PG_SHARED_BUFFERS";
21450
+ var HINDSIGHT_PG_FSYNC_ENV = "SWITCHROOM_HINDSIGHT_PG_FSYNC";
21451
+ var HINDSIGHT_PG_DEFAULT_FSYNC = "on";
21447
21452
  var HINDSIGHT_PG_DEFAULTS = [
21448
21453
  [
21449
21454
  HINDSIGHT_PG_EFFECTIVE_CACHE_SIZE_ENV,
21450
21455
  pgMib(HINDSIGHT_PG_DEFAULT_EFFECTIVE_CACHE_SIZE_MIB)
21451
21456
  ],
21452
- [HINDSIGHT_PG_SHARED_BUFFERS_ENV, pgMib(HINDSIGHT_PG_DEFAULT_SHARED_BUFFERS_MIB)]
21457
+ [HINDSIGHT_PG_SHARED_BUFFERS_ENV, pgMib(HINDSIGHT_PG_DEFAULT_SHARED_BUFFERS_MIB)],
21458
+ [HINDSIGHT_PG_FSYNC_ENV, HINDSIGHT_PG_DEFAULT_FSYNC]
21453
21459
  ];
21454
21460
  var HINDSIGHT_PG_ENV_KEYS = new Set(HINDSIGHT_PG_DEFAULTS.map(([k]) => k));
21455
21461
 
@@ -19220,6 +19220,9 @@ init_schema();
19220
19220
  // src/config/users.ts
19221
19221
  init_merge();
19222
19222
 
19223
+ // src/config/thinking-effort-risk.ts
19224
+ var RISKY_EFFORTS = new Set(["medium", "high", "xhigh", "max"]);
19225
+
19223
19226
  // src/agents/scaffold.ts
19224
19227
  init_merge();
19225
19228
  init_timezone();
@@ -19341,12 +19344,15 @@ function pgMib(mib) {
19341
19344
  }
19342
19345
  var HINDSIGHT_PG_EFFECTIVE_CACHE_SIZE_ENV = "SWITCHROOM_HINDSIGHT_PG_EFFECTIVE_CACHE_SIZE";
19343
19346
  var HINDSIGHT_PG_SHARED_BUFFERS_ENV = "SWITCHROOM_HINDSIGHT_PG_SHARED_BUFFERS";
19347
+ var HINDSIGHT_PG_FSYNC_ENV = "SWITCHROOM_HINDSIGHT_PG_FSYNC";
19348
+ var HINDSIGHT_PG_DEFAULT_FSYNC = "on";
19344
19349
  var HINDSIGHT_PG_DEFAULTS = [
19345
19350
  [
19346
19351
  HINDSIGHT_PG_EFFECTIVE_CACHE_SIZE_ENV,
19347
19352
  pgMib(HINDSIGHT_PG_DEFAULT_EFFECTIVE_CACHE_SIZE_MIB)
19348
19353
  ],
19349
- [HINDSIGHT_PG_SHARED_BUFFERS_ENV, pgMib(HINDSIGHT_PG_DEFAULT_SHARED_BUFFERS_MIB)]
19354
+ [HINDSIGHT_PG_SHARED_BUFFERS_ENV, pgMib(HINDSIGHT_PG_DEFAULT_SHARED_BUFFERS_MIB)],
19355
+ [HINDSIGHT_PG_FSYNC_ENV, HINDSIGHT_PG_DEFAULT_FSYNC]
19350
19356
  ];
19351
19357
  var HINDSIGHT_PG_ENV_KEYS = new Set(HINDSIGHT_PG_DEFAULTS.map(([k]) => k));
19352
19358
 
@@ -19461,6 +19461,9 @@ init_schema();
19461
19461
  // src/config/users.ts
19462
19462
  init_merge();
19463
19463
 
19464
+ // src/config/thinking-effort-risk.ts
19465
+ var RISKY_EFFORTS = new Set(["medium", "high", "xhigh", "max"]);
19466
+
19464
19467
  // src/agents/scaffold.ts
19465
19468
  init_merge();
19466
19469
  init_timezone();
@@ -19754,12 +19757,15 @@ function pgMib(mib) {
19754
19757
  }
19755
19758
  var HINDSIGHT_PG_EFFECTIVE_CACHE_SIZE_ENV = "SWITCHROOM_HINDSIGHT_PG_EFFECTIVE_CACHE_SIZE";
19756
19759
  var HINDSIGHT_PG_SHARED_BUFFERS_ENV = "SWITCHROOM_HINDSIGHT_PG_SHARED_BUFFERS";
19760
+ var HINDSIGHT_PG_FSYNC_ENV = "SWITCHROOM_HINDSIGHT_PG_FSYNC";
19761
+ var HINDSIGHT_PG_DEFAULT_FSYNC = "on";
19757
19762
  var HINDSIGHT_PG_DEFAULTS = [
19758
19763
  [
19759
19764
  HINDSIGHT_PG_EFFECTIVE_CACHE_SIZE_ENV,
19760
19765
  pgMib(HINDSIGHT_PG_DEFAULT_EFFECTIVE_CACHE_SIZE_MIB)
19761
19766
  ],
19762
- [HINDSIGHT_PG_SHARED_BUFFERS_ENV, pgMib(HINDSIGHT_PG_DEFAULT_SHARED_BUFFERS_MIB)]
19767
+ [HINDSIGHT_PG_SHARED_BUFFERS_ENV, pgMib(HINDSIGHT_PG_DEFAULT_SHARED_BUFFERS_MIB)],
19768
+ [HINDSIGHT_PG_FSYNC_ENV, HINDSIGHT_PG_DEFAULT_FSYNC]
19763
19769
  ];
19764
19770
  var HINDSIGHT_PG_ENV_KEYS = new Set(HINDSIGHT_PG_DEFAULTS.map(([k]) => k));
19765
19771
 
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.34",
4
+ "version": "0.19.35",
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": {