switchroom 0.14.75 → 0.14.77

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.
@@ -11211,6 +11211,7 @@ var profileFields = {
11211
11211
  memory: exports_external.object({
11212
11212
  collection: exports_external.string().optional(),
11213
11213
  auto_recall: exports_external.boolean().optional(),
11214
+ file: exports_external.boolean().optional(),
11214
11215
  isolation: exports_external.enum(["default", "strict"]).optional(),
11215
11216
  recall: exports_external.object({
11216
11217
  max_memories: exports_external.number().int().min(0).optional(),
@@ -11211,6 +11211,7 @@ var profileFields = {
11211
11211
  memory: exports_external.object({
11212
11212
  collection: exports_external.string().optional(),
11213
11213
  auto_recall: exports_external.boolean().optional(),
11214
+ file: exports_external.boolean().optional(),
11214
11215
  isolation: exports_external.enum(["default", "strict"]).optional(),
11215
11216
  recall: exports_external.object({
11216
11217
  max_memories: exports_external.number().int().min(0).optional(),
@@ -13891,8 +13892,29 @@ class AuthBroker {
13891
13892
  return override;
13892
13893
  return auth.active ?? null;
13893
13894
  }
13894
- async opGetCredentials(socket, id, identity2) {
13895
+ servingAccount(identity2) {
13895
13896
  const account = this.callerAccount(identity2);
13897
+ if (identity2.kind !== "consumer")
13898
+ return account;
13899
+ return this.consumerAccountWithFailover(account);
13900
+ }
13901
+ isAccountExhausted(account) {
13902
+ const q = this.quota[account];
13903
+ return q !== undefined && q.exhausted_until > this.now();
13904
+ }
13905
+ consumerAccountWithFailover(pinned) {
13906
+ if (!pinned || !this.isAccountExhausted(pinned))
13907
+ return pinned;
13908
+ for (const cand of this.config.auth?.fallback_order ?? []) {
13909
+ if (cand === pinned || this.isAccountExhausted(cand))
13910
+ continue;
13911
+ if (readAccountCredentials(cand, this.home))
13912
+ return cand;
13913
+ }
13914
+ return pinned;
13915
+ }
13916
+ async opGetCredentials(socket, id, identity2) {
13917
+ const account = this.servingAccount(identity2);
13896
13918
  if (!account) {
13897
13919
  this.audit({ op: "get-credentials", identity: identity2, ok: false, error: "no-active-account" });
13898
13920
  socket.write(encodeError(id, "ACCOUNT_NOT_FOUND", "no active account configured"));
@@ -11959,6 +11959,7 @@ var profileFields = {
11959
11959
  memory: exports_external.object({
11960
11960
  collection: exports_external.string().optional(),
11961
11961
  auto_recall: exports_external.boolean().optional(),
11962
+ file: exports_external.boolean().optional(),
11962
11963
  isolation: exports_external.enum(["default", "strict"]).optional(),
11963
11964
  recall: exports_external.object({
11964
11965
  max_memories: exports_external.number().int().min(0).optional(),
@@ -13775,6 +13775,7 @@ var init_schema = __esm(() => {
13775
13775
  memory: exports_external.object({
13776
13776
  collection: exports_external.string().optional(),
13777
13777
  auto_recall: exports_external.boolean().optional(),
13778
+ file: exports_external.boolean().optional(),
13778
13779
  isolation: exports_external.enum(["default", "strict"]).optional(),
13779
13780
  recall: exports_external.object({
13780
13781
  max_memories: exports_external.number().int().min(0).optional(),
@@ -20875,7 +20876,7 @@ function getHindsightSettingsEntry(agentName, config) {
20875
20876
  }
20876
20877
  const collection = getCollectionForAgent(agentName, config);
20877
20878
  const mcpConfig = generateHindsightMcpConfig(collection, memoryConfig);
20878
- return { key: "hindsight", value: mcpConfig };
20879
+ return { key: "hindsight", value: { ...mcpConfig, alwaysLoad: true } };
20879
20880
  }
20880
20881
  function getPlaywrightMcpSettingsEntry() {
20881
20882
  return {
@@ -28973,7 +28974,7 @@ function classifyExtractionLogs(logs) {
28973
28974
  name: "hindsight extraction",
28974
28975
  status: "fail",
28975
28976
  detail: "fact-extraction LLM calls are failing" + (quotaHint ? " (429 / weekly-limit detected)" : "") + " \u2014 retains are accepted but extract 0 facts, so nothing becomes recallable",
28976
- fix: "Usually hindsight's `auth.consumers[hindsight].account` is quota-" + "exhausted or its OAuth broke. Repoint it to an account with quota in " + "switchroom.yaml, then `docker restart switchroom-auth-broker` and " + "`docker restart switchroom-hindsight` (single-file config mount needs " + "the broker restart to re-read). Confirm with a `claude -p` inside the " + "container."
28977
+ fix: "Usually hindsight's `auth.consumers[hindsight].account` is quota-" + "exhausted or its OAuth broke. Repoint it to an account with quota in " + "switchroom.yaml, then `docker restart switchroom-auth-broker` and " + "`docker restart switchroom-hindsight` (single-file config mount needs " + "the broker restart to re-read). Confirm with a headless `claude` run " + "inside the container."
28977
28978
  };
28978
28979
  }
28979
28980
  if (zeroFacts >= 3 && okFacts === 0) {
@@ -49680,8 +49681,8 @@ var {
49680
49681
  } = import__.default;
49681
49682
 
49682
49683
  // src/build-info.ts
49683
- var VERSION = "0.14.75";
49684
- var COMMIT_SHA = "8c331b53";
49684
+ var VERSION = "0.14.77";
49685
+ var COMMIT_SHA = "2473dbbf";
49685
49686
 
49686
49687
  // src/cli/agent.ts
49687
49688
  init_source();
@@ -50533,16 +50534,16 @@ Every turn that answers a user message ends with a user-visible
50533
50534
  sees; your terminal output never reaches them.`;
50534
50535
  var MEMORY_GUIDANCE = `## Memory \u2014 proactive, conversational
50535
50536
 
50536
- You have Hindsight tools: \`mcp__hindsight__sync_retain\`, \`mcp__hindsight__delete_memory\`, \`mcp__hindsight__recall\`, \`mcp__hindsight__reflect\`. Use them without being asked.
50537
+ You have Hindsight tools: \`mcp__hindsight__sync_retain\`, \`mcp__hindsight__delete_document\`, \`mcp__hindsight__recall\`, \`mcp__hindsight__reflect\`. Use them without being asked.
50537
50538
 
50538
50539
  ### Retain proactively
50539
50540
  When the user shares a fact, preference, decision, or plan worth keeping across sessions, call \`sync_retain\` in the same turn. Briefly acknowledge in your reply ("got it, April 2nd anniversary"). Don't narrate the tool call. Skip small talk and transient tool output, the auto-retain hook handles conversation-level signal.
50540
50541
 
50541
50542
  ### Correct proactively
50542
- When the user corrects you or contradicts a prior memory, call \`delete_memory\` on the wrong entry, then \`sync_retain\` the correction. Acknowledge the correction in one line ("noted, Alice not Bob").
50543
+ When the user corrects you or contradicts a prior memory, call \`delete_document\` on the wrong entry, then \`sync_retain\` the correction. Acknowledge the correction in one line ("noted, Alice not Bob").
50543
50544
 
50544
50545
  ### Forget proactively
50545
- When the user asks you to forget something ("forget that", "delete X", "drop what I said about Y"), call \`delete_memory\` for matching entries and confirm what was removed.
50546
+ When the user asks you to forget something ("forget that", "delete X", "drop what I said about Y"), call \`delete_document\` for matching entries and confirm what was removed.
50546
50547
 
50547
50548
  ### Inspect proactively
50548
50549
  When the user asks "what do you know about X / me", "what do you remember about Y", or any memory audit, use \`reflect\` to synthesize an answer across the bank. Return it as honest prose, not a raw dump. If the bank has little on the topic, say so.
@@ -50599,7 +50600,18 @@ name from that list.
50599
50600
  Only call the \`vault_request_access\` MCP tool \u2014 which pings the
50600
50601
  operator for a Telegram approval \u2014 for a key you've **confirmed via
50601
50602
  \`vault list\` you don't already have.** Requesting access to a guessed
50602
- or already-held key wastes the operator's tap and fails.`;
50603
+ or already-held key wastes the operator's tap and fails.
50604
+
50605
+ **Never put a secret's value \u2014 or a \`vault:<key>\` placeholder \u2014 in a
50606
+ tool call.** Tool arguments are sent to the underlying API verbatim;
50607
+ nothing resolves a \`vault:\` reference for them. A real secret never
50608
+ needs to appear in a tool call \u2014 the launcher injects it into the
50609
+ tool's environment for you. A value you genuinely must pass literally
50610
+ (an account or customer ID) is a public identifier, not a secret, and
50611
+ belongs in plain config, not the vault. If a tool call is **blocked for
50612
+ containing a vaulted secret**, report that exact block message to the
50613
+ operator and stop \u2014 don't theorise about auth or tokens; it just means
50614
+ a stored value reached a tool argument.`;
50603
50615
  function renderFleetInvariants() {
50604
50616
  return [
50605
50617
  "<!--",
@@ -51110,6 +51122,11 @@ function buildHumanizerEnvVars(agentDir, agent) {
51110
51122
  const resolved = expanded.startsWith("/") ? expanded : resolve11(agentDir, expanded);
51111
51123
  return { HUMANIZER_VOICE_FILE: resolved };
51112
51124
  }
51125
+ function buildToolSearchEnvVars() {
51126
+ if (process.env.SWITCHROOM_DISABLE_TOOL_SEARCH === "1")
51127
+ return {};
51128
+ return { ENABLE_TOOL_SEARCH: process.env.SWITCHROOM_TOOL_SEARCH_MODE ?? "auto" };
51129
+ }
51113
51130
  var SWITCHROOM_OWNED_SETTINGS_KEYS = new Set([
51114
51131
  "permissions",
51115
51132
  "mcpServers",
@@ -51439,6 +51456,7 @@ function buildWorkspaceContext(args) {
51439
51456
  fallbackModelQ: agentConfig.fallback_model ? shellSingleQuote(agentConfig.fallback_model) : undefined,
51440
51457
  userEnvQuoted: (() => {
51441
51458
  const combined = {
51459
+ ...buildToolSearchEnvVars(),
51442
51460
  ...channelsToEnv(agentConfig),
51443
51461
  ...agentConfig.env ?? {},
51444
51462
  ...buildRepoEnvVars(name, agentDir, agentConfig),
@@ -51749,7 +51767,8 @@ function scaffoldAgent(name, agentConfigRaw, agentsDir, telegramConfig, switchro
51749
51767
  TELEGRAM_STATE_DIR: telegramStateDir,
51750
51768
  SWITCHROOM_CONFIG: resolvedConfigPath,
51751
51769
  SWITCHROOM_CLI_PATH: switchroomCliPath
51752
- }
51770
+ },
51771
+ alwaysLoad: true
51753
51772
  },
51754
51773
  "agent-config": {
51755
51774
  command: switchroomCliPath,
@@ -51757,7 +51776,8 @@ function scaffoldAgent(name, agentConfigRaw, agentsDir, telegramConfig, switchro
51757
51776
  env: {
51758
51777
  SWITCHROOM_AGENT_NAME: name,
51759
51778
  SWITCHROOM_CONFIG: resolvedConfigPath
51760
- }
51779
+ },
51780
+ alwaysLoad: true
51761
51781
  }
51762
51782
  };
51763
51783
  if (agentConfig.admin === true) {
@@ -52502,6 +52522,7 @@ function reconcileAgent(name, agentConfigRaw, agentsDir, telegramConfig, switchr
52502
52522
  fallbackModelQ: agentConfig.fallback_model ? shellSingleQuote(agentConfig.fallback_model) : undefined,
52503
52523
  userEnvQuoted: (() => {
52504
52524
  const combined = {
52525
+ ...buildToolSearchEnvVars(),
52505
52526
  ...channelsToEnv(agentConfig),
52506
52527
  ...agentConfig.env ?? {},
52507
52528
  ...buildRepoEnvVars(name, agentDir, agentConfig)
@@ -52784,7 +52805,8 @@ ${body}
52784
52805
  TELEGRAM_STATE_DIR: telegramStateDir,
52785
52806
  SWITCHROOM_CONFIG: resolvedConfigPath,
52786
52807
  SWITCHROOM_CLI_PATH: switchroomCliPath
52787
- }
52808
+ },
52809
+ alwaysLoad: true
52788
52810
  },
52789
52811
  "agent-config": {
52790
52812
  command: switchroomCliPath,
@@ -52792,7 +52814,8 @@ ${body}
52792
52814
  env: {
52793
52815
  SWITCHROOM_AGENT_NAME: name,
52794
52816
  SWITCHROOM_CONFIG: resolvedConfigPath
52795
- }
52817
+ },
52818
+ alwaysLoad: true
52796
52819
  }
52797
52820
  };
52798
52821
  if (agentConfig.admin === true) {
@@ -64068,6 +64091,15 @@ import { existsSync as existsSync37 } from "node:fs";
64068
64091
 
64069
64092
  // src/vault/doctor.ts
64070
64093
  var SENSITIVE_KEY_RE = /oauth(?![a-zA-Z])|token(?![a-zA-Z])|secret(?![a-zA-Z])|api[-_]?key(?![a-zA-Z])|password(?![a-zA-Z])/i;
64094
+ function looksLikeIdentifierValue(value) {
64095
+ const v = value.trim();
64096
+ if (v.length < 8 || v.length > 24)
64097
+ return false;
64098
+ if (!/^[0-9][0-9 -]*[0-9]$/.test(v))
64099
+ return false;
64100
+ const digitCount = v.replace(/[^0-9]/g, "").length;
64101
+ return digitCount >= 8;
64102
+ }
64071
64103
  function analyseVaultHealth(input) {
64072
64104
  const diagnostics = [];
64073
64105
  if (input.brokerConfigured && input.brokerRunning === false) {
@@ -64123,6 +64155,23 @@ ${keyList}`,
64123
64155
  fix: "Consider `switchroom vault set <key> --allow <agent>` to restrict access"
64124
64156
  });
64125
64157
  }
64158
+ const identifierKeys = [];
64159
+ for (const [keyName, entry] of Object.entries(input.vaultKeys)) {
64160
+ if (entry.looksLikeIdentifier)
64161
+ identifierKeys.push(keyName);
64162
+ }
64163
+ if (identifierKeys.length > 0) {
64164
+ const keyList = identifierKeys.map((k) => ` ${k}`).join(`
64165
+ `);
64166
+ diagnostics.push({
64167
+ level: "warn",
64168
+ check: "identifier-stored-as-secret",
64169
+ message: `${identifierKeys.length} vault key(s) hold an identifier-like value (digits only):
64170
+ ${keyList}
64171
+ ` + `If an agent must pass one of these in a tool call, the secret-guard hook will block it \u2014 it cannot tell a public ID from a secret.`,
64172
+ fix: "If the value is a public identifier (account/customer ID), remove it from the vault and pass it as plain config (e.g. hardcode it in the MCP launcher). If it is genuinely secret, ignore this."
64173
+ });
64174
+ }
64126
64175
  const unreferencedKeys = [];
64127
64176
  for (const keyName of Object.keys(input.vaultKeys)) {
64128
64177
  if (!referencedKeys.has(keyName)) {
@@ -64214,7 +64263,8 @@ function registerVaultDoctorCommand(vault, program3) {
64214
64263
  vaultKeys = {};
64215
64264
  for (const [name, entry] of Object.entries(entries)) {
64216
64265
  vaultKeys[name] = {
64217
- scope: "scope" in entry && entry.scope ? entry.scope : undefined
64266
+ scope: "scope" in entry && entry.scope ? entry.scope : undefined,
64267
+ looksLikeIdentifier: entry.kind === "string" ? looksLikeIdentifierValue(entry.value) : false
64218
64268
  };
64219
64269
  }
64220
64270
  } catch (err) {
@@ -77206,7 +77256,18 @@ function formatBytes(bytes) {
77206
77256
  return `${bytes.toLocaleString()} bytes`;
77207
77257
  }
77208
77258
  function estimateTokens(bytes) {
77209
- return Math.round(bytes / 4);
77259
+ return Math.round(bytes / 3.7);
77260
+ }
77261
+ function readMcpServerNames(agentDir) {
77262
+ const mcpPath = join64(agentDir, ".mcp.json");
77263
+ if (!existsSync64(mcpPath))
77264
+ return [];
77265
+ try {
77266
+ const parsed = JSON.parse(readFileSync56(mcpPath, "utf-8"));
77267
+ return Object.keys(parsed.mcpServers ?? {});
77268
+ } catch {
77269
+ return null;
77270
+ }
77210
77271
  }
77211
77272
  function sha256(content) {
77212
77273
  return createHash13("sha256").update(content).digest("hex").slice(0, 16);
@@ -77424,14 +77485,31 @@ function registerDebugCommand(program3) {
77424
77485
  const soulMdBytes = soulMdContent.length;
77425
77486
  const perTurnBytes = dynamicResult.concatenated.length;
77426
77487
  const userBytes = userMessage?.text.length ?? 0;
77427
- const totalBytes = stableBytes + perSessionBytes + claudeMdBytes + soulMdBytes + perTurnBytes + userBytes;
77428
- console.log(`Stable prefix: ${formatBytes(stableBytes).padEnd(20)} (cache-hot)`);
77488
+ const fleetDir = join64(agentsDir, "..", "fleet");
77489
+ const fleetInvPath = join64(fleetDir, "switchroom-invariants.md");
77490
+ const fleetClaudePath = join64(fleetDir, "CLAUDE.md");
77491
+ const fleetInvBytes = existsSync64(fleetInvPath) ? readFileSync56(fleetInvPath, "utf-8").length : 0;
77492
+ const fleetClaudeBytes = existsSync64(fleetClaudePath) ? readFileSync56(fleetClaudePath, "utf-8").length : 0;
77493
+ const fleetBytes = fleetInvBytes + fleetClaudeBytes;
77494
+ const totalBytes = stableBytes + perSessionBytes + claudeMdBytes + fleetBytes + perTurnBytes + userBytes;
77495
+ console.log(`Stable prefix: ${formatBytes(stableBytes).padEnd(20)} (cache-hot; includes SOUL.md ${soulMdBytes.toLocaleString()}B)`);
77429
77496
  console.log(`Per-session: ${formatBytes(perSessionBytes).padEnd(20)} (cache-warm until next session)`);
77430
- console.log(`CLAUDE.md: ${formatBytes(claudeMdBytes).padEnd(20)}`);
77431
- console.log(`SOUL.md: ${formatBytes(soulMdBytes).padEnd(20)}`);
77432
- console.log(`Per-turn: ${formatBytes(perTurnBytes).padEnd(20)} (never cached)`);
77497
+ console.log(`CLAUDE.md (cwd): ${formatBytes(claudeMdBytes).padEnd(20)} (cache-hot)`);
77498
+ console.log(`Fleet invariants: ${formatBytes(fleetBytes).padEnd(20)} (--add-dir, cache-hot)`);
77499
+ console.log(`Per-turn: ${formatBytes(perTurnBytes).padEnd(20)} (never cached \u2014 the real per-turn $ cost)`);
77433
77500
  console.log(`User message: ${formatBytes(userBytes).padEnd(20)}`);
77434
- console.log(`Total: ${formatBytes(totalBytes).padEnd(20)} (~${estimateTokens(totalBytes).toLocaleString()} tokens est.)`);
77501
+ console.log(`Authored text: ${formatBytes(totalBytes).padEnd(20)} (~${estimateTokens(totalBytes).toLocaleString()} tokens est.)`);
77502
+ const mcpServers = readMcpServerNames(agentDir);
77503
+ const mcpCount = mcpServers?.length ?? null;
77504
+ const mcpEstK = mcpCount != null ? mcpCount * 3 : null;
77505
+ const mcpLabel = mcpCount != null ? `${mcpCount} servers` : "(unreadable \u2014 agent-private .mcp.json)";
77506
+ const mcpEstLabel = mcpEstK != null ? `~${mcpEstK.toLocaleString()}k tok` : "~30k tok (audit est.)";
77507
+ console.log(`MCP tool surface: ${mcpLabel.padEnd(20)} (NOT counted above; ~3k tok/server \u2248 ${mcpEstLabel} \u2014 deferred under tool search)`);
77508
+ if (mcpServers && mcpServers.length > 0) {
77509
+ console.log(` [${mcpServers.join(", ")}]`);
77510
+ }
77511
+ const floorMcp = mcpEstK != null ? `~${mcpEstK.toLocaleString()}k` : "~30k";
77512
+ console.log(`Per-turn FLOOR: ${`~${estimateTokens(totalBytes).toLocaleString()} + ${floorMcp} MCP + ~13k CLI-base`.padEnd(20)} tokens est. (before the user msg, recall, or tool results)`);
77435
77513
  const stableCacheInput = stableResult.concatenated + progressGuidance + baseSystemPromptAppend;
77436
77514
  const stableHash = sha256(stableCacheInput);
77437
77515
  console.log(`Cache stable hash: sha256:${stableHash}`);
@@ -13946,6 +13946,7 @@ var profileFields = {
13946
13946
  memory: exports_external.object({
13947
13947
  collection: exports_external.string().optional(),
13948
13948
  auto_recall: exports_external.boolean().optional(),
13949
+ file: exports_external.boolean().optional(),
13949
13950
  isolation: exports_external.enum(["default", "strict"]).optional(),
13950
13951
  recall: exports_external.object({
13951
13952
  max_memories: exports_external.number().int().min(0).optional(),
@@ -11532,6 +11532,7 @@ var init_schema = __esm(() => {
11532
11532
  memory: exports_external.object({
11533
11533
  collection: exports_external.string().optional(),
11534
11534
  auto_recall: exports_external.boolean().optional(),
11535
+ file: exports_external.boolean().optional(),
11535
11536
  isolation: exports_external.enum(["default", "strict"]).optional(),
11536
11537
  recall: exports_external.object({
11537
11538
  max_memories: exports_external.number().int().min(0).optional(),
@@ -11532,6 +11532,7 @@ var init_schema = __esm(() => {
11532
11532
  memory: exports_external.object({
11533
11533
  collection: exports_external.string().optional(),
11534
11534
  auto_recall: exports_external.boolean().optional(),
11535
+ file: exports_external.boolean().optional(),
11535
11536
  isolation: exports_external.enum(["default", "strict"]).optional(),
11536
11537
  recall: exports_external.object({
11537
11538
  max_memories: exports_external.number().int().min(0).optional(),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "switchroom",
3
- "version": "0.14.75",
3
+ "version": "0.14.77",
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": {
@@ -11,23 +11,21 @@ Before answering anything non-trivial:
11
11
 
12
12
  1. Confirm who you are (see `IDENTITY.md` if present).
13
13
  2. Confirm who you're talking to (see `USER.md` if present).
14
- 3. Remember what matters — `MEMORY.md` is your long-term memory; the
15
- `memory/` subdirectory has daily notes (`memory/YYYY-MM-DD.md`) with
16
- recent context.
14
+ 3. Recall what matters — your memory is **Hindsight**: call
15
+ `mcp__hindsight__recall` when you need prior context, decisions, or who
16
+ someone is. (Full memory protocol is in your `CLAUDE.md`.)
17
17
  4. If tools are configured, `TOOLS.md` captures setup-specific notes
18
18
  (host paths, credentials, endpoints) that save you discovery work.
19
19
 
20
20
  Don't ask permission to read these files. They exist so you can use them.
21
21
 
22
- ## Memory discipline
22
+ ## Memory
23
23
 
24
- - **Short-term / recent context** `memory/YYYY-MM-DD.md` raw log-style
25
- notes of what happened today. Create the day's file when you need it.
26
- - **Long-term / curated** `MEMORY.md` — distilled learnings, decisions,
27
- preferences, identity of people you interact with.
28
- - **Lessons** → When you make a mistake or learn something worth keeping,
29
- write it down. Future-you is fresh every session. Files are your
30
- continuity.
24
+ Memory is **Hindsight**, not files `recall` to read, `sync_retain` to keep a
25
+ fact across sessions, `delete_document` to forget. Claude Code's built-in
26
+ file-based auto-memory is disabled for this agent; do not maintain a `MEMORY.md`
27
+ index or a `memory/` daily-notes directory. Your `CLAUDE.md` has the full
28
+ protocol.
31
29
 
32
30
  ## Safety defaults
33
31
 
@@ -55,8 +53,8 @@ Failed edits burn your own context and the user's patience. Verify first.
55
53
  tool exists.
56
54
  - Long-running commands: use the background flag and poll, rather than
57
55
  blocking.
58
- - `memory_search` and `memory_get` (when available) are your primary
59
- interface to workspace knowledge; use them before asking the user.
56
+ - `mcp__hindsight__recall` is your interface to past context and decisions;
57
+ recall before asking the user something you may already know.
60
58
 
61
59
  ## Working on code repositories
62
60
 
@@ -24035,6 +24035,7 @@ var init_schema = __esm(() => {
24035
24035
  memory: exports_external.object({
24036
24036
  collection: exports_external.string().optional(),
24037
24037
  auto_recall: exports_external.boolean().optional(),
24038
+ file: exports_external.boolean().optional(),
24038
24039
  isolation: exports_external.enum(["default", "strict"]).optional(),
24039
24040
  recall: exports_external.object({
24040
24041
  max_memories: exports_external.number().int().min(0).optional(),
@@ -52820,11 +52821,11 @@ function sweepStaleTurnActiveMarker(stateDir, opts) {
52820
52821
  }
52821
52822
 
52822
52823
  // ../src/build-info.ts
52823
- var VERSION = "0.14.75";
52824
- var COMMIT_SHA = "8c331b53";
52825
- var COMMIT_DATE = "2026-06-06T06:33:56Z";
52826
- var LATEST_PR = 2192;
52827
- var COMMITS_AHEAD_OF_TAG = 0;
52824
+ var VERSION = "0.14.77";
52825
+ var COMMIT_SHA = "2473dbbf";
52826
+ var COMMIT_DATE = "2026-06-07T00:02:11+10:00";
52827
+ var LATEST_PR = null;
52828
+ var COMMITS_AHEAD_OF_TAG = 4;
52828
52829
 
52829
52830
  // gateway/boot-version.ts
52830
52831
  function formatRelativeAgo(iso) {
@@ -194,10 +194,19 @@ async function main() {
194
194
  }
195
195
  const hit = scanToolInput(toolInput, vaultValues)
196
196
  if (hit) {
197
+ // The reason is read by the model. It must be CORRECT and actionable:
198
+ // earlier versions said "reference it as vault:<key> instead", which
199
+ // suggested a `vault:` resolver that does not exist for tool arguments
200
+ // — sending agents (and operators) chasing a phantom mechanism. State
201
+ // the two real resolutions instead, and do NOT imply a resolver.
197
202
  process.stdout.write(
198
203
  JSON.stringify({
199
204
  decision: 'block',
200
- reason: `tool_input contains a vaulted secret — reference it as vault:${hit.key} instead`,
205
+ reason:
206
+ `Blocked: this tool input contains the value of vault secret '${hit.key}'. ` +
207
+ `Secrets must never appear in a tool call. ` +
208
+ `If '${hit.key}' is a real secret, do not pass it as an argument — the launcher injects it into the tool's environment, so you never type it. ` +
209
+ `If '${hit.key}' is actually a public identifier (e.g. an account or customer ID that must appear in the call), it should not be in the vault — ask the operator to move it to plain config.`,
201
210
  }),
202
211
  )
203
212
  process.exit(0)
@@ -132,7 +132,13 @@ describe('secret-guard-pretool.mjs (broker-direct)', () => {
132
132
  })
133
133
  expect(r.status).toBe(0)
134
134
  expect(r.stdout).toContain('"decision":"block"')
135
- expect(r.stdout).toContain('vault:gh-token')
135
+ // The reason must name the offending key and explain the real fix.
136
+ expect(r.stdout).toContain('gh-token')
137
+ expect(r.stdout).toContain('Secrets must never appear in a tool call')
138
+ // It must NOT resurrect the old, misleading "reference it as vault:<key>"
139
+ // suggestion — there is no vault: resolver for tool arguments, and that
140
+ // advice sent agents down a dead end (see the hook's block-reason note).
141
+ expect(r.stdout).not.toContain('reference it as vault:')
136
142
  })
137
143
 
138
144
  it('allows when tool_input contains no vaulted value', async () => {