switchroom 0.15.44 → 0.15.45

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.
@@ -15016,6 +15016,40 @@ var init_loader = __esm(() => {
15016
15016
  };
15017
15017
  });
15018
15018
 
15019
+ // src/config/users.ts
15020
+ function resolveUsers(config, agentName) {
15021
+ const users = config.users ?? {};
15022
+ const agentRaw = config.agents?.[agentName];
15023
+ const agentConfig = agentRaw ? resolveAgentConfig(config.defaults, config.profiles, agentRaw) : undefined;
15024
+ const uniq = (xs) => [...new Set(xs)];
15025
+ const serves = uniq([
15026
+ ...config.defaults?.serves ?? [],
15027
+ ...agentConfig?.serves ?? []
15028
+ ]);
15029
+ const knows = uniq([
15030
+ ...config.defaults?.knows ?? [],
15031
+ ...agentConfig?.knows ?? []
15032
+ ]);
15033
+ const senderBanks = {};
15034
+ for (const userName of serves) {
15035
+ const u = users[userName];
15036
+ if (!u)
15037
+ continue;
15038
+ for (const id of u.telegram_ids) {
15039
+ senderBanks[id] = u.profile_bank;
15040
+ }
15041
+ }
15042
+ Object.assign(senderBanks, agentConfig?.memory?.recall?.sender_banks ?? {});
15043
+ const additionalBanks = uniq([
15044
+ ...knows.map((entry) => users[entry]?.profile_bank ?? entry),
15045
+ ...agentConfig?.memory?.recall?.additional_banks ?? []
15046
+ ]);
15047
+ return { senderBanks, additionalBanks };
15048
+ }
15049
+ var init_users = __esm(() => {
15050
+ init_merge();
15051
+ });
15052
+
15019
15053
  // src/memory/hindsight.ts
15020
15054
  function generateHindsightMcpConfig(collection, memoryConfig) {
15021
15055
  const url = memoryConfig.config?.url ?? "http://localhost:8888/mcp/";
@@ -15031,6 +15065,27 @@ function getCollectionForAgent(agentName, config) {
15031
15065
  const agentConfig = config.agents[agentName];
15032
15066
  return agentConfig?.memory?.collection ?? agentName;
15033
15067
  }
15068
+ function collectProfileBanks(config, opts = {}) {
15069
+ const { excludeAgentCollections = true } = opts;
15070
+ const banks = new Set;
15071
+ for (const user of Object.values(config.users ?? {})) {
15072
+ if (user.profile_bank)
15073
+ banks.add(user.profile_bank);
15074
+ }
15075
+ for (const agentName of Object.keys(config.agents)) {
15076
+ const { senderBanks, additionalBanks } = resolveUsers(config, agentName);
15077
+ for (const b of Object.values(senderBanks))
15078
+ banks.add(b);
15079
+ for (const b of additionalBanks)
15080
+ banks.add(b);
15081
+ }
15082
+ if (excludeAgentCollections) {
15083
+ for (const agentName of Object.keys(config.agents)) {
15084
+ banks.delete(getCollectionForAgent(agentName, config));
15085
+ }
15086
+ }
15087
+ return banks;
15088
+ }
15034
15089
  function isStrictIsolation(agentName, config) {
15035
15090
  const agentConfig = config.agents[agentName];
15036
15091
  return agentConfig?.memory?.isolation === "strict";
@@ -15460,6 +15515,7 @@ async function addMemoryTag(apiUrl, bankId, memoryId, tag, opts) {
15460
15515
  }
15461
15516
  var DEFAULT_RETAIN_MISSION, DEMOTE_FROM_RECALL_TAG = "[demote-from-recall]";
15462
15517
  var init_hindsight = __esm(() => {
15518
+ init_users();
15463
15519
  DEFAULT_RETAIN_MISSION = "Extract user preferences, ongoing projects, recurring commitments, " + "important context, and durable facts that should help across future " + "conversations. Skip one-off chatter and temporary task noise.";
15464
15520
  });
15465
15521
 
@@ -32964,9 +33020,14 @@ async function checkBankIngestHealth(config, url, opts) {
32964
33020
  const bankId = agentConfig.memory?.collection ?? agentName;
32965
33021
  banks.set(bankId, [...banks.get(bankId) ?? [], agentName]);
32966
33022
  }
33023
+ const profileBanks = collectProfileBanks(config);
33024
+ for (const bank of profileBanks) {
33025
+ if (!banks.has(bank))
33026
+ banks.set(bank, []);
33027
+ }
32967
33028
  const inspected = await Promise.all([...banks].map(async ([bankId, agents]) => [bankId, agents, await inspectBankHealth(url, bankId, { fetchImpl: opts?.fetchImpl })]));
32968
33029
  for (const [bankId, agents, h] of inspected) {
32969
- const label = `bank ${bankId}` + (agents[0] !== bankId ? ` (${agents.join(", ")})` : "");
33030
+ const label = `bank ${bankId}` + (profileBanks.has(bankId) ? " (profile)" : agents[0] !== bankId ? ` (${agents.join(", ")})` : "");
32970
33031
  if (!h.ok) {
32971
33032
  results.push({
32972
33033
  name: label,
@@ -50803,8 +50864,8 @@ import { existsSync, readFileSync } from "node:fs";
50803
50864
  import { dirname, join } from "node:path";
50804
50865
 
50805
50866
  // src/build-info.ts
50806
- var VERSION = "0.15.44";
50807
- var COMMIT_SHA = "a375378f";
50867
+ var VERSION = "0.15.45";
50868
+ var COMMIT_SHA = "41082e8b";
50808
50869
 
50809
50870
  // src/cli/resolve-version.ts
50810
50871
  function readPackageVersion() {
@@ -50935,40 +50996,7 @@ var LEGACY_CRON_SCRIPT_BASENAME_RE = /^cron-(\d+)\.sh$/;
50935
50996
  init_schema();
50936
50997
  init_cron_routing();
50937
50998
  init_tier_selector();
50938
-
50939
- // src/config/users.ts
50940
- init_merge();
50941
- function resolveUsers(config, agentName) {
50942
- const users = config.users ?? {};
50943
- const agentRaw = config.agents?.[agentName];
50944
- const agentConfig = agentRaw ? resolveAgentConfig(config.defaults, config.profiles, agentRaw) : undefined;
50945
- const uniq = (xs) => [...new Set(xs)];
50946
- const serves = uniq([
50947
- ...config.defaults?.serves ?? [],
50948
- ...agentConfig?.serves ?? []
50949
- ]);
50950
- const knows = uniq([
50951
- ...config.defaults?.knows ?? [],
50952
- ...agentConfig?.knows ?? []
50953
- ]);
50954
- const senderBanks = {};
50955
- for (const userName of serves) {
50956
- const u = users[userName];
50957
- if (!u)
50958
- continue;
50959
- for (const id of u.telegram_ids) {
50960
- senderBanks[id] = u.profile_bank;
50961
- }
50962
- }
50963
- Object.assign(senderBanks, agentConfig?.memory?.recall?.sender_banks ?? {});
50964
- const additionalBanks = uniq([
50965
- ...knows.map((entry) => users[entry]?.profile_bank ?? entry),
50966
- ...agentConfig?.memory?.recall?.additional_banks ?? []
50967
- ]);
50968
- return { senderBanks, additionalBanks };
50969
- }
50970
-
50971
- // src/agents/scaffold.ts
50999
+ init_users();
50972
51000
  init_merge();
50973
51001
  init_timezone();
50974
51002
 
@@ -53738,15 +53766,6 @@ ${body}
53738
53766
  }).catch((err) => {
53739
53767
  console.warn(` ${source_default.yellow("\u26a0")} Bank mission update error for ${formatAgentBankLabel(name, hindsightBankId)}: ${err}`);
53740
53768
  });
53741
- ensureUserProfileMentalModel(apiUrl, hindsightBankId, { timeoutMs: 5000 }).then((result) => {
53742
- if (result.ok) {
53743
- console.log(` ${source_default.green("\u2713")} User-profile Mental Model ready for ${formatAgentBankLabel(name, hindsightBankId)}`);
53744
- } else {
53745
- console.warn(` ${source_default.yellow("\u26a0")} Failed to create user-profile MM for ${formatAgentBankLabel(name, hindsightBankId)}: ${result.reason}`);
53746
- }
53747
- }).catch((err) => {
53748
- console.warn(` ${source_default.yellow("\u26a0")} User-profile MM error for ${formatAgentBankLabel(name, hindsightBankId)}: ${err}`);
53749
- });
53750
53769
  });
53751
53770
  }
53752
53771
  for (const f of rewrittenWithBackup) {
@@ -53802,14 +53821,6 @@ function buildSettingsHooksBlock(p) {
53802
53821
  async: true
53803
53822
  });
53804
53823
  }
53805
- if (hindsightEnabled) {
53806
- stopHooks.push({
53807
- type: "command",
53808
- command: wrap("hook:user-profile-refresh", `bash "${join10(DOCKER_BIN_PATH, "user-profile-refresh-hook.sh")}"`),
53809
- timeout: 10,
53810
- async: true
53811
- });
53812
- }
53813
53824
  if (useSwitchroomPlugin) {
53814
53825
  stopHooks.push({
53815
53826
  type: "command",
@@ -54682,15 +54693,6 @@ ${body}
54682
54693
  console.warn(` ${source_default.yellow("\u26a0")} Bank mission update error for ${formatAgentBankLabel(name, hindsightBankId)}: ${err}`);
54683
54694
  });
54684
54695
  }
54685
- ensureUserProfileMentalModel(apiUrl, hindsightBankId, { timeoutMs: 5000 }).then((result) => {
54686
- if (result.ok) {
54687
- console.log(` ${source_default.green("\u2713")} User-profile Mental Model ready for ${formatAgentBankLabel(name, hindsightBankId)}`);
54688
- } else {
54689
- console.warn(` ${source_default.yellow("\u26a0")} Failed to create user-profile MM for ${formatAgentBankLabel(name, hindsightBankId)}: ${result.reason}`);
54690
- }
54691
- }).catch((err) => {
54692
- console.warn(` ${source_default.yellow("\u26a0")} User-profile MM error for ${formatAgentBankLabel(name, hindsightBankId)}: ${err}`);
54693
- });
54694
54696
  });
54695
54697
  }
54696
54698
  return {
@@ -69340,6 +69342,26 @@ Searching all eligible collections:
69340
69342
  console.log(` ${row}`);
69341
69343
  }
69342
69344
  console.log();
69345
+ const profileBanks = collectProfileBanks(config);
69346
+ if (profileBanks.size > 0) {
69347
+ const owners = new Map;
69348
+ for (const [uname, u] of Object.entries(config.users ?? {})) {
69349
+ if (u.profile_bank) {
69350
+ owners.set(u.profile_bank, [
69351
+ ...owners.get(u.profile_bank) ?? [],
69352
+ uname
69353
+ ]);
69354
+ }
69355
+ }
69356
+ console.log(source_default.bold(` Profile banks (per-user / shared):
69357
+ `));
69358
+ for (const bank of [...profileBanks].sort()) {
69359
+ const who = owners.get(bank);
69360
+ const label = who ? `user: ${who.join(", ")}` : "shared";
69361
+ console.log(` ${bank.padEnd(widths[1])} ${source_default.gray(label)}`);
69362
+ }
69363
+ console.log();
69364
+ }
69343
69365
  console.log(source_default.bold(` Hindsight CLI commands:
69344
69366
  `));
69345
69367
  for (const name of agentNames) {
@@ -75702,6 +75724,11 @@ async function handleGetMemoryHealth(config, opts) {
75702
75724
  const bank = getCollectionForAgent(agentName, config);
75703
75725
  banks.set(bank, [...banks.get(bank) ?? [], agentName]);
75704
75726
  }
75727
+ const profileBankSet = collectProfileBanks(config);
75728
+ for (const bank of profileBankSet) {
75729
+ if (!banks.has(bank))
75730
+ banks.set(bank, []);
75731
+ }
75705
75732
  const rows = await Promise.all([...banks].map(async ([bank, agents]) => {
75706
75733
  const h = await inspectBankHealth(url, bank, { fetchImpl: opts?.fetchImpl });
75707
75734
  const gaps = recentUnextracted(h.unextractedDocuments, 30, now);
@@ -75746,7 +75773,8 @@ async function handleGetMemoryHealth(config, opts) {
75746
75773
  staleMentalModelCount: stale.length,
75747
75774
  corruptedMentalModelNames: corrupted.map((m) => m.name),
75748
75775
  status,
75749
- statusDetail
75776
+ statusDetail,
75777
+ kind: profileBankSet.has(bank) ? "profile" : "agent"
75750
75778
  };
75751
75779
  }));
75752
75780
  rows.sort((a, b) => a.bank.localeCompare(b.bank));
@@ -75760,7 +75788,7 @@ function isKnownBank(config, bank) {
75760
75788
  if (getCollectionForAgent(agentName, config) === bank)
75761
75789
  return true;
75762
75790
  }
75763
- return false;
75791
+ return collectProfileBanks(config).has(bank);
75764
75792
  }
75765
75793
  async function handleMemoryReprocess(config, body, deps) {
75766
75794
  const bank = typeof body.bank === "string" ? body.bank : "";
@@ -898,7 +898,7 @@
898
898
  return `<div class="agent-card">
899
899
  <div class="card-header" style="cursor:default">
900
900
  ${statusDot(b.status)}<span class="agent-name">${escapeHtml(b.bank)}</span>
901
- <span style="color:var(--text-dim);font-size:.85em;margin-left:.5rem">${escapeHtml((b.agents || []).join(', '))}</span>
901
+ <span style="color:var(--text-dim);font-size:.85em;margin-left:.5rem">${b.kind === 'profile' ? '<em>profile bank</em>' : escapeHtml((b.agents || []).join(', '))}</span>
902
902
  </div>
903
903
  <div style="padding:0 1.25rem 1rem">
904
904
  <div style="color:var(--text-dim);margin:.1rem 0 .3rem">${escapeHtml(b.statusDetail || '')}</div>
@@ -13133,6 +13133,9 @@ function getNonce(db, request_id) {
13133
13133
  import { createHash } from "node:crypto";
13134
13134
  init_merge();
13135
13135
 
13136
+ // src/config/users.ts
13137
+ init_merge();
13138
+
13136
13139
  // src/memory/hindsight.ts
13137
13140
  var DEFAULT_RETAIN_MISSION = "Extract user preferences, ongoing projects, recurring commitments, " + "important context, and durable facts that should help across future " + "conversations. Skip one-off chatter and temporary task noise.";
13138
13141
 
@@ -12677,6 +12677,9 @@ function identify(socketPath, socket, execFileSyncOverride) {
12677
12677
  return { uid, pid, exe, systemdUnit };
12678
12678
  }
12679
12679
 
12680
+ // src/config/users.ts
12681
+ init_merge();
12682
+
12680
12683
  // src/memory/hindsight.ts
12681
12684
  var DEFAULT_RETAIN_MISSION = "Extract user preferences, ongoing projects, recurring commitments, " + "important context, and durable facts that should help across future " + "conversations. Skip one-off chatter and temporary task noise.";
12682
12685
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "switchroom",
3
- "version": "0.15.44",
3
+ "version": "0.15.45",
4
4
  "description": "Run Claude Code 24/7 on your Claude Pro/Max subscription over Telegram. Open-source alternative to OpenClaw and NanoClaw — no API keys.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -61,10 +61,10 @@ Hindsight is a memory bank with semantic search, knowledge graph, entity resolut
61
61
  - `mcp__hindsight__retain` — store a new memory. The plugin automatically retains the conversation transcript every ~10 turns via the Stop hook, so you usually don't need this. Call manually for significant decisions, corrections, or facts you want immediately searchable.
62
62
  - `mcp__hindsight__reflect` — Hindsight's LLM-powered "answer this query using the bank's content + directives". Use when the user asks a question that requires synthesis across multiple past memories.
63
63
 
64
- ### Mental Models (replaces hand-curated user profile)
65
- A mental model is a pre-computed semantic summary backed by reflection over the bank. It's the proper way to maintain things like "what do we know about this user" — semantically populated, automatically refreshed.
64
+ ### Mental Models
65
+ A mental model is a pre-computed semantic summary backed by reflection over the bank a way to maintain a standing answer to a recurring question, semantically populated and refreshed.
66
66
 
67
- - `mcp__hindsight__create_mental_model(name, source_query)` — create one. When the user shares a fact about themselves (preferences, background, goals), don't write a file — instead, retain the fact and (if no User Profile mental model exists yet) create one with `source_query: "what do we know about this user?"`. Hindsight will populate it from the retained memories.
67
+ - `mcp__hindsight__create_mental_model(name, source_query)` — create one for a recurring synthesis you need. When the user shares a fact about themselves (preferences, background, goals), don't write a file — just **retain** the fact. You do NOT need to build or maintain a per-agent "user profile": who the user is lives in dedicated per-user profile banks that the operator curates out-of-band, and recall surfaces it automatically.
68
68
 
69
69
  ### Directives (replaces feedback rules)
70
70
  Hard rules the agent must follow during reflect — guardrails that are always applied.
@@ -54631,10 +54631,10 @@ function readTurnActiveMarkerAgeMs(stateDir, now) {
54631
54631
  }
54632
54632
 
54633
54633
  // ../src/build-info.ts
54634
- var VERSION = "0.15.44";
54635
- var COMMIT_SHA = "a375378f";
54636
- var COMMIT_DATE = "2026-06-19T07:17:32Z";
54637
- var LATEST_PR = 2445;
54634
+ var VERSION = "0.15.45";
54635
+ var COMMIT_SHA = "41082e8b";
54636
+ var COMMIT_DATE = "2026-06-19T09:03:39Z";
54637
+ var LATEST_PR = 2448;
54638
54638
  var COMMITS_AHEAD_OF_TAG = 0;
54639
54639
 
54640
54640
  // gateway/boot-version.ts