switchroom 0.19.43 → 0.19.44

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.
@@ -4030,7 +4030,7 @@ var init_observation_scopes = __esm(() => {
4030
4030
  });
4031
4031
 
4032
4032
  // src/config/schema.ts
4033
- var CodeRepoEntrySchema, AgentBindMountSchema, HttpDiffPollSchema, PollSpecSchema, TelegramMessageActionSchema, WebhookActionSchema, ActionSpecSchema, ScheduleEntrySchema, AgentSoulSchema, AgentToolsSchema, ObservationScopesSchema, ObservationScopeStrategySchema, AgentMemorySchema, HookEntrySchema, AgentHooksSchema, SubagentSchema, SessionSchema, SessionContinuitySchema, webhookDispatchRule, TelegramChannelSchema, ChannelsSchema, TIMEZONE_REGEX, ApproverIdSchema, GoogleWorkspaceTierSchema, GoogleWorkspaceConfigSchema, LiteLLMConfigSchema, HindsightPerOpLlmSchema, HindsightConfigSchema, MicrosoftWorkspaceConfigSchema, NotionWorkspaceConfigSchema, AgentGoogleWorkspaceConfigSchema, MicrosoftAccountEmailSchema, MicrosoftToolTokenSchema, MicrosoftAccountBindingSchema, AgentMicrosoftWorkspaceConfigSchema, AgentNotionWorkspaceConfigSchema, ReactionsSchema, ReactionDispatchSchema, releaseBlockFields, ReleaseBlock, RootReleaseBlock, NetworkIsolationSchema, servesField, knowsField, profileFields, ProfileSchema, _omitExtends, defaultsFields, AgentDefaultsSchema, AgentSchema, TelegramConfigSchema, MemoryBackendConfigSchema, VaultConfigSchema, QuotaConfigSchema, AutoReleaseCheckSchema, HostControlConfigSchema, WebServiceConfigSchema, FleetHealthConfigSchema, HostdConfigSchema, CronEgressSchema, CronConfigSchema, UserSchema, SwitchroomConfigSchema;
4033
+ var CodeRepoEntrySchema, AgentBindMountSchema, HttpDiffPollSchema, PollSpecSchema, TelegramMessageActionSchema, WebhookActionSchema, ActionSpecSchema, ScheduleEntrySchema, AgentSoulSchema, AgentToolsSchema, ObservationScopesSchema, ObservationScopeStrategySchema, AntiConfabulationDirectiveSchema, AgentMemorySchema, HookEntrySchema, AgentHooksSchema, SubagentSchema, SessionSchema, SessionContinuitySchema, webhookDispatchRule, TelegramChannelSchema, ChannelsSchema, TIMEZONE_REGEX, ApproverIdSchema, GoogleWorkspaceTierSchema, GoogleWorkspaceConfigSchema, LiteLLMConfigSchema, HindsightPerOpLlmSchema, HindsightConfigSchema, MicrosoftWorkspaceConfigSchema, NotionWorkspaceConfigSchema, AgentGoogleWorkspaceConfigSchema, MicrosoftAccountEmailSchema, MicrosoftToolTokenSchema, MicrosoftAccountBindingSchema, AgentMicrosoftWorkspaceConfigSchema, AgentNotionWorkspaceConfigSchema, ReactionsSchema, ReactionDispatchSchema, releaseBlockFields, ReleaseBlock, RootReleaseBlock, NetworkIsolationSchema, servesField, knowsField, profileFields, ProfileSchema, _omitExtends, defaultsFields, AgentDefaultsSchema, AgentSchema, TelegramConfigSchema, MemoryBackendConfigSchema, VaultConfigSchema, QuotaConfigSchema, AutoReleaseCheckSchema, HostControlConfigSchema, WebServiceConfigSchema, FleetHealthConfigSchema, HostdConfigSchema, CronEgressSchema, CronConfigSchema, UserSchema, SwitchroomConfigSchema;
4034
4034
  var init_schema = __esm(() => {
4035
4035
  init_zod();
4036
4036
  init_observation_scopes();
@@ -4147,6 +4147,7 @@ var init_schema = __esm(() => {
4147
4147
  }).optional();
4148
4148
  ObservationScopesSchema = exports_external.enum(OBSERVATION_SCOPES).optional().describe("Operator PIN for the per-row observation scope, overriding the " + 'default `curated` strategy (see docs/configuration.md). "shared" ' + "forces Hindsight's consolidation to write EVERY observation into ONE " + "global untagged scope — what several agents pooling one bank want so " + "their observations merge rather than sitting in parallel silos. " + '"combined" is the pin form of opting out (restores the pre-#4035 ' + "engine default). UNSET does NOT mean 'nothing on the wire': the " + "`curated` strategy still computes a scope per retain. Applies to " + "every retain path (Stop hook, " + "sidechain, boot reconcile, queue drain, backfill, session-handoff " + "mirror) and is carried on the queued payload, so a retain that fails " + "now and drains later still lands in this scope. Accepted values: " + `${OBSERVATION_SCOPES.join(", ")}. ` + "Cascade: override (per-agent wins over default).");
4149
4149
  ObservationScopeStrategySchema = exports_external.enum(OBSERVATION_SCOPE_STRATEGIES).optional().describe("Per-retain strategy that decides how the observation scope is " + 'computed when no `observation_scopes` pin is set. "curated" ' + "(the default since #4035) strips volatile per-session provenance " + "tags from each retain's consolidation scope — keeping the stable " + "semantic ones on the source fact — so an agent's observations dedup " + "and merge across sessions instead of pocketing one-per-session. " + '"shared" sends every retain to the one global untagged scope. ' + '"combined" / "off" opt OUT: no per-row scope goes on the wire, ' + "restoring the pre-feature engine default (byte-identical to an " + "unconfigured client). OMITTED BY DEFAULT: unset leaves the plugin's " + "own default (`curated`) in force. A manual `observation_scopes` pin " + "wins over this strategy. Accepted values: " + `${OBSERVATION_SCOPE_STRATEGIES.join(", ")}. ` + "Cascade: override (per-agent wins over default).");
4150
+ AntiConfabulationDirectiveSchema = exports_external.union([exports_external.boolean(), exports_external.string().min(1)]).optional().describe("The seeded anti-confabulation directive: reflect has no relevance " + "floor (`min_scores` is a /recall-only parameter), so a bank holding " + "nothing relevant still yields a confident answer, and a question that " + "presupposes a decision reliably produces one. Directives ARE applied " + "during reflect, so this is the lever. Unset or true (the default) " + "seeds and maintains switchroom's own text; false disables seeding and " + "leaves any existing directive untouched; a string is operator-authored " + "text that wins outright. Switchroom only ever upgrades directive text " + "byte-equal to a default it shipped, so editing the directive in the " + "bank is also a permanent override. " + "Cascade: override (per-agent wins over default).");
4150
4151
  AgentMemorySchema = exports_external.object({
4151
4152
  collection: exports_external.string().describe("Hindsight collection name for this agent"),
4152
4153
  auto_recall: exports_external.boolean().default(true).describe("Auto-search memories before each response"),
@@ -4182,6 +4183,7 @@ var init_schema = __esm(() => {
4182
4183
  empathy: exports_external.number().int().min(1).max(5).optional().describe("How much the bank weights emotional/relational context (1-5; engine default 3).")
4183
4184
  }).optional().describe("Personality traits (1-5 each) steering how this bank frames recall, " + "reflect, and observation synthesis — a coach leans empathy-high, a " + "lawyer/analyst leans skepticism/literalism-high. Maps to the engine's " + "flat `disposition_skepticism`/`_literalism`/`_empathy` fields. " + "Cascade: per-key merge (an agent overrides individual traits and " + "inherits the rest, matching `recall`)."),
4184
4185
  directive_capture_nudge: exports_external.boolean().optional().describe("Deterministic directive-capture nudge (issue #2848 Stage B). When " + "on (switchroom default true — Stage A measured a ~55% miss rate on " + "durable corrections), the auto-recall hook regex-detects correction " + '/ standing-rule-shaped inbound ("always/never …", "from now on …", ' + `"stop doing …", a stated preference, "that's wrong, it's …") and ` + "appends a terse advisory to the turn's context telling the model to " + "persist the rule with mcp__hindsight__create_directive if it IS " + "durable. Detection is pure regex — the model does the judgment " + "in-session and calls create_directive itself (no model callsite, no " + "silent hook-side write). Set false to disable per-agent. " + "Cascade: override (per-agent wins over default)."),
4186
+ anti_confabulation_directive: AntiConfabulationDirectiveSchema,
4185
4187
  observation_scopes: ObservationScopesSchema,
4186
4188
  observation_scope_strategy: ObservationScopeStrategySchema,
4187
4189
  recall: exports_external.object({
@@ -4452,7 +4454,7 @@ var init_schema = __esm(() => {
4452
4454
  reflect: HindsightPerOpLlmSchema.optional().describe("Per-op override for the `reflect` LLM op (synthesis / mental-model " + "refresh). Emits `HINDSIGHT_API_REFLECT_LLM_*`. Absent → uses global."),
4453
4455
  consolidation: HindsightPerOpLlmSchema.optional().describe("Per-op override for the `consolidation` LLM op (background memory " + "merge). Emits `HINDSIGHT_API_CONSOLIDATION_LLM_*`. Absent → global.")
4454
4456
  }).optional().describe("LLM knob for the hindsight container. The flat `provider`/`model` set " + "the global default (backward-compatible); optional `retain`/`reflect`/" + "`consolidation` blocks override individual ops. All fields optional; " + "unset fields fall back to the hard-coded defaults."),
4455
- env: exports_external.record(exports_external.union([exports_external.string(), exports_external.number(), exports_external.boolean()])).optional().describe("Operator overrides for switchroom's capability-gated Hindsight " + "performance defaults. Only the keys switchroom actually manages are " + "honoured (`HINDSIGHT_PERF_ENV_KEYS` in " + "src/setup/hindsight-perf-defaults.ts: RERANKER_LOCAL_FP16, " + "RERANKER_LOCAL_BATCH_SIZE, LLM_MAX_CONCURRENT, " + "RETAIN/CONSOLIDATION_LLM_MAX_CONCURRENT, LLM_STRICT_SCHEMA, " + "LLM_MAX_RETRIES, CONSOLIDATION_LLM_PARALLELISM, " + "MAX_OBSERVATIONS_PER_SCOPE, " + "RECALL_MAX_CANDIDATES_PER_SOURCE, LINK_EXPANSION_PER_ENTITY_LIMIT, " + "LINK_EXPANSION_TIMEOUT, LLM_REASONING_EFFORT, " + "RERANKER_LOCAL_BUCKET_BATCHING, RERANKER_MAX_CANDIDATES, " + "RERANKER_LOCAL_MAX_CONCURRENT, RECALL_MAX_CONCURRENT, " + "REFLECT_WALL_TIMEOUT, WORKER_CONSOLIDATION_RESERVED_SLOTS, " + "WORKER_CONSOLIDATION_SLOT_LIMIT, " + "CONSOLIDATION_MAX_MEMORIES_PER_ROUND, GRAPH_SEED_MIN_SIMILARITY, " + "LLM_SUPPORTS_MAX_ITEMS, RECENCY_DECAY_FUNCTION, " + "RECENCY_DECAY_HALFLIFE_DAYS — switchroom defaults recall's recency " + "curve to `exponential` with a 30-day half-life so a fact retained " + "today outranks a stale one, instead of upstream's near-flat " + "linear/365-day window), the override-only keys " + "switchroom manages but ships NO default for " + "(`HINDSIGHT_PERF_OVERRIDE_ONLY_KEYS`: " + "HINDSIGHT_API_WORKER_CONSOLIDATION_BANK_PRIORITY — a per-deployment " + "`bank-pattern:priority,...` map; unset means upstream's flat " + "created_at FIFO across banks; and " + "HINDSIGHT_CE_DECISIVE_RELATIVE_GAP — the rollback knob for " + "switchroom's CE-saturation damping patch, a float; >= ~0.65 backs the " + "damping out entirely, unset means the patch's own derived gap; and " + "HINDSIGHT_API_RECENCY_DECAY_LINEAR_WINDOW_DAYS — only read when the " + "decay function is `linear`, so switchroom ships no default for it but " + "still honours an operator who flips the function back; and " + "HINDSIGHT_API_WORKER_MAX_SLOTS — the worker poller's TOTAL in-flight " + "task budget, the pool WORKER_CONSOLIDATION_RESERVED_SLOTS reserves out of; " + "unset means upstream's own default; and " + "HINDSIGHT_API_WORKER_RETAIN_RESERVED_SLOTS — the reserved slot FLOOR for the " + "retain (memory write) lane, carved from that same total; unset means " + "upstream's own 0, i.e. no floor and retain competes for the shared " + "pool; and " + "the pre-0.8.6 name HINDSIGHT_API_WORKER_<TYPE>_MAX_SLOTS is still " + "accepted for every operation type and normalised to " + "..._RESERVED_SLOTS on the way in, because setting both names for " + "one type is a hard boot failure in the engine and switchroom " + "therefore emits only the canonical name; and " + "HINDSIGHT_API_SEMANTIC_MIN_SIMILARITY — the cosine-similarity floor a " + "candidate must reach to be returned by the semantic retrieval arm at " + "all (0..1); unset means upstream's own 0.3, and where it should sit " + "depends on the bank's embedding model and phrasing diversity, so " + "switchroom ships no opinion; and " + "HINDSIGHT_MCP_RECALL_BUDGET_MODE — the rollback knob for switchroom's " + "mcp-recall-token-budget image patch; `legacy` restores upstream's " + "exact pre-patch recall returns, anything else or unset is the " + "honest-envelope mode; and " + "HINDSIGHT_API_LLM_TEMPERATURE_REFLECT — upstream's own per-op reflect " + "temperature knob, made live by switchroom's reflect-temperature image " + "patch; a float, or `none` to omit the kwarg (provider default, " + "upstream's accidental pre-patch behaviour); unset means the image's " + "baked default 0.1), plus the " + "embedded-PostgreSQL (pg0) sizing keys switchroom manages in " + "src/setup/hindsight-pg-defaults.ts (`HINDSIGHT_PG_ENV_KEYS`: " + "SWITCHROOM_HINDSIGHT_PG_EFFECTIVE_CACHE_SIZE, " + "SWITCHROOM_HINDSIGHT_PG_SHARED_BUFFERS — a postgres size string such " + "as `4GB`, or the sentinel `off` to leave pg0's own default for that " + "one knob). A value set here " + "REPLACES switchroom's default and is emitted even when the gating " + "capability is absent, so an operator can always force a knob. Other " + "`HINDSIGHT_API_*` keys are deliberately IGNORED — a blanket " + "passthrough would collide with the vars startHindsight() derives " + "itself (HINDSIGHT_API_PORT, the retain token/deadline budget).")
4457
+ env: exports_external.record(exports_external.union([exports_external.string(), exports_external.number(), exports_external.boolean()])).optional().describe("Operator overrides for switchroom's capability-gated Hindsight " + "performance defaults. Only the keys switchroom actually manages are " + "honoured (`HINDSIGHT_PERF_ENV_KEYS` in " + "src/setup/hindsight-perf-defaults.ts: RERANKER_LOCAL_FP16, " + "RERANKER_LOCAL_BATCH_SIZE, LLM_MAX_CONCURRENT, " + "RETAIN/CONSOLIDATION_LLM_MAX_CONCURRENT, LLM_STRICT_SCHEMA, " + "LLM_MAX_RETRIES, CONSOLIDATION_LLM_PARALLELISM, " + "MAX_OBSERVATIONS_PER_SCOPE, " + "RECALL_MAX_CANDIDATES_PER_SOURCE, LINK_EXPANSION_PER_ENTITY_LIMIT, " + "LINK_EXPANSION_TIMEOUT, LLM_REASONING_EFFORT, " + "RERANKER_LOCAL_BUCKET_BATCHING, RERANKER_MAX_CANDIDATES, " + "RERANKER_LOCAL_MAX_CONCURRENT, RECALL_MAX_CONCURRENT, " + "REFLECT_WALL_TIMEOUT, WORKER_CONSOLIDATION_RESERVED_SLOTS, " + "WORKER_CONSOLIDATION_SLOT_LIMIT, " + "CONSOLIDATION_MAX_MEMORIES_PER_ROUND, GRAPH_SEED_MIN_SIMILARITY, " + "LLM_SUPPORTS_MAX_ITEMS, RECENCY_DECAY_FUNCTION, " + "RECENCY_DECAY_HALFLIFE_DAYS — switchroom defaults recall's recency " + "curve to `exponential` with a 30-day half-life so a fact retained " + "today outranks a stale one, instead of upstream's near-flat " + "linear/365-day window), the override-only keys " + "switchroom manages but ships NO default for " + "(`HINDSIGHT_PERF_OVERRIDE_ONLY_KEYS`: " + "HINDSIGHT_API_WORKER_CONSOLIDATION_BANK_PRIORITY — a per-deployment " + "`bank-pattern:priority,...` map; unset means upstream's flat " + "created_at FIFO across banks; and " + "HINDSIGHT_CE_DECISIVE_RELATIVE_GAP — the rollback knob for " + "switchroom's CE-saturation damping patch, a float; >= ~0.65 backs the " + "damping out entirely, unset means the patch's own derived gap; and " + "HINDSIGHT_API_RECENCY_DECAY_LINEAR_WINDOW_DAYS — only read when the " + "decay function is `linear`, so switchroom ships no default for it but " + "still honours an operator who flips the function back; and " + "HINDSIGHT_API_WORKER_MAX_SLOTS — the worker poller's TOTAL in-flight " + "task budget, the pool WORKER_CONSOLIDATION_RESERVED_SLOTS reserves out of; " + "unset means upstream's own default; and " + "HINDSIGHT_API_WORKER_RETAIN_RESERVED_SLOTS — the reserved slot FLOOR for the " + "retain (memory write) lane, carved from that same total; unset means " + "upstream's own 0, i.e. no floor and retain competes for the shared " + "pool; and " + "the pre-0.8.6 name HINDSIGHT_API_WORKER_<TYPE>_MAX_SLOTS is still " + "accepted for every operation type and normalised to " + "..._RESERVED_SLOTS on the way in, because setting both names for " + "one type is a hard boot failure in the engine and switchroom " + "therefore emits only the canonical name; and " + "HINDSIGHT_API_SEMANTIC_MIN_SIMILARITY — the cosine-similarity floor a " + "candidate must reach to be returned by the semantic retrieval arm at " + "all (0..1); unset means upstream's own 0.3, and where it should sit " + "depends on the bank's embedding model and phrasing diversity, so " + "switchroom ships no opinion; and " + "HINDSIGHT_MCP_RECALL_BUDGET_MODE — the rollback knob for switchroom's " + "mcp-recall-token-budget image patch; `legacy` restores upstream's " + "exact pre-patch recall returns, anything else or unset is the " + "honest-envelope mode; and " + "HINDSIGHT_API_LLM_TEMPERATURE_REFLECT — upstream's own per-op reflect " + "temperature knob, made live by switchroom's reflect-temperature image " + "patch; a float, or `none` to omit the kwarg (provider default, " + "upstream's accidental pre-patch behaviour); unset means the image's " + "baked default 0.1; and " + "HINDSIGHT_API_CONSOLIDATION_RECALL_MAX_CONCURRENT — the background " + "half of switchroom's recall-admission split (#3660): of the " + "RECALL_MAX_CONCURRENT admission slots, at most this many may be held " + "by background consolidation recalls at once, so foreground per-turn " + "recall always keeps the remainder; must be >= 1 and strictly less " + "than RECALL_MAX_CONCURRENT or the engine refuses to boot; unset " + "means the image's derived default min(2, RECALL_MAX_CONCURRENT - 1); " + "1 biases hard toward the interactive lane while a consolidation " + "backlog drains), plus the " + "embedded-PostgreSQL (pg0) sizing keys switchroom manages in " + "src/setup/hindsight-pg-defaults.ts (`HINDSIGHT_PG_ENV_KEYS`: " + "SWITCHROOM_HINDSIGHT_PG_EFFECTIVE_CACHE_SIZE, " + "SWITCHROOM_HINDSIGHT_PG_SHARED_BUFFERS — a postgres size string such " + "as `4GB`, or the sentinel `off` to leave pg0's own default for that " + "one knob). A value set here " + "REPLACES switchroom's default and is emitted even when the gating " + "capability is absent, so an operator can always force a knob. Other " + "`HINDSIGHT_API_*` keys are deliberately IGNORED — a blanket " + "passthrough would collide with the vars startHindsight() derives " + "itself (HINDSIGHT_API_PORT, the retain token/deadline budget).")
4456
4458
  });
4457
4459
  MicrosoftWorkspaceConfigSchema = exports_external.object({
4458
4460
  microsoft_client_id: exports_external.string().min(1).optional().describe("Microsoft OAuth application (client) ID from Entra portal " + "(literal string or vault reference e.g. " + "'vault:microsoft-oauth-client-id'). OPTIONAL — omit it to use " + "switchroom's shipped default Microsoft app (zero-config). " + "Set it only to bring your own Entra app (BYO)."),
@@ -4586,6 +4588,7 @@ var init_schema = __esm(() => {
4586
4588
  isolation: exports_external.enum(["default", "strict"]).optional(),
4587
4589
  profile: exports_external.string().optional(),
4588
4590
  directive_capture_nudge: exports_external.boolean().optional(),
4591
+ anti_confabulation_directive: AntiConfabulationDirectiveSchema,
4589
4592
  observation_scopes: ObservationScopesSchema,
4590
4593
  observation_scope_strategy: ObservationScopeStrategySchema,
4591
4594
  recall: exports_external.object({
@@ -5075,7 +5078,7 @@ function parseSocketIdentity(socketPath, spec) {
5075
5078
 
5076
5079
  // src/vault/broker/peercred.ts
5077
5080
  import { execFileSync } from "node:child_process";
5078
- import { readFileSync, readlinkSync, fstatSync } from "node:fs";
5081
+ import { readFileSync as readFileSync2, readlinkSync, fstatSync } from "node:fs";
5079
5082
  function socketPathToIdentity(socketPath) {
5080
5083
  return parseSocketIdentity(socketPath, {
5081
5084
  patterns: [SOCKET_PATH_AGENT_RE, SOCKET_PATH_AGENT_SUBDIR_RE],
@@ -5156,7 +5159,7 @@ function findClientPidByServerInode(rows, socketPath, serverInode) {
5156
5159
  }
5157
5160
  function readUid(pid) {
5158
5161
  try {
5159
- const status = readFileSync(`/proc/${pid}/status`, "utf8");
5162
+ const status = readFileSync2(`/proc/${pid}/status`, "utf8");
5160
5163
  const m = status.match(/^Uid:\s+(\d+)/m);
5161
5164
  if (!m)
5162
5165
  return null;
@@ -5174,7 +5177,7 @@ function readExe(pid) {
5174
5177
  }
5175
5178
  function readSystemdUnit(pid) {
5176
5179
  try {
5177
- const content = readFileSync(`/proc/${pid}/cgroup`, "utf8");
5180
+ const content = readFileSync2(`/proc/${pid}/cgroup`, "utf8");
5178
5181
  const lines = content.split(`
5179
5182
  `);
5180
5183
  for (const line of lines) {
@@ -12691,8 +12694,8 @@ var require_dist = __commonJS((exports) => {
12691
12694
  });
12692
12695
 
12693
12696
  // src/config/paths.ts
12694
- import { existsSync } from "node:fs";
12695
- import { resolve } from "node:path";
12697
+ import { existsSync as existsSync2 } from "node:fs";
12698
+ import { resolve as resolve2 } from "node:path";
12696
12699
  function home() {
12697
12700
  return process.env.HOME ?? "/root";
12698
12701
  }
@@ -12707,12 +12710,12 @@ function resolveDualPath(pathStr) {
12707
12710
  const h = home();
12708
12711
  if (pathStr.startsWith("~/")) {
12709
12712
  const rest = pathStr.slice(2);
12710
- const absolute = resolve(h, rest);
12713
+ const absolute = resolve2(h, rest);
12711
12714
  if (rest.startsWith(`${DEFAULT_STATE_DIR}/`)) {
12712
12715
  const frag = rest.slice(DEFAULT_STATE_DIR.length + 1);
12713
- if (!existsSync(absolute)) {
12714
- const legacy = resolve(h, LEGACY_STATE_DIR, frag);
12715
- if (existsSync(legacy)) {
12716
+ if (!existsSync2(absolute)) {
12717
+ const legacy = resolve2(h, LEGACY_STATE_DIR, frag);
12718
+ if (existsSync2(legacy)) {
12716
12719
  warnLegacyStateOnce(legacy);
12717
12720
  return legacy;
12718
12721
  }
@@ -12720,7 +12723,7 @@ function resolveDualPath(pathStr) {
12720
12723
  }
12721
12724
  return absolute;
12722
12725
  }
12723
- return resolve(pathStr);
12726
+ return resolve2(pathStr);
12724
12727
  }
12725
12728
  var DEFAULT_STATE_DIR = ".switchroom", LEGACY_STATE_DIR = ".clerk", _legacyStateWarned = false;
12726
12729
  var init_paths = () => {};
@@ -12737,8 +12740,8 @@ var init_overlay_schema = __esm(() => {
12737
12740
  });
12738
12741
 
12739
12742
  // src/config/overlay-loader.ts
12740
- import { existsSync as existsSync2, readFileSync as readFileSync2, readdirSync, statSync } from "node:fs";
12741
- import { basename, resolve as resolve2 } from "node:path";
12743
+ import { existsSync as existsSync3, readFileSync as readFileSync3, readdirSync, statSync } from "node:fs";
12744
+ import { basename, resolve as resolve3 } from "node:path";
12742
12745
  function deriveOverlayTitle(raw, fileName) {
12743
12746
  const titleFromComment = raw.match(/^#[^\S\n]*name:[^\S\n]*(\S.*?)[^\S\n]*$/m)?.[1];
12744
12747
  if (titleFromComment)
@@ -12750,10 +12753,10 @@ function deriveOverlayTitle(raw, fileName) {
12750
12753
  }
12751
12754
  function overlayDirFor(agentName, subdir) {
12752
12755
  const base = resolveDualPath(`~/.switchroom/agents/${agentName}/${subdir}`);
12753
- return resolve2(base);
12756
+ return resolve3(base);
12754
12757
  }
12755
12758
  function listYamlFiles(dir) {
12756
- if (!existsSync2(dir))
12759
+ if (!existsSync3(dir))
12757
12760
  return [];
12758
12761
  let entries;
12759
12762
  try {
@@ -12765,7 +12768,7 @@ function listYamlFiles(dir) {
12765
12768
  for (const name of entries) {
12766
12769
  if (!/\.ya?ml$/i.test(name))
12767
12770
  continue;
12768
- const full = resolve2(dir, name);
12771
+ const full = resolve3(dir, name);
12769
12772
  try {
12770
12773
  if (statSync(full).isFile())
12771
12774
  out.push(full);
@@ -12801,7 +12804,7 @@ function applyAgentOverlays(config) {
12801
12804
  const merged = [...agentCfg.schedule ?? []];
12802
12805
  for (const file of files) {
12803
12806
  try {
12804
- const raw = readFileSync2(file, "utf-8");
12807
+ const raw = readFileSync3(file, "utf-8");
12805
12808
  const parsed = import_yaml.parse(raw);
12806
12809
  const doc = OverlayDocSchema.parse(parsed);
12807
12810
  const title = deriveOverlayTitle(raw, basename(file));
@@ -12842,7 +12845,7 @@ function applyAgentOverlays(config) {
12842
12845
  const seen = new Set(merged);
12843
12846
  for (const file of skillFiles) {
12844
12847
  try {
12845
- const raw = readFileSync2(file, "utf-8");
12848
+ const raw = readFileSync3(file, "utf-8");
12846
12849
  const parsed = import_yaml.parse(raw);
12847
12850
  const doc = OverlayDocSchema.parse(parsed);
12848
12851
  for (const skillName of doc.skills ?? []) {
@@ -12917,9 +12920,9 @@ __export(exports_loader, {
12917
12920
  findConfigFile: () => findConfigFile,
12918
12921
  ConfigError: () => ConfigError
12919
12922
  });
12920
- import { readFileSync as readFileSync3, existsSync as existsSync3 } from "node:fs";
12923
+ import { readFileSync as readFileSync4, existsSync as existsSync4 } from "node:fs";
12921
12924
  import { homedir } from "node:os";
12922
- import { resolve as resolve3 } from "node:path";
12925
+ import { resolve as resolve4 } from "node:path";
12923
12926
  function formatZodErrors(error) {
12924
12927
  return error.errors.map((e) => {
12925
12928
  const path = e.path.join(".");
@@ -12967,24 +12970,24 @@ function coerceLegacyGoogleWorkspaceKeys(parsed, filePath) {
12967
12970
  function findConfigFile(startDir) {
12968
12971
  const envPath = process.env.SWITCHROOM_CONFIG;
12969
12972
  const home2 = homedir();
12970
- const userDir = resolve3(home2, ".switchroom");
12973
+ const userDir = resolve4(home2, ".switchroom");
12971
12974
  const searchPaths = [
12972
- envPath ? resolve3(envPath) : null,
12973
- startDir ? resolve3(startDir, "switchroom.yaml") : null,
12974
- startDir ? resolve3(startDir, "switchroom.yml") : null,
12975
- startDir ? resolve3(startDir, "clerk.yaml") : null,
12976
- startDir ? resolve3(startDir, "clerk.yml") : null,
12977
- resolve3(process.cwd(), "switchroom.yaml"),
12978
- resolve3(process.cwd(), "switchroom.yml"),
12979
- resolve3(process.cwd(), "clerk.yaml"),
12980
- resolve3(process.cwd(), "clerk.yml"),
12981
- resolve3(userDir, "switchroom.yaml"),
12982
- resolve3(userDir, "switchroom.yml"),
12983
- resolve3(userDir, "clerk.yaml"),
12984
- resolve3(userDir, "clerk.yml")
12975
+ envPath ? resolve4(envPath) : null,
12976
+ startDir ? resolve4(startDir, "switchroom.yaml") : null,
12977
+ startDir ? resolve4(startDir, "switchroom.yml") : null,
12978
+ startDir ? resolve4(startDir, "clerk.yaml") : null,
12979
+ startDir ? resolve4(startDir, "clerk.yml") : null,
12980
+ resolve4(process.cwd(), "switchroom.yaml"),
12981
+ resolve4(process.cwd(), "switchroom.yml"),
12982
+ resolve4(process.cwd(), "clerk.yaml"),
12983
+ resolve4(process.cwd(), "clerk.yml"),
12984
+ resolve4(userDir, "switchroom.yaml"),
12985
+ resolve4(userDir, "switchroom.yml"),
12986
+ resolve4(userDir, "clerk.yaml"),
12987
+ resolve4(userDir, "clerk.yml")
12985
12988
  ].filter(Boolean);
12986
12989
  for (const path of searchPaths) {
12987
- if (existsSync3(path)) {
12990
+ if (existsSync4(path)) {
12988
12991
  return path;
12989
12992
  }
12990
12993
  }
@@ -12992,12 +12995,12 @@ function findConfigFile(startDir) {
12992
12995
  }
12993
12996
  function loadConfig(configPath) {
12994
12997
  const filePath = configPath ?? findConfigFile();
12995
- if (!existsSync3(filePath)) {
12998
+ if (!existsSync4(filePath)) {
12996
12999
  throw new ConfigError(`Config file not found: ${filePath}`);
12997
13000
  }
12998
13001
  let raw;
12999
13002
  try {
13000
- raw = readFileSync3(filePath, "utf-8");
13003
+ raw = readFileSync4(filePath, "utf-8");
13001
13004
  } catch (err) {
13002
13005
  throw new ConfigError(`Failed to read config file: ${filePath}`, [
13003
13006
  ` ${err.message}`
@@ -18674,13 +18677,13 @@ var require_lib = __commonJS((exports, module) => {
18674
18677
 
18675
18678
  // src/vault/flock.ts
18676
18679
  import {
18677
- existsSync as existsSync5,
18680
+ existsSync as existsSync6,
18678
18681
  openSync as openSync2,
18679
18682
  closeSync as closeSync2,
18680
18683
  writeSync as writeSync2,
18681
18684
  fsyncSync as fsyncSync2,
18682
18685
  unlinkSync,
18683
- readFileSync as readFileSync5,
18686
+ readFileSync as readFileSync6,
18684
18687
  readdirSync as readdirSync3,
18685
18688
  rmdirSync,
18686
18689
  statSync as statSync3,
@@ -18691,7 +18694,7 @@ function lockPathFor(vaultPath) {
18691
18694
  }
18692
18695
  function readLockHolder(lockPath) {
18693
18696
  try {
18694
- const raw = readFileSync5(lockPath, "utf8");
18697
+ const raw = readFileSync6(lockPath, "utf8");
18695
18698
  const lines = raw.split(`
18696
18699
  `);
18697
18700
  const pid = Number.parseInt(lines[0] ?? "", 10);
@@ -18707,7 +18710,7 @@ function readLockHolder(lockPath) {
18707
18710
  }
18708
18711
  function pidIsLive(pid) {
18709
18712
  if (process.platform === "linux") {
18710
- return existsSync5(`/proc/${pid}`);
18713
+ return existsSync6(`/proc/${pid}`);
18711
18714
  }
18712
18715
  try {
18713
18716
  process.kill(pid, 0);
@@ -18743,8 +18746,8 @@ function pidStartTimeMs(pid) {
18743
18746
  if (process.platform !== "linux")
18744
18747
  return null;
18745
18748
  try {
18746
- const statLine = readFileSync5(`/proc/${pid}/stat`, "utf8");
18747
- const procStat = readFileSync5("/proc/stat", "utf8");
18749
+ const statLine = readFileSync6(`/proc/${pid}/stat`, "utf8");
18750
+ const procStat = readFileSync6("/proc/stat", "utf8");
18748
18751
  return parseProcStartTimeMs(statLine, procStat, Date.now());
18749
18752
  } catch {
18750
18753
  return null;
@@ -18767,7 +18770,7 @@ function lockFileMtimeIsOlderThan(lockPath, budgetMs) {
18767
18770
  }
18768
18771
  function clearStaleSentinelDir(lockPath) {
18769
18772
  try {
18770
- if (!existsSync5(lockPath))
18773
+ if (!existsSync6(lockPath))
18771
18774
  return true;
18772
18775
  const s = statSync3(lockPath);
18773
18776
  if (!s.isDirectory())
@@ -18889,9 +18892,9 @@ var init_flock = __esm(() => {
18889
18892
  // src/vault/vault.ts
18890
18893
  import { randomBytes, scryptSync, createCipheriv, createDecipheriv } from "node:crypto";
18891
18894
  import {
18892
- readFileSync as readFileSync6,
18895
+ readFileSync as readFileSync7,
18893
18896
  writeSync as writeSync3,
18894
- existsSync as existsSync6,
18897
+ existsSync as existsSync7,
18895
18898
  renameSync as renameSync2,
18896
18899
  mkdirSync as mkdirSync2,
18897
18900
  unlinkSync as unlinkSync2,
@@ -18899,18 +18902,18 @@ import {
18899
18902
  openSync as openSync3,
18900
18903
  closeSync as closeSync3,
18901
18904
  lstatSync,
18902
- realpathSync as realpathSync2
18905
+ realpathSync as realpathSync3
18903
18906
  } from "node:fs";
18904
- import { dirname, basename as basename2, resolve as resolve5 } from "node:path";
18907
+ import { dirname as dirname2, basename as basename2, resolve as resolve6 } from "node:path";
18905
18908
  function atomicWriteFileSync(path, data, mode) {
18906
18909
  let effectivePath = path;
18907
18910
  try {
18908
- if (existsSync6(path) && lstatSync(path).isSymbolicLink()) {
18909
- effectivePath = realpathSync2(path);
18911
+ if (existsSync7(path) && lstatSync(path).isSymbolicLink()) {
18912
+ effectivePath = realpathSync3(path);
18910
18913
  }
18911
18914
  } catch {}
18912
- const dir = dirname(resolve5(effectivePath));
18913
- const tmp = resolve5(dir, `.${basename2(effectivePath)}.${process.pid}.${Date.now()}.tmp`);
18915
+ const dir = dirname2(resolve6(effectivePath));
18916
+ const tmp = resolve6(dir, `.${basename2(effectivePath)}.${process.pid}.${Date.now()}.tmp`);
18914
18917
  try {
18915
18918
  const fd = openSync3(tmp, "wx", mode);
18916
18919
  try {
@@ -18930,7 +18933,7 @@ function atomicWriteFileSync(path, data, mode) {
18930
18933
  } catch {}
18931
18934
  } catch (err) {
18932
18935
  try {
18933
- if (existsSync6(tmp))
18936
+ if (existsSync7(tmp))
18934
18937
  unlinkSync2(tmp);
18935
18938
  } catch {}
18936
18939
  throw err;
@@ -18978,12 +18981,12 @@ function normalizeSecrets(raw) {
18978
18981
  return out;
18979
18982
  }
18980
18983
  function openVault(passphrase, vaultPath) {
18981
- if (!existsSync6(vaultPath)) {
18984
+ if (!existsSync7(vaultPath)) {
18982
18985
  throw new VaultError(`Vault file not found: ${vaultPath}`);
18983
18986
  }
18984
18987
  let vaultFile;
18985
18988
  try {
18986
- vaultFile = JSON.parse(readFileSync6(vaultPath, "utf8"));
18989
+ vaultFile = JSON.parse(readFileSync7(vaultPath, "utf8"));
18987
18990
  } catch {
18988
18991
  throw new VaultError(`Failed to read vault file: ${vaultPath}`);
18989
18992
  }
@@ -19005,7 +19008,7 @@ function openVault(passphrase, vaultPath) {
19005
19008
  return normalizeSecrets(vaultData.secrets ?? {});
19006
19009
  }
19007
19010
  function saveVault(passphrase, vaultPath, secrets) {
19008
- if (!existsSync6(vaultPath)) {
19011
+ if (!existsSync7(vaultPath)) {
19009
19012
  throw new VaultError(`Vault file not found: ${vaultPath}`);
19010
19013
  }
19011
19014
  let releaseLock = null;
@@ -19020,7 +19023,7 @@ function saveVault(passphrase, vaultPath, secrets) {
19020
19023
  try {
19021
19024
  let vaultFile;
19022
19025
  try {
19023
- vaultFile = JSON.parse(readFileSync6(vaultPath, "utf8"));
19026
+ vaultFile = JSON.parse(readFileSync7(vaultPath, "utf8"));
19024
19027
  } catch {
19025
19028
  throw new VaultError(`Failed to read vault file: ${vaultPath}`);
19026
19029
  }
@@ -19456,15 +19459,72 @@ var init_resolver = __esm(() => {
19456
19459
 
19457
19460
  // src/vault/broker/server.ts
19458
19461
  import * as net from "node:net";
19459
- import { mkdirSync as mkdirSync8, chmodSync as chmodSync5, chownSync as chownSync2, existsSync as existsSync12, readFileSync as readFileSync12, readdirSync as readdirSync5, statSync as statSync7, unlinkSync as unlinkSync6, writeFileSync as writeFileSync5 } from "node:fs";
19462
+ import { mkdirSync as mkdirSync8, chmodSync as chmodSync5, chownSync as chownSync2, existsSync as existsSync13, readFileSync as readFileSync13, readdirSync as readdirSync5, statSync as statSync7, unlinkSync as unlinkSync6, writeFileSync as writeFileSync5 } from "node:fs";
19460
19463
 
19461
19464
  // src/agents/compose.ts
19462
19465
  init_schema();
19463
19466
 
19464
19467
  // src/agents/scaffold.ts
19465
- import { dirname as dirname2, isAbsolute, join as join4, relative, resolve as resolve6 } from "node:path";
19466
19468
  init_atomic();
19467
19469
 
19470
+ // src/util/shipped-assets.ts
19471
+ import { existsSync, readFileSync, realpathSync } from "node:fs";
19472
+ import { dirname, resolve } from "node:path";
19473
+ var FHS_SHARE_ROOTS = [
19474
+ "/usr/local/share/switchroom",
19475
+ "/usr/share/switchroom"
19476
+ ];
19477
+ var PROFILES_ASSET = {
19478
+ asset: "profiles",
19479
+ envVar: "SWITCHROOM_PROFILES_ROOT"
19480
+ };
19481
+ function orderedCandidates(spec, probe) {
19482
+ const out = [];
19483
+ const push = (path, source) => {
19484
+ if (!out.some((c) => c.path === path))
19485
+ out.push({ path, source });
19486
+ };
19487
+ if (probe.bundleDir) {
19488
+ push(resolve(probe.bundleDir, "../..", spec.asset), "npm");
19489
+ push(resolve(probe.bundleDir, spec.asset), "image");
19490
+ }
19491
+ if (probe.execPath) {
19492
+ push(resolve(dirname(probe.execPath), "../share/switchroom", spec.asset), "sea-sibling");
19493
+ }
19494
+ for (const root of FHS_SHARE_ROOTS) {
19495
+ push(resolve(root, spec.asset), "fhs");
19496
+ }
19497
+ return out;
19498
+ }
19499
+ function resolveShippedAsset(spec, probe) {
19500
+ const env = probe.env ?? process.env;
19501
+ const override = env[spec.envVar]?.trim();
19502
+ if (override) {
19503
+ const path = canonicalise(resolve(override), probe);
19504
+ return { path, candidates: [path], source: "env" };
19505
+ }
19506
+ const exists = probe.exists ?? existsSync;
19507
+ const candidates = orderedCandidates(spec, probe);
19508
+ for (const c of candidates) {
19509
+ if (exists(c.path)) {
19510
+ return {
19511
+ path: canonicalise(c.path, probe),
19512
+ candidates: candidates.map((x) => x.path),
19513
+ source: c.source
19514
+ };
19515
+ }
19516
+ }
19517
+ return { path: null, candidates: candidates.map((x) => x.path), source: "none" };
19518
+ }
19519
+ function canonicalise(path, probe) {
19520
+ const realpath = probe.realpath ?? realpathSync;
19521
+ try {
19522
+ return realpath(path);
19523
+ } catch {
19524
+ return path;
19525
+ }
19526
+ }
19527
+
19468
19528
  // src/agents/agent-uid.ts
19469
19529
  init_peercred();
19470
19530
  import { createHash } from "node:crypto";
@@ -19695,37 +19755,29 @@ var AUDIT_ROOT = join(homedir2(), ".switchroom", "audit");
19695
19755
 
19696
19756
  // src/agents/profiles.ts
19697
19757
  var import_handlebars = __toESM(require_lib(), 1);
19698
- import { readFileSync as readFileSync4, writeFileSync, existsSync as existsSync4, readdirSync as readdirSync2, statSync as statSync2, copyFileSync, mkdirSync, realpathSync } from "node:fs";
19699
- import { resolve as resolve4, join as join2, sep as pathSep } from "node:path";
19700
- function resolveProfilesRoot() {
19701
- const envOverride = process.env.SWITCHROOM_PROFILES_ROOT?.trim();
19702
- if (envOverride) {
19703
- return resolve4(envOverride);
19704
- }
19705
- const candidates = [
19706
- resolve4(import.meta.dirname, "../../profiles"),
19707
- resolve4(import.meta.dirname, "profiles")
19708
- ];
19709
- for (const candidate of candidates) {
19710
- if (existsSync4(candidate)) {
19711
- return candidate;
19712
- }
19713
- }
19714
- return candidates[0];
19758
+ import { readFileSync as readFileSync5, writeFileSync, existsSync as existsSync5, readdirSync as readdirSync2, statSync as statSync2, copyFileSync, mkdirSync, realpathSync as realpathSync2 } from "node:fs";
19759
+ import { resolve as resolve5, join as join2, sep as pathSep } from "node:path";
19760
+ function resolveProfilesRootDetailed() {
19761
+ return resolveShippedAsset(PROFILES_ASSET, {
19762
+ bundleDir: import.meta.dirname,
19763
+ execPath: process.execPath
19764
+ });
19715
19765
  }
19716
- var PROFILES_ROOT = resolveProfilesRoot();
19766
+ var PROFILES_RESOLUTION = resolveProfilesRootDetailed();
19767
+ var PROFILES_ROOT = PROFILES_RESOLUTION.path ?? PROFILES_RESOLUTION.candidates[0];
19768
+ var PROFILES_ROOT_SEARCH = PROFILES_RESOLUTION.candidates;
19717
19769
  import_handlebars.default.registerHelper("json", (value) => {
19718
19770
  return new import_handlebars.default.SafeString(JSON.stringify(value, null, 2));
19719
19771
  });
19720
19772
  import_handlebars.default.registerHelper("isNumber", (value) => {
19721
19773
  return typeof value === "number" && Number.isFinite(value);
19722
19774
  });
19723
- var SHARED_FRAGMENTS_DIR = resolve4(PROFILES_ROOT, "_shared");
19775
+ var SHARED_FRAGMENTS_DIR = resolve5(PROFILES_ROOT, "_shared");
19724
19776
  var SHARED_FRAGMENTS = ["vault-protocol", "agent-self-service", "execution-discipline", "reply-discipline", "dev-protocol"];
19725
19777
  for (const name of SHARED_FRAGMENTS) {
19726
19778
  const fragPath = join2(SHARED_FRAGMENTS_DIR, `${name}.md.hbs`);
19727
- if (existsSync4(fragPath)) {
19728
- import_handlebars.default.registerPartial(name, readFileSync4(fragPath, "utf-8"));
19779
+ if (existsSync5(fragPath)) {
19780
+ import_handlebars.default.registerPartial(name, readFileSync5(fragPath, "utf-8"));
19729
19781
  }
19730
19782
  }
19731
19783
 
@@ -19928,7 +19980,8 @@ var HINDSIGHT_PERF_OVERRIDE_ONLY_KEYS = new Set([
19928
19980
  "HINDSIGHT_API_SEMANTIC_MIN_SIMILARITY",
19929
19981
  "HINDSIGHT_MCP_RECALL_BUDGET_MODE",
19930
19982
  "HINDSIGHT_API_LLM_TEMPERATURE_REFLECT",
19931
- "HINDSIGHT_API_RETAIN_WALL_TIMEOUT"
19983
+ "HINDSIGHT_API_RETAIN_WALL_TIMEOUT",
19984
+ "HINDSIGHT_API_CONSOLIDATION_RECALL_MAX_CONCURRENT"
19932
19985
  ]);
19933
19986
  var HINDSIGHT_WORKER_SLOT_TYPES = [
19934
19987
  "consolidation",
@@ -20023,6 +20076,15 @@ var DEFAULT_RETAIN_MISSION = `Extract durable facts that will still be true and
20023
20076
  ` + `- Hindsight's own errors, retries, backlogs, or internal state — the memory
20024
20077
  ` + ` system's self-reports are not memories.
20025
20078
  ` + `- Restatements of the user's current request or the task in progress.
20079
+ ` + `- The assistant's own answers, summaries, recaps, or reflect output. Before
20080
+ ` + ` extracting, ask: does anything in this transcript support this claim OTHER
20081
+ ` + ` than the assistant having asserted it? If not, drop it — a model's own
20082
+ ` + ` synthesis re-extracted as a fact is how a guess becomes permanent.
20083
+ ` + ` Concretely, never produce a fact whose only support is the assistant
20084
+ ` + ` stating a date, a version, an attribution, a total, or a decision that the
20085
+ ` + ` user never confirmed and no tool output shows. An unverified claim recalls
20086
+ ` + ` later as though it had been established, which is worse than not
20087
+ ` + ` remembering it at all.
20026
20088
  ` + `- Volatile state written as a timeless assertion. A version, count, size,
20027
20089
  ` + ` backlog, status, or any "X is running Y" / "X is at Y" / "X is currently Y"
20028
20090
  ` + ` claim is true only at the instant it was said. Concretely, never produce a
@@ -20178,6 +20240,64 @@ var SUPERSEDED_RETAIN_MISSIONS = [
20178
20240
  ` + `
20179
20241
  ` + `If a candidate fact matches an exclusion, drop it rather than rewording it. If
20180
20242
  ` + `nothing durable remains, return an empty facts list.
20243
+ `,
20244
+ `Extract durable facts that will still be true and useful weeks from now, once this session is forgotten.
20245
+ ` + `
20246
+ ` + `DURABILITY GATE. Emit a candidate ONLY if it is one of these five classes:
20247
+ ` + `- PREFERENCE — what the user likes, wants, or always does; a standing rule or correction.
20248
+ ` + `- DECISION — a settled choice that changes how future work is done, including a choice NOT to do something. A decision about the mechanics of the CURRENT task (which worker to dispatch, which branch to rebase, which PR to merge now, what to do next) is process narration, not a durable decision — drop it unless it establishes a standing rule or permanently changes a system.
20249
+ ` + `- FINDING — a root cause, a measurement, or verified behaviour of a system. Include the number.
20250
+ ` + `- OUTCOME — a completed result that changed the world: what shipped, what a thing turned out to be. Not the act of shipping it.
20251
+ ` + `- RELATIONSHIP — who a person is, what a project or tool is, and how they connect.
20252
+ ` + `If a candidate fits none of the five, it is not a memory. Drop it; do not reword it into one.
20253
+ ` + `
20254
+ ` + `A preference revealed by a request is durable — record the preference (what the user likes, wants, or always does), not the request itself.
20255
+ ` + `
20256
+ ` + `A TOOL RESULT IS NOT A FACT. Before extracting, ask: is the subject of this
20257
+ ` + `candidate a file path, a command/process/agent/session id, a temp directory, or
20258
+ ` + `the location where some output was written? If yes, drop it — it is transcript
20259
+ ` + `exhaust, not memory.
20260
+ ` + `
20261
+ ` + `NEVER extract:
20262
+ ` + `- Tool results verbatim or paraphrased. Concretely, never produce a fact whose
20263
+ ` + ` text resembles any of these: "File created successfully at /path/to/file",
20264
+ ` + ` "A background command with ID bctz4yskm is running, and its output will be
20265
+ ` + ` written to /tmp/...", "Async agent a745598ba84e71df1 was launched successfully
20266
+ ` + ` and is running in the background", "User executed a Bash command to sleep for
20267
+ ` + ` 200 seconds", "The assistant used grep to locate 'truncateSync' in src/foo.ts".
20268
+ ` + `- Anything mentioning a path under /tmp, a scratchpad directory, or a .tmp file.
20269
+ ` + `- Agent tool-use traces or narration of what the assistant did (e.g. "the
20270
+ ` + ` assistant used X to query Y", "ran a search", "sent the message").
20271
+ ` + `- The act of delegating, dispatching, spawning, launching, steering or merging
20272
+ ` + ` work — including when it succeeded. "X was dispatched and completed" is the
20273
+ ` + ` session describing itself. Record only what the work LEARNED or CHANGED.
20274
+ ` + `- In-flight workflow/process narration (a sub-task started, paused, or is still
20275
+ ` + ` running) — retain the outcome only once the task completes or a decision is made.
20276
+ ` + `- Operation, request, batch, agent, command or session IDs, UUIDs, hashes, or error codes.
20277
+ ` + `- Slash commands the user typed and their effects (e.g. "User issued /clear to
20278
+ ` + ` reset assistant state").
20279
+ ` + `- Hindsight's own errors, retries, backlogs, or internal state — the memory
20280
+ ` + ` system's self-reports are not memories.
20281
+ ` + `- Restatements of the user's current request or the task in progress.
20282
+ ` + `- Volatile state written as a timeless assertion. A version, count, size,
20283
+ ` + ` backlog, status, or any "X is running Y" / "X is at Y" / "X is currently Y"
20284
+ ` + ` claim is true only at the instant it was said. Concretely, never produce a
20285
+ ` + ` fact whose text resembles any of these: "Switchroom fleet is running image
20286
+ ` + ` version v0.18.19", "The switchroom repo is at /path/to/fleet, version
20287
+ ` + ` v0.19.5", "Bank overlord has 43155 pending consolidations", "The build is
20288
+ ` + ` currently green". If the claim is worth keeping, put the date INSIDE the
20289
+ ` + ` fact text ("As of 2026-07-19 the fleet was running v0.18.19"); if you
20290
+ ` + ` cannot date it, drop it. An undated one is recalled forever as though it
20291
+ ` + ` were still true, which is worse than not remembering it at all.
20292
+ ` + `- Transient state (unread counts, build status, what is running right now) unless
20293
+ ` + ` the fact is explicitly dated, in which case record it as a dated observation.
20294
+ ` + `- Greetings, acknowledgements, and routine operational chatter.
20295
+ ` + `
20296
+ ` + `Write each fact so it stands alone: name the thing, the number, and the date. A
20297
+ ` + `sentence that only makes sense while reading this transcript is not durable.
20298
+ ` + `
20299
+ ` + `If a candidate fact matches an exclusion, drop it rather than rewording it. If
20300
+ ` + `nothing durable remains, return an empty facts list.
20181
20301
  `
20182
20302
  ];
20183
20303
  var DEFAULT_OBSERVATIONS_MISSION = `Synthesise durable, standing knowledge about the people, projects, and systems this agent works with: preferences and standing rules, roles and relationships, skills and recurring patterns, technical and operational decisions with their rationale, and the state of long-running work once it lands.
@@ -20255,6 +20375,22 @@ var PROFILE_MEMORY_DEFAULTS = {
20255
20375
  var warnedMissingPool = new Set;
20256
20376
  var warnedMissingDefault = new Set;
20257
20377
 
20378
+ // src/memory/hindsight-retain-provenance.ts
20379
+ var RETAIN_PROVENANCE_TAG = "source:transcript";
20380
+ var RETAIN_TAGS_DEFAULT = Object.freeze([
20381
+ "{session_id}",
20382
+ RETAIN_PROVENANCE_TAG
20383
+ ]);
20384
+
20385
+ // src/memory/hindsight-seed-directives.ts
20386
+ var ANTI_CONFABULATION_DIRECTIVE = `When the retrieved memories do not support an answer, say so instead of composing one.
20387
+ ` + `
20388
+ ` + `- If nothing retrieved scores meaningfully above zero, or what came back is off-topic, the honest answer is that the bank does not know. Say that plainly, and say what would settle it.
20389
+ ` + `- Never assert a date, a version, a number, an attribution, a decision, or an outcome that no retrieved memory states. A question that presupposes something is not evidence for it: asked when a decision was made, when the bank records no such decision, answer that none is recorded — do not supply one.
20390
+ ` + `- Keep what the bank RECORDED separate from what you INFER. State inferences as inferences, and say which memory a recorded fact came from.
20391
+ ` + `- Treat a memory that was extracted from a conversation transcript as an unverified claim, not an established fact — it may be an assistant's own earlier guess. If it is the only support for an answer, say that it is.
20392
+ ` + "- Partial knowledge with its gaps named is more useful than a fluent answer that fills them in.";
20393
+
20258
20394
  // src/telegram/state.ts
20259
20395
  init_paths();
20260
20396
 
@@ -20292,7 +20428,6 @@ var RECALL_PASSTHROUGH_DEFAULTS = Object.freeze({
20292
20428
  init_paths();
20293
20429
 
20294
20430
  // src/agents/scaffold.ts
20295
- var REPO_ROOT = resolve6(import.meta.dirname, "../..");
20296
20431
  var CLAUDE_MD_YOURS_PLACEHOLDER = "This space is yours. Add per-agent rules, exceptions, or context the " + "Switchroom template doesn't capture. Everything above the marker line is " + "regenerated on every apply; this section is preserved.";
20297
20432
  var pendingBankOps = new Set;
20298
20433
  var SWITCHROOM_OWNED_SETTINGS_KEYS = new Set([
@@ -20378,8 +20513,8 @@ function migrateLegacyGrantsDbLocation(newDbPath = getGrantsDbPath(), deps) {
20378
20513
  var BIND_MOUNT_EXACT_SOURCE_DENY = new Set(["/var/run/docker.sock"]);
20379
20514
 
20380
20515
  // src/vault/broker/write-token-file.ts
20381
- import { chownSync, mkdirSync as mkdirSync4, readFileSync as readFileSync7, writeFileSync as writeFileSync2 } from "node:fs";
20382
- import { join as join6 } from "node:path";
20516
+ import { chownSync, mkdirSync as mkdirSync4, readFileSync as readFileSync8, writeFileSync as writeFileSync2 } from "node:fs";
20517
+ import { join as join5 } from "node:path";
20383
20518
  class TokenFileWriteError extends Error {
20384
20519
  tokenPath;
20385
20520
  constructor(tokenPath, message, options) {
@@ -20396,13 +20531,13 @@ function writeAgentTokenFile(args, deps = {}) {
20396
20531
  const writeFile = deps.writeFile ?? ((p, d) => {
20397
20532
  writeFileSync2(p, d, { mode: 384 });
20398
20533
  });
20399
- const readFile = deps.readFile ?? ((p) => readFileSync7(p, "utf8"));
20534
+ const readFile = deps.readFile ?? ((p) => readFileSync8(p, "utf8"));
20400
20535
  const chown = deps.chown ?? ((p, uid, gid) => {
20401
20536
  chownSync(p, uid, gid);
20402
20537
  });
20403
20538
  const agentUid = deps.agentUid ?? allocateAgentUid;
20404
- const tokenDir = join6(agentsDir, agent);
20405
- const tokenPath = join6(tokenDir, ".vault-token");
20539
+ const tokenDir = join5(agentsDir, agent);
20540
+ const tokenPath = join5(tokenDir, ".vault-token");
20406
20541
  try {
20407
20542
  mkdir(tokenDir);
20408
20543
  } catch (err) {
@@ -20455,26 +20590,26 @@ init_vault();
20455
20590
  import {
20456
20591
  copyFileSync as copyFileSync2,
20457
20592
  chmodSync as chmodSync2,
20458
- existsSync as existsSync8,
20593
+ existsSync as existsSync9,
20459
20594
  fsyncSync as fsyncSync4,
20460
20595
  lstatSync as lstatSync2,
20461
20596
  mkdirSync as mkdirSync5,
20462
20597
  openSync as openSync4,
20463
20598
  closeSync as closeSync4,
20464
- readFileSync as readFileSync8,
20599
+ readFileSync as readFileSync9,
20465
20600
  renameSync as renameSync4,
20466
20601
  statSync as statSync4,
20467
20602
  symlinkSync,
20468
20603
  unlinkSync as unlinkSync3
20469
20604
  } from "node:fs";
20470
20605
  import { createHash as createHash2 } from "node:crypto";
20471
- import { basename as basename3, dirname as dirname4, join as join7 } from "node:path";
20606
+ import { basename as basename3, dirname as dirname4, join as join6 } from "node:path";
20472
20607
  function vaultLayoutPaths(home2) {
20473
- const switchroomRoot = join7(home2, ".switchroom");
20608
+ const switchroomRoot = join6(home2, ".switchroom");
20474
20609
  return {
20475
- oldPath: join7(switchroomRoot, "vault.enc"),
20476
- newPath: join7(switchroomRoot, "vault", "vault.enc"),
20477
- parent: join7(switchroomRoot, "vault"),
20610
+ oldPath: join6(switchroomRoot, "vault.enc"),
20611
+ newPath: join6(switchroomRoot, "vault", "vault.enc"),
20612
+ parent: join6(switchroomRoot, "vault"),
20478
20613
  switchroomRoot
20479
20614
  };
20480
20615
  }
@@ -20483,11 +20618,11 @@ function inspectVaultLayout(home2) {
20483
20618
  }
20484
20619
  function runMigration(home2, opts) {
20485
20620
  const { oldPath, newPath, parent, switchroomRoot } = vaultLayoutPaths(home2);
20486
- const lockTarget = existsSync8(newPath) ? newPath : existsSync8(oldPath) ? oldPath : null;
20621
+ const lockTarget = existsSync9(newPath) ? newPath : existsSync9(oldPath) ? oldPath : null;
20487
20622
  const release = !opts.dryRun && lockTarget !== null ? acquireVaultLock(lockTarget) : null;
20488
20623
  try {
20489
20624
  const oldStat = lstatSyncOrNull(oldPath);
20490
- const newExists = existsSync8(newPath);
20625
+ const newExists = existsSync9(newPath);
20491
20626
  if (oldStat === null && !newExists) {
20492
20627
  return { kind: "no-vault" };
20493
20628
  }
@@ -20551,12 +20686,12 @@ function lstatSyncOrNull(path2) {
20551
20686
  }
20552
20687
  }
20553
20688
  function sha256File(path2) {
20554
- const data = readFileSync8(path2);
20689
+ const data = readFileSync9(path2);
20555
20690
  return createHash2("sha256").update(data).digest("hex");
20556
20691
  }
20557
20692
  function atomicReplaceWithSymlink(target, linkTarget) {
20558
- const tmp = join7(dirname4(target), `.${basename3(target)}.symlink-tmp`);
20559
- if (existsSync8(tmp)) {
20693
+ const tmp = join6(dirname4(target), `.${basename3(target)}.symlink-tmp`);
20694
+ if (existsSync9(tmp)) {
20560
20695
  try {
20561
20696
  unlinkSync3(tmp);
20562
20697
  } catch {}
@@ -20589,11 +20724,11 @@ import { createHmac, randomBytes as randomBytes2, createCipheriv as createCipher
20589
20724
  import {
20590
20725
  chmodSync as chmodSync3,
20591
20726
  closeSync as closeSync5,
20592
- existsSync as existsSync9,
20727
+ existsSync as existsSync10,
20593
20728
  fsyncSync as fsyncSync5,
20594
20729
  mkdirSync as mkdirSync6,
20595
20730
  openSync as openSync5,
20596
- readFileSync as readFileSync9,
20731
+ readFileSync as readFileSync10,
20597
20732
  renameSync as renameSync5,
20598
20733
  unlinkSync as unlinkSync4,
20599
20734
  writeSync as writeSync4
@@ -20632,7 +20767,7 @@ function readMachineId() {
20632
20767
  }
20633
20768
  for (const path2 of [MACHINE_ID_PRIMARY, MACHINE_ID_FALLBACK]) {
20634
20769
  try {
20635
- const id = readFileSync9(path2, "utf8").trim();
20770
+ const id = readFileSync10(path2, "utf8").trim();
20636
20771
  if (id.length > 0)
20637
20772
  return id;
20638
20773
  } catch {}
@@ -20669,12 +20804,12 @@ function decryptAutoUnlock(blob, machineId) {
20669
20804
  }
20670
20805
  }
20671
20806
  function readAutoUnlockFile(filePath) {
20672
- if (!existsSync9(filePath)) {
20807
+ if (!existsSync10(filePath)) {
20673
20808
  throw new AutoUnlockDecryptError("io");
20674
20809
  }
20675
20810
  let blob;
20676
20811
  try {
20677
- blob = readFileSync9(filePath);
20812
+ blob = readFileSync10(filePath);
20678
20813
  } catch {
20679
20814
  throw new AutoUnlockDecryptError("io");
20680
20815
  }
@@ -20759,7 +20894,7 @@ function seedChain(path2) {
20759
20894
  // src/vault/broker/test-isolation-guard.ts
20760
20895
  import { mkdtempSync } from "node:fs";
20761
20896
  import { homedir as homedir5, tmpdir } from "node:os";
20762
- import { join as join8, resolve as resolve8, sep } from "node:path";
20897
+ import { join as join7, resolve as resolve8, sep } from "node:path";
20763
20898
  function isTestRuntime() {
20764
20899
  return process.env.VITEST !== undefined;
20765
20900
  }
@@ -20780,7 +20915,7 @@ function safeVaultPath(requestedPath) {
20780
20915
  return requestedPath;
20781
20916
  if (!isUnderRealSwitchroomHome(requestedPath))
20782
20917
  return requestedPath;
20783
- const redirected = join8(mkdtempSync(join8(tmpdir(), "sw-test-vault-")), "vault.enc");
20918
+ const redirected = join7(mkdtempSync(join7(tmpdir(), "sw-test-vault-")), "vault.enc");
20784
20919
  if (!warnedVault) {
20785
20920
  warnedVault = true;
20786
20921
  process.stderr.write(`[test-isolation guard] vault path redirected away from the ` + `production tree to an isolated tmpdir — a vault/broker test ` + `resolved its vault path inside ~/.switchroom. See CLAUDE.md ` + `"Vault & shared-state test discipline".
@@ -20796,7 +20931,7 @@ function safeAuditLogPath(requestedPath) {
20796
20931
  if (!isUnderRealSwitchroomHome(requestedPath))
20797
20932
  return requestedPath;
20798
20933
  if (!cachedTmpAuditLog) {
20799
- cachedTmpAuditLog = join8(mkdtempSync(join8(tmpdir(), "sw-test-audit-")), "vault-audit.log");
20934
+ cachedTmpAuditLog = join7(mkdtempSync(join7(tmpdir(), "sw-test-audit-")), "vault-audit.log");
20800
20935
  }
20801
20936
  if (!warnedAudit) {
20802
20937
  warnedAudit = true;
@@ -22943,8 +23078,8 @@ function listGrants(db, agent_slug) {
22943
23078
  }
22944
23079
 
22945
23080
  // src/vault/broker/reap-token-file.ts
22946
- import { readFileSync as readFileSync11, writeFileSync as writeFileSync4 } from "node:fs";
22947
- import { join as join11 } from "node:path";
23081
+ import { readFileSync as readFileSync12, writeFileSync as writeFileSync4 } from "node:fs";
23082
+ import { join as join10 } from "node:path";
22948
23083
  var REAPABLE_REASONS = new Set(["grant-invalid", "grant-expired"]);
22949
23084
  function reapUnusableGrantToken(args, deps = {}) {
22950
23085
  const { agentsDir, agent, presentedToken, reason } = args;
@@ -22952,9 +23087,9 @@ function reapUnusableGrantToken(args, deps = {}) {
22952
23087
  return "kept:reason-not-reapable";
22953
23088
  if (!agent)
22954
23089
  return "kept:no-agent-identity";
22955
- const readFile = deps.readFile ?? ((p) => readFileSync11(p, "utf8"));
23090
+ const readFile = deps.readFile ?? ((p) => readFileSync12(p, "utf8"));
22956
23091
  const writeFile = deps.writeFile ?? ((p, d) => writeFileSync4(p, d, { mode: 384 }));
22957
- const tokenPath = join11(agentsDir, agent, ".vault-token");
23092
+ const tokenPath = join10(agentsDir, agent, ".vault-token");
22958
23093
  let onDisk;
22959
23094
  try {
22960
23095
  onDisk = readFile(tokenPath);
@@ -23539,7 +23674,7 @@ class VaultBroker {
23539
23674
  chmodSync5(parentDir, 448);
23540
23675
  } catch {}
23541
23676
  for (const p of [this.socketPath, this.unlockSocketPath]) {
23542
- if (existsSync12(p)) {
23677
+ if (existsSync13(p)) {
23543
23678
  try {
23544
23679
  unlinkSync6(p);
23545
23680
  } catch {}
@@ -23575,7 +23710,7 @@ class VaultBroker {
23575
23710
  try {
23576
23711
  if (ready) {
23577
23712
  writeFileSync5(p, "", { mode: 384 });
23578
- } else if (existsSync12(p)) {
23713
+ } else if (existsSync13(p)) {
23579
23714
  unlinkSync6(p);
23580
23715
  }
23581
23716
  } catch {}
@@ -23608,7 +23743,7 @@ class VaultBroker {
23608
23743
  try {
23609
23744
  entry.server.close();
23610
23745
  } catch {}
23611
- if (existsSync12(sockPath)) {
23746
+ if (existsSync13(sockPath)) {
23612
23747
  try {
23613
23748
  unlinkSync6(sockPath);
23614
23749
  } catch {}
@@ -23616,7 +23751,7 @@ class VaultBroker {
23616
23751
  }
23617
23752
  this.agentServers.clear();
23618
23753
  for (const p of [this.socketPath, this.unlockSocketPath]) {
23619
- if (p && existsSync12(p)) {
23754
+ if (p && existsSync13(p)) {
23620
23755
  try {
23621
23756
  unlinkSync6(p);
23622
23757
  } catch {}
@@ -23624,7 +23759,7 @@ class VaultBroker {
23624
23759
  }
23625
23760
  try {
23626
23761
  const pidPath = resolvePath(PID_FILE_DEFAULT);
23627
- if (existsSync12(pidPath))
23762
+ if (existsSync13(pidPath))
23628
23763
  unlinkSync6(pidPath);
23629
23764
  } catch {}
23630
23765
  }
@@ -23650,7 +23785,7 @@ class VaultBroker {
23650
23785
  return new Promise((resolveP, rejectP) => {
23651
23786
  if (abs.endsWith("/sock")) {
23652
23787
  const dir = abs.slice(0, -"/sock".length);
23653
- if (existsSync12(dir)) {
23788
+ if (existsSync13(dir)) {
23654
23789
  try {
23655
23790
  chownSync2(dir, 0, 0);
23656
23791
  } catch {}
@@ -23659,7 +23794,7 @@ class VaultBroker {
23659
23794
  } catch {}
23660
23795
  }
23661
23796
  }
23662
- if (existsSync12(abs)) {
23797
+ if (existsSync13(abs)) {
23663
23798
  try {
23664
23799
  unlinkSync6(abs);
23665
23800
  } catch (err) {
@@ -23691,7 +23826,7 @@ class VaultBroker {
23691
23826
  } catch {}
23692
23827
  this.agentServers.set(abs, { server, agentName });
23693
23828
  const unlockAbs = unlockSocketFor(abs);
23694
- if (existsSync12(unlockAbs)) {
23829
+ if (existsSync13(unlockAbs)) {
23695
23830
  try {
23696
23831
  unlinkSync6(unlockAbs);
23697
23832
  } catch {}
@@ -23746,7 +23881,7 @@ class VaultBroker {
23746
23881
  if (!Number.isFinite(uid) || uid <= 0)
23747
23882
  return;
23748
23883
  try {
23749
- if (existsSync12(this.vaultPath))
23884
+ if (existsSync13(this.vaultPath))
23750
23885
  chownSync2(this.vaultPath, uid, uid);
23751
23886
  } catch {}
23752
23887
  }
@@ -23759,7 +23894,7 @@ class VaultBroker {
23759
23894
  const unlockAbs = unlockSocketFor(abs);
23760
23895
  if (abs.endsWith("/sock")) {
23761
23896
  const dir = abs.slice(0, -"/sock".length);
23762
- if (existsSync12(dir)) {
23897
+ if (existsSync13(dir)) {
23763
23898
  try {
23764
23899
  chownSync2(dir, 0, 0);
23765
23900
  } catch {}
@@ -23769,7 +23904,7 @@ class VaultBroker {
23769
23904
  }
23770
23905
  }
23771
23906
  for (const p of [abs, unlockAbs]) {
23772
- if (existsSync12(p)) {
23907
+ if (existsSync13(p)) {
23773
23908
  try {
23774
23909
  unlinkSync6(p);
23775
23910
  } catch {}
@@ -24784,7 +24919,7 @@ class VaultBroker {
24784
24919
  if (row && AgentNameSchema.safeParse(row.agent_slug).success) {
24785
24920
  const agentsDir = this.config ? resolveAgentsDir(this.config) : path5.join(os3.homedir(), ".switchroom", "agents");
24786
24921
  const tokenPath = path5.join(agentsDir, row.agent_slug, ".vault-token");
24787
- if (existsSync12(tokenPath)) {
24922
+ if (existsSync13(tokenPath)) {
24788
24923
  try {
24789
24924
  unlinkSync6(tokenPath);
24790
24925
  } catch {}
@@ -25022,7 +25157,7 @@ class VaultBroker {
25022
25157
  const envPath = process.env.SWITCHROOM_VAULT_BROKER_AUTO_UNLOCK_PATH;
25023
25158
  const configuredPath = (envPath && envPath.length > 0 ? envPath : undefined) ?? this.config?.vault?.broker?.autoUnlockCredentialPath ?? DEFAULT_AUTO_UNLOCK_PATH;
25024
25159
  const filePath = resolvePath(configuredPath);
25025
- if (!existsSync12(filePath))
25160
+ if (!existsSync13(filePath))
25026
25161
  return false;
25027
25162
  try {
25028
25163
  if (statSync7(filePath).size === 0)
@@ -25065,7 +25200,7 @@ class VaultBroker {
25065
25200
  const credPath = `${dir}/vault-passphrase`;
25066
25201
  let passphrase;
25067
25202
  try {
25068
- passphrase = readFileSync12(credPath, "utf8").replace(/\n+$/, "");
25203
+ passphrase = readFileSync13(credPath, "utf8").replace(/\n+$/, "");
25069
25204
  } catch (err) {
25070
25205
  const code = err.code;
25071
25206
  if (code === "ENOENT") {
@@ -25137,7 +25272,7 @@ async function main() {
25137
25272
  const vaultPath = process.env.SWITCHROOM_VAULT_PATH;
25138
25273
  let perAgentTargets = [];
25139
25274
  try {
25140
- if (existsSync12(perAgentDir)) {
25275
+ if (existsSync13(perAgentDir)) {
25141
25276
  const entries = readdirSync5(perAgentDir, { withFileTypes: true });
25142
25277
  const flat = [];
25143
25278
  const subdirs = [];
@@ -25192,7 +25327,7 @@ async function main() {
25192
25327
  }
25193
25328
  const operatorUidStr = process.env.SWITCHROOM_BROKER_OPERATOR_UID;
25194
25329
  const operatorDir = "/run/switchroom/broker/operator";
25195
- if (operatorUidStr !== undefined && existsSync12(operatorDir)) {
25330
+ if (operatorUidStr !== undefined && existsSync13(operatorDir)) {
25196
25331
  const operatorUid = parseInt(operatorUidStr, 10);
25197
25332
  if (!Number.isFinite(operatorUid) || operatorUid <= 0) {
25198
25333
  process.stderr.write(`[vault-broker] SWITCHROOM_BROKER_OPERATOR_UID='${operatorUidStr}' is not a positive integer; skipping operator listener